@gfazioli/mantine-json-tree 2.1.0 → 3.1.0

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/README.md CHANGED
@@ -18,6 +18,7 @@
18
18
  ## Overview
19
19
 
20
20
  This component is created on top of the [Mantine](https://mantine.dev/) library.
21
+ It requires **Mantine 9.x** and **React 19**.
21
22
 
22
23
  [Mantine JsonTree](https://gfazioli.github.io/mantine-json-tree) provides a structured, interactive view of heterogeneous data—strings, numbers, booleans, nulls, objects, arrays, and even functions—organized as a collapsible tree. Developers can control initial expansion, show visual indent guides, and customize expand/collapse controls with arbitrary React nodes (e.g., emojis or styled icons) to match their design system. For function values, the component offers flexible rendering modes: show the function signature as text, hide functions entirely, or inspect them as objects when needed.
23
24
 
@@ -26,9 +27,13 @@ Wrapped with Mantine layout primitives like Paper, Stack, and SimpleGrid, JsonTr
26
27
  ## Features
27
28
 
28
29
  - Interactive collapsible tree view for any JSON-serializable data
30
+ - **Search** with text highlight, filtered tree view, and auto-expand matching branches
31
+ - **Redesigned toolbar** with key count badge, global copy, search toggle, and modern icons
32
+ - **Paper wrapper** with `withBorder` for bordered container look
33
+ - **Custom root name** via `rootName` prop
29
34
  - Syntax highlighting with customizable colors for 16+ data types (strings, numbers, booleans, null, Date, RegExp, Map, Set, BigInt, Symbol, React elements, etc.)
30
35
  - Dark mode support with automatic color adaptation
31
- - Copy-to-clipboard on individual nodes
36
+ - Copy-to-clipboard on individual nodes + global copy all JSON
32
37
  - Keyboard navigation (arrow keys, Space to expand, Ctrl+C to copy)
33
38
  - Configurable expansion depth with expand/collapse all controls
34
39
  - Controlled expand/collapse state with `expanded` and `onExpandedChange` props
@@ -39,10 +44,10 @@ Wrapped with Mantine layout primitives like Paper, Stack, and SimpleGrid, JsonTr
39
44
  - Sticky header support with configurable offset
40
45
  - Function display modes: as-string, hide, or as-object introspection
41
46
  - Responsive font size via Mantine breakpoint objects (CSS-native, no re-renders)
42
- - Full Mantine Styles API support with 13 style selectors and 25+ CSS variables
47
+ - Full Mantine Styles API support with 21 style selectors and 25+ CSS variables
43
48
  - Custom icons for expand/collapse and copy controls
44
49
  - Item count badges for objects and arrays
45
- - `onExpand`, `onCollapse`, `onNodeClick`, and `onCopy` callbacks
50
+ - `onExpand`, `onCollapse`, `onNodeClick`, `onCopy`, and `onCopyAll` callbacks
46
51
 
47
52
  > [!note]
48
53
  >
@@ -4,11 +4,13 @@
4
4
  var React = require('react');
5
5
  var iconsReact = require('@tabler/icons-react');
6
6
  var core = require('@mantine/core');
7
+ var hooks = require('@mantine/hooks');
7
8
  var JsonTreeMediaVariables = require('./JsonTreeMediaVariables.cjs');
8
9
  var utils = require('./lib/utils.cjs');
9
10
  var JsonTree_module = require('./JsonTree.module.css.cjs');
10
11
 
11
12
  const defaultProps = {
13
+ rootName: "root",
12
14
  defaultExpanded: false,
13
15
  maxDepth: 2,
14
16
  withExpandAll: false,
@@ -19,10 +21,71 @@ const defaultProps = {
19
21
  showPathOnHover: false,
20
22
  stickyHeader: false,
21
23
  displayFunctions: "as-string",
22
- expandAllControlIcon: /* @__PURE__ */ React.createElement(iconsReact.IconLibraryPlus, { size: 16 }),
23
- collapseAllControlIcon: /* @__PURE__ */ React.createElement(iconsReact.IconLibraryMinus, { size: 16 }),
24
- copyToClipboardIcon: /* @__PURE__ */ React.createElement(iconsReact.IconCopy, { size: 12 })
24
+ expandAllControlIcon: /* @__PURE__ */ React.createElement(iconsReact.IconArrowBarToDown, { size: 16 }),
25
+ collapseAllControlIcon: /* @__PURE__ */ React.createElement(iconsReact.IconArrowBarToUp, { size: 16 }),
26
+ copyToClipboardIcon: /* @__PURE__ */ React.createElement(iconsReact.IconCopy, { size: 12 }),
27
+ withBorder: false,
28
+ borderRadius: "sm",
29
+ withKeyCountBadge: false,
30
+ withCopyAll: false,
31
+ withSearch: false,
32
+ copyAllIcon: /* @__PURE__ */ React.createElement(iconsReact.IconCopy, { size: 16 }),
33
+ searchIcon: /* @__PURE__ */ React.createElement(iconsReact.IconSearch, { size: 16 }),
34
+ searchPlaceholder: "Filter keys and values...",
35
+ searchDebounce: 300
25
36
  };
37
+ function highlightText(text, query, getStyles) {
38
+ if (!query) {
39
+ return text;
40
+ }
41
+ const lowerText = text.toLowerCase();
42
+ const lowerQuery = query.toLowerCase();
43
+ const idx = lowerText.indexOf(lowerQuery);
44
+ if (idx === -1) {
45
+ return text;
46
+ }
47
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, text.substring(0, idx), /* @__PURE__ */ React.createElement("span", { ...getStyles("searchHighlight") }, text.substring(idx, idx + query.length)), text.substring(idx + query.length));
48
+ }
49
+ function CopyNodeButton({
50
+ icon,
51
+ getStyles,
52
+ onCopy
53
+ }) {
54
+ const [copied, setCopied] = React.useState(false);
55
+ const timeoutRef = React.useRef(null);
56
+ React.useEffect(() => {
57
+ return () => {
58
+ if (timeoutRef.current) {
59
+ clearTimeout(timeoutRef.current);
60
+ }
61
+ };
62
+ }, []);
63
+ const handleClick = async (e) => {
64
+ const success = await onCopy(e);
65
+ if (!success) {
66
+ return;
67
+ }
68
+ setCopied(true);
69
+ if (timeoutRef.current) {
70
+ clearTimeout(timeoutRef.current);
71
+ }
72
+ timeoutRef.current = setTimeout(() => {
73
+ setCopied(false);
74
+ timeoutRef.current = null;
75
+ }, 1500);
76
+ };
77
+ return /* @__PURE__ */ React.createElement(
78
+ core.ActionIcon,
79
+ {
80
+ size: "xs",
81
+ variant: "subtle",
82
+ color: copied ? "green" : "gray",
83
+ onClick: handleClick,
84
+ ...getStyles("copyButton")
85
+ },
86
+ copied ? /* @__PURE__ */ React.createElement(iconsReact.IconCheck, { size: 12 }) : icon
87
+ );
88
+ }
26
89
  function renderJSONNode({ node, expanded, hasChildren, elementProps, tree }, props, ctx, onNodeClick) {
27
90
  const {
28
91
  getStyles,
@@ -62,7 +125,9 @@ function renderJSONNode({ node, expanded, hasChildren, elementProps, tree }, pro
62
125
  const copy = JSON.stringify(value, null, 2);
63
126
  await navigator.clipboard.writeText(copy);
64
127
  onCopy?.(copy, value);
65
- } catch (error) {
128
+ return true;
129
+ } catch {
130
+ return false;
66
131
  }
67
132
  };
68
133
  const handleClick = () => {
@@ -119,26 +184,21 @@ function renderJSONNode({ node, expanded, hasChildren, elementProps, tree }, pro
119
184
  wrap: "nowrap",
120
185
  ...elementProps,
121
186
  onClick: handleClick,
122
- style: { cursor: onNodeClick ? "pointer" : "default", position: "relative" }
187
+ style: {
188
+ cursor: onNodeClick ? "pointer" : "default",
189
+ position: "relative",
190
+ backgroundColor: ctx.directMatches?.has(node.value) ? "rgba(251, 191, 36, 0.15)" : void 0,
191
+ borderRadius: ctx.directMatches?.has(node.value) ? "4px" : void 0
192
+ }
123
193
  },
124
194
  lineNumber,
125
195
  renderIndentGuides(),
126
- key !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("key"), "data-key": key }, key), /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("keyValueSeparator") }, ":")),
196
+ key !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("key"), "data-key": key }, ctx.searchQuery ? highlightText(String(key), ctx.searchQuery, getStyles) : key), /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("keyValueSeparator") }, ":")),
127
197
  (() => {
128
198
  const formattedValue = utils.formatValue(value, type);
129
- return /* @__PURE__ */ React.createElement(core.Code, { ...getStyles("value"), "data-type": type, "data-value": formattedValue }, formattedValue);
199
+ return /* @__PURE__ */ React.createElement(core.Code, { ...getStyles("value"), "data-type": type, "data-value": formattedValue }, ctx.searchQuery ? highlightText(formattedValue, ctx.searchQuery, getStyles) : formattedValue);
130
200
  })(),
131
- withCopyToClipboard && /* @__PURE__ */ React.createElement(
132
- core.ActionIcon,
133
- {
134
- size: "xs",
135
- variant: "subtle",
136
- color: "gray",
137
- onClick: handleCopy,
138
- ...getStyles("copyButton")
139
- },
140
- copyToClipboardIcon
141
- )
201
+ withCopyToClipboard && /* @__PURE__ */ React.createElement(CopyNodeButton, { icon: copyToClipboardIcon, getStyles, onCopy: handleCopy })
142
202
  )
143
203
  );
144
204
  }
@@ -182,7 +242,12 @@ function renderJSONNode({ node, expanded, hasChildren, elementProps, tree }, pro
182
242
  "data-expanded": expanded,
183
243
  "data-has-children": hasChildren,
184
244
  "data-type": type,
185
- style: { cursor: onNodeClick ? "pointer" : "default", position: "relative" }
245
+ style: {
246
+ cursor: onNodeClick ? "pointer" : "default",
247
+ position: "relative",
248
+ backgroundColor: ctx.directMatches?.has(node.value) ? "rgba(251, 191, 36, 0.15)" : void 0,
249
+ borderRadius: ctx.directMatches?.has(node.value) ? "4px" : void 0
250
+ }
186
251
  },
187
252
  lineNumber,
188
253
  renderIndentGuides(),
@@ -196,7 +261,7 @@ function renderJSONNode({ node, expanded, hasChildren, elementProps, tree }, pro
196
261
  },
197
262
  expandCollapseIcon
198
263
  ),
199
- key !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("key") }, key), /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("keyValueSeparator") }, ":")),
264
+ key !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("key") }, ctx.searchQuery ? highlightText(String(key), ctx.searchQuery, getStyles) : key), /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("keyValueSeparator") }, ":")),
200
265
  /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("bracket") }, openBracket),
201
266
  !expanded && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Text, { component: "span", size: "xs", ...getStyles("ellipsis") }, "..."), /* @__PURE__ */ React.createElement(core.Text, { component: "span", ...getStyles("bracket") }, closeBracket), itemCount !== void 0 && showItemsCount && /* @__PURE__ */ React.createElement(core.Badge, { size: "xs", variant: "light", color: "gray", ...getStyles("itemsCount") }, itemCount)),
202
267
  withCopyToClipboard && /* @__PURE__ */ React.createElement(
@@ -257,14 +322,25 @@ const varsResolver = core.createVarsResolver(
257
322
  lineNumber: { "--json-tree-color-line-number": "var(--mantine-color-gray-5)" },
258
323
  itemsCount: {},
259
324
  controls: {},
260
- copyButton: {}
325
+ copyButton: {},
326
+ paper: {},
327
+ toolbar: {},
328
+ keyCountBadge: {},
329
+ copyAllButton: {},
330
+ searchToggle: {},
331
+ searchBar: {},
332
+ searchInput: {},
333
+ searchHighlight: {
334
+ "--json-tree-search-highlight-color": "var(--mantine-color-yellow-3)"
335
+ }
261
336
  };
262
337
  }
263
338
  );
264
- const JsonTree = core.factory((_props, ref) => {
339
+ const JsonTree = core.factory((_props) => {
265
340
  const props = core.useProps("JsonTree", defaultProps, _props);
266
341
  const {
267
342
  data,
343
+ rootName,
268
344
  defaultExpanded,
269
345
  maxDepth,
270
346
  onNodeClick,
@@ -291,6 +367,19 @@ const JsonTree = core.factory((_props, ref) => {
291
367
  expandControlIcon,
292
368
  collapseControlIcon,
293
369
  size,
370
+ withBorder,
371
+ borderRadius,
372
+ withKeyCountBadge,
373
+ keyCountBadgeLabel,
374
+ withCopyAll,
375
+ copyAllIcon,
376
+ onCopyAll,
377
+ withSearch,
378
+ searchIcon,
379
+ searchPlaceholder,
380
+ searchQuery: controlledSearchQuery,
381
+ onSearchChange,
382
+ searchDebounce,
294
383
  classNames,
295
384
  style,
296
385
  styles,
@@ -313,8 +402,8 @@ const JsonTree = core.factory((_props, ref) => {
313
402
  });
314
403
  const responsiveClassName = core.useRandomClassName();
315
404
  const treeData = React.useMemo(
316
- () => [utils.convertToTreeData(data, void 0, "root", 0, displayFunctions)],
317
- [data, displayFunctions]
405
+ () => [utils.convertToTreeData(data, rootName ?? "root", rootName ?? "root", 0, displayFunctions)],
406
+ [data, rootName, displayFunctions]
318
407
  );
319
408
  const initialExpandedState = React.useMemo(() => {
320
409
  if (controlledExpanded) {
@@ -331,7 +420,7 @@ const JsonTree = core.factory((_props, ref) => {
331
420
  const expandedNodes = [];
332
421
  const traverse = (nodes, depth) => {
333
422
  nodes.forEach((node) => {
334
- if (depth < maxDepth && node.children) {
423
+ if (depth < (maxDepth ?? Infinity) && node.children) {
335
424
  expandedNodes.push(node.value);
336
425
  traverse(node.children, depth + 1);
337
426
  }
@@ -353,7 +442,7 @@ const JsonTree = core.factory((_props, ref) => {
353
442
  });
354
443
  tree.setExpandedState(state);
355
444
  }
356
- }, [controlledExpanded, tree.setExpandedState]);
445
+ }, [controlledExpanded]);
357
446
  const handleKeyDown = React.useCallback(
358
447
  async (e) => {
359
448
  if ((e.metaKey || e.ctrlKey) && e.key === "c" && withCopyToClipboard) {
@@ -379,6 +468,83 @@ const JsonTree = core.factory((_props, ref) => {
379
468
  },
380
469
  [withCopyToClipboard, treeData, onCopy]
381
470
  );
471
+ const totalKeyCount = React.useMemo(() => utils.getItemCount(data), [data]);
472
+ const [searchOpen, setSearchOpen] = React.useState(false);
473
+ const [searchQueryInternal, setSearchQueryInternal] = React.useState("");
474
+ const activeSearchQuery = controlledSearchQuery ?? searchQueryInternal ?? "";
475
+ const [debouncedQuery] = hooks.useDebouncedValue(activeSearchQuery, searchDebounce ?? 300);
476
+ const preSearchExpandedRef = React.useRef(null);
477
+ const searchResults = React.useMemo(
478
+ () => utils.searchTree(treeData, debouncedQuery),
479
+ [treeData, debouncedQuery]
480
+ );
481
+ const filteredTreeData = React.useMemo(() => {
482
+ if (!debouncedQuery || searchResults.matchedPaths.size === 0) {
483
+ return treeData;
484
+ }
485
+ return utils.filterTreeBySearch(treeData, searchResults.matchedPaths);
486
+ }, [treeData, debouncedQuery, searchResults]);
487
+ React.useEffect(() => {
488
+ if (debouncedQuery && searchResults.expandedPaths.length > 0) {
489
+ if (!preSearchExpandedRef.current) {
490
+ preSearchExpandedRef.current = { ...tree.expandedState };
491
+ }
492
+ const newState = {};
493
+ searchResults.expandedPaths.forEach((p) => {
494
+ newState[p] = true;
495
+ });
496
+ if (onExpandedChange) {
497
+ onExpandedChange(Object.keys(newState).filter((k) => newState[k]));
498
+ } else {
499
+ tree.setExpandedState(newState);
500
+ }
501
+ }
502
+ }, [debouncedQuery, searchResults]);
503
+ const handleClearSearch = React.useCallback(() => {
504
+ setSearchQueryInternal("");
505
+ onSearchChange?.("");
506
+ if (preSearchExpandedRef.current) {
507
+ if (onExpandedChange) {
508
+ onExpandedChange(
509
+ Object.keys(preSearchExpandedRef.current).filter((k) => preSearchExpandedRef.current[k])
510
+ );
511
+ } else {
512
+ tree.setExpandedState(preSearchExpandedRef.current);
513
+ }
514
+ preSearchExpandedRef.current = null;
515
+ }
516
+ }, [onExpandedChange, onSearchChange, tree]);
517
+ const handleCloseSearch = React.useCallback(() => {
518
+ setSearchOpen(false);
519
+ handleClearSearch();
520
+ }, [handleClearSearch]);
521
+ const handleExpandAll = React.useCallback(() => {
522
+ const allState = core.getTreeExpandedState(treeData, "*");
523
+ if (onExpandedChange) {
524
+ onExpandedChange(Object.keys(allState).filter((k) => allState[k]));
525
+ } else {
526
+ tree.expandAllNodes();
527
+ }
528
+ }, [treeData, onExpandedChange, tree]);
529
+ const handleCollapseAll = React.useCallback(() => {
530
+ if (onExpandedChange) {
531
+ onExpandedChange([]);
532
+ } else {
533
+ tree.collapseAllNodes();
534
+ }
535
+ }, [onExpandedChange, tree]);
536
+ const [copiedAll, setCopiedAll] = React.useState(false);
537
+ const handleCopyAll = React.useCallback(async () => {
538
+ try {
539
+ const json = JSON.stringify(data, null, 2);
540
+ await navigator.clipboard.writeText(json);
541
+ onCopyAll?.(json);
542
+ onCopy?.(json, data);
543
+ setCopiedAll(true);
544
+ setTimeout(() => setCopiedAll(false), 1500);
545
+ } catch {
546
+ }
547
+ }, [data, onCopyAll, onCopy]);
382
548
  const renderCtx = {
383
549
  getStyles,
384
550
  copyToClipboardIcon,
@@ -386,60 +552,99 @@ const JsonTree = core.factory((_props, ref) => {
386
552
  collapseControlIcon,
387
553
  onExpand,
388
554
  onCollapse,
389
- onExpandedChange
555
+ onExpandedChange,
556
+ searchQuery: debouncedQuery || void 0,
557
+ matchedPaths: debouncedQuery ? searchResults.matchedPaths : void 0,
558
+ directMatches: debouncedQuery ? searchResults.directMatches : void 0
390
559
  };
391
560
  const treeComponent = /* @__PURE__ */ React.createElement(
392
561
  core.Tree,
393
562
  {
394
- data: treeData,
563
+ data: filteredTreeData,
395
564
  tree,
396
565
  levelOffset: 32,
397
566
  renderNode: (payload) => renderJSONNode(payload, props, renderCtx, onNodeClick)
398
567
  }
399
568
  );
400
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(JsonTreeMediaVariables.JsonTreeMediaVariables, { size, selector: `.${responsiveClassName}` }), /* @__PURE__ */ React.createElement(
569
+ const showHeader = title || withExpandAll || withKeyCountBadge || withCopyAll || withSearch;
570
+ const content = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(JsonTreeMediaVariables.JsonTreeMediaVariables, { size, selector: `.${responsiveClassName}` }), /* @__PURE__ */ React.createElement(
401
571
  core.Box,
402
572
  {
403
- ref,
404
573
  ...getStyles("root", { className: responsiveClassName }),
405
574
  ...others,
406
575
  "data-line-numbers": showLineNumbers || void 0,
576
+ "data-searching": debouncedQuery ? true : void 0,
407
577
  onKeyDown: handleKeyDown
408
578
  },
409
- (title || withExpandAll) && /* @__PURE__ */ React.createElement(core.Group, { ...getStyles("header"), justify: "space-between", mod: { sticky: stickyHeader } }, title || /* @__PURE__ */ React.createElement("div", null), withExpandAll && utils.isExpandable(data) && /* @__PURE__ */ React.createElement(core.Group, { gap: "xs", style: { top: 10, zIndex: 1 } }, /* @__PURE__ */ React.createElement(
579
+ showHeader && /* @__PURE__ */ React.createElement(core.Group, { ...getStyles("header"), justify: "space-between", mod: { sticky: stickyHeader } }, /* @__PURE__ */ React.createElement(core.Group, { gap: "xs" }, title || /* @__PURE__ */ React.createElement("div", null), withKeyCountBadge && utils.isExpandable(data) && /* @__PURE__ */ React.createElement(core.Badge, { size: "sm", variant: "light", color: "gray", ...getStyles("keyCountBadge") }, keyCountBadgeLabel ? keyCountBadgeLabel(totalKeyCount) : `${totalKeyCount} ${Array.isArray(data) ? "items" : "keys"}`)), /* @__PURE__ */ React.createElement(core.Group, { gap: 4, ...getStyles("toolbar") }, withSearch && /* @__PURE__ */ React.createElement(
410
580
  core.ActionIcon,
411
581
  {
412
- size: "xs",
413
- variant: "transparent",
582
+ size: "sm",
583
+ variant: searchOpen ? "light" : "subtle",
584
+ color: "gray",
414
585
  onClick: () => {
415
- const allState = core.getTreeExpandedState(treeData, "*");
416
- if (onExpandedChange) {
417
- onExpandedChange(Object.keys(allState).filter((k) => allState[k]));
586
+ if (searchOpen) {
587
+ handleCloseSearch();
418
588
  } else {
419
- tree.expandAllNodes();
589
+ setSearchOpen(true);
420
590
  }
421
591
  },
592
+ ...getStyles("searchToggle")
593
+ },
594
+ searchIcon
595
+ ), withExpandAll && utils.isExpandable(data) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
596
+ core.ActionIcon,
597
+ {
598
+ size: "sm",
599
+ variant: "subtle",
600
+ color: "gray",
601
+ onClick: handleExpandAll,
422
602
  ...getStyles("controls")
423
603
  },
424
604
  expandAllControlIcon
425
605
  ), /* @__PURE__ */ React.createElement(
426
606
  core.ActionIcon,
427
607
  {
428
- size: "xs",
429
- variant: "transparent",
430
- onClick: () => {
431
- if (onExpandedChange) {
432
- onExpandedChange([]);
433
- } else {
434
- tree.collapseAllNodes();
435
- }
436
- },
608
+ size: "sm",
609
+ variant: "subtle",
610
+ color: "gray",
611
+ onClick: handleCollapseAll,
437
612
  ...getStyles("controls")
438
613
  },
439
614
  collapseAllControlIcon
615
+ )), withCopyAll && /* @__PURE__ */ React.createElement(
616
+ core.ActionIcon,
617
+ {
618
+ size: "sm",
619
+ variant: "subtle",
620
+ color: copiedAll ? "green" : "gray",
621
+ onClick: handleCopyAll,
622
+ ...getStyles("copyAllButton")
623
+ },
624
+ copiedAll ? /* @__PURE__ */ React.createElement(iconsReact.IconCheck, { size: 16 }) : copyAllIcon
625
+ ))),
626
+ searchOpen && withSearch && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(core.Divider, null), /* @__PURE__ */ React.createElement(core.Box, { ...getStyles("searchBar"), p: "xs" }, /* @__PURE__ */ React.createElement(
627
+ core.TextInput,
628
+ {
629
+ placeholder: searchPlaceholder,
630
+ value: activeSearchQuery,
631
+ onChange: (e) => {
632
+ const val = e.currentTarget.value;
633
+ setSearchQueryInternal(val);
634
+ onSearchChange?.(val);
635
+ },
636
+ leftSection: /* @__PURE__ */ React.createElement(iconsReact.IconSearch, { size: 14 }),
637
+ rightSection: activeSearchQuery ? /* @__PURE__ */ React.createElement(core.CloseButton, { size: "sm", onClick: handleClearSearch }) : null,
638
+ size: "sm",
639
+ ...getStyles("searchInput")
640
+ }
440
641
  ))),
441
642
  maxHeight ? /* @__PURE__ */ React.createElement(core.ScrollArea.Autosize, { mah: maxHeight }, treeComponent) : treeComponent
442
643
  ));
644
+ if (withBorder) {
645
+ return /* @__PURE__ */ React.createElement(core.Paper, { withBorder: true, radius: borderRadius, ...getStyles("paper") }, content);
646
+ }
647
+ return content;
443
648
  });
444
649
  JsonTree.classes = JsonTree_module;
445
650
  JsonTree.displayName = "JsonTree";