@asup/tree-of-nodes 0.9.1 → 1.0.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.
Files changed (33) hide show
  1. package/README.md +134 -1
  2. package/dist/cjs/main.js +75 -78
  3. package/dist/cjs/src/__mocks__/mockNodes.d.ts +4 -0
  4. package/dist/cjs/src/__mocks__/styleMock.d.ts +2 -0
  5. package/dist/cjs/src/__tests__/TreeNode.test.d.ts +1 -0
  6. package/dist/cjs/src/__tests__/TreeOfNodes.test.d.ts +1 -0
  7. package/dist/cjs/src/__tests__/WordEntry.test.d.ts +1 -0
  8. package/dist/cjs/{components → src/components}/ContextMenu.d.ts +0 -0
  9. package/dist/cjs/{components → src/components}/ContextMenuProvider.d.ts +0 -0
  10. package/dist/cjs/{components → src/components}/TreeNode.d.ts +0 -0
  11. package/dist/cjs/src/components/TreeOfNodes.d.ts +20 -0
  12. package/dist/cjs/src/components/TreeOfNodesContext.d.ts +28 -0
  13. package/dist/cjs/{components → src/components}/WordEntry.d.ts +2 -2
  14. package/dist/cjs/{components → src/components}/index.d.ts +2 -1
  15. package/dist/cjs/{main.d.ts → src/main.d.ts} +0 -0
  16. package/dist/main.js +62 -65
  17. package/dist/src/__mocks__/mockNodes.d.ts +4 -0
  18. package/dist/src/__mocks__/styleMock.d.ts +2 -0
  19. package/dist/src/__tests__/TreeNode.test.d.ts +1 -0
  20. package/dist/src/__tests__/TreeOfNodes.test.d.ts +1 -0
  21. package/dist/src/__tests__/WordEntry.test.d.ts +1 -0
  22. package/dist/{components → src/components}/ContextMenu.d.ts +0 -0
  23. package/dist/{components → src/components}/ContextMenuProvider.d.ts +0 -0
  24. package/dist/{components → src/components}/TreeNode.d.ts +0 -0
  25. package/dist/src/components/TreeOfNodes.d.ts +20 -0
  26. package/dist/src/components/TreeOfNodesContext.d.ts +28 -0
  27. package/dist/{components → src/components}/WordEntry.d.ts +2 -2
  28. package/dist/{components → src/components}/index.d.ts +2 -1
  29. package/dist/{main.d.ts → src/main.d.ts} +0 -0
  30. package/dist/tree-of-nodes.d.ts +30 -73
  31. package/package.json +6 -3
  32. package/dist/cjs/components/TreeOfNodes.d.ts +0 -44
  33. package/dist/components/TreeOfNodes.d.ts +0 -44
package/README.md CHANGED
@@ -1,5 +1,138 @@
1
- # Tree of nodes for REACT
1
+ [npm]: https://img.shields.io/npm/v/@asup/tree-of-nodes
2
+ [npm-url]: https://www.npmjs.com/package/@asup/tree-of-nodes
3
+ [size]: https://packagephobia.now.sh/badge?p=@asup/tree-of-nodes
4
+ [size-url]: https://packagephobia.now.sh/result?p=@asup/tree-of-nodes
5
+
6
+ [![npm][npm]][npm-url]
7
+ [![size][size]][size-url]
8
+ ![npm bundle size](https://img.shields.io/bundlephobia/min/@asup/simple-table)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/PaulDThomas/tree-of-nodes/master/LICENCE)
10
+
11
+ # @asup/tree-of-notes
2
12
 
3
13
  REACT treeview, because I couldn't quite find what I wanted.
4
14
 
5
15
  Works with/needs bootstrap, react-bootstrap, react-bootstrap-icons.
16
+ Uses a context menu to facilitate Add/Rename/Remove actions.
17
+
18
+ ## Installation
19
+
20
+ ```
21
+ # with npm
22
+ npm install @asup/tree-of-nodes
23
+ ```
24
+
25
+ ## Tree-of-nodes Usage
26
+
27
+ ```
28
+ import { iNodeUpdate, TreeNodeData, TreeOfNodes } from '@asup/tree-of-nodes';
29
+ import '@asup/tree-of-nodes/dist/style.css';
30
+
31
+ ... inside REACT component
32
+
33
+ <TreeOfNodes<T>
34
+ id: string;
35
+ nodeList: TreeNodeData<T>[];
36
+ roots: Key[];
37
+ selectedId?: Key;
38
+ handleSelect?: (ret: Key) => void;
39
+ onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
40
+ onRemove?: (childId: Key) => Promise<iNodeUpdate>;
41
+ onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
42
+ canAddRoot?: boolean;
43
+ canRemoveRoot?: boolean;
44
+ canRenameRoot?: boolean;
45
+ canAddChildren?: boolean;
46
+ canRemoveChildren?: boolean;
47
+ canRenameChildren?: boolean;
48
+ />
49
+ ```
50
+
51
+ The component expects a list of nodes of type `TreeNodeData<T>` with unique `Key`s.
52
+
53
+ ## Properties
54
+
55
+ | Prop | Description | Default |
56
+ | :---------------- | :---------------------------------------------------------------------------------------------- | :---------------: |
57
+ | id | HTML id attribute | |
58
+ | nodeList | Array of node data | |
59
+ | roots | One or more node Keys to use as the root of the tree |
60
+ | selectedId | Currently selectedId | |
61
+ | handleSelect | Function called when a node is clicked (selected), this should be used to update the selectedId | `() => {return;}` |
62
+ | onAdd | Function called when a new node is added, this should be used to update the nodeList | |
63
+ | onRemove | Function called when a node is removed, this should be used to update the nodeList | |
64
+ | onRename | Function called when a node is renamed, this should be used to update the nodeList | |
65
+ | ~~canAddRoot~~ | Not currently implemented | |
66
+ | canRemoveRoot | Allows removal of a root node in combination with specification of onRemove function | `false` |
67
+ | canRenameRoot | Allows renaming of a root node in combination with specification of onRename function | `false` |
68
+ | canAddChildren | Allows addition of children in combination with specification of onAdd function | `false` |
69
+ | canRemoveChildren | Allows renaming of non-root nodes in combination with specification of onRemove function | `false` |
70
+ | canRenameChildren | Allows renaming of non-root nodes in combination with specification of onRename function | `false` |
71
+
72
+ ### TreeNodeData
73
+
74
+ Required format for list of nodes, includes a holder for any type of data, which is there specified in the `TreeOfNodes` specification.
75
+
76
+ ```
77
+ export type TreeNodeData<T> = {
78
+ id: Key;
79
+ label: string;
80
+ parentId?: Key;
81
+ data: T;
82
+ };
83
+ ```
84
+
85
+ ### iNodeUpdate
86
+
87
+ Use this format to return from update functions.
88
+
89
+ ```
90
+ export interface iNodeUpdate {
91
+ success: boolean;
92
+ ErrorText?: string;
93
+ }
94
+ ```
95
+
96
+ ## Context Menu Usage
97
+
98
+ Inbuilt context menu provider, takes a list of available actions and renders a context menu on appropriate click.
99
+
100
+ ```
101
+ import { ContextMenuProvider, iMenuItem } from '@asup/tree-of-nodes';
102
+ import '@asup/tree-of-nodes/dist/style.css';
103
+
104
+ ... inside REACT component
105
+
106
+ <ContextMenuProvider>
107
+
108
+ <SomeChild
109
+
110
+ const showMenu () = useCallback(
111
+ (e: React.MouseEvent<HTMLDivElement>) => {
112
+ e.preventDefault();
113
+ e.stopPropagation();
114
+ const menuItems: iMenuItem[] = [
115
+ { label: 'Item 1', action: item1Function },
116
+ { label: 'Item 2', action: item2Function },
117
+ ...
118
+ ];
119
+ menuContext.set && menuContext.set({
120
+ visible: true,
121
+ y: e.pageY,
122
+ x: e.pageX,
123
+ menuItems: menuItems,
124
+ });
125
+ }
126
+ },
127
+ [...]);
128
+
129
+ return (
130
+ <div onContextMenu={showMenu}>
131
+ </div>
132
+ );
133
+ >
134
+
135
+ </ContextMenuProvider>
136
+ ```
137
+
138
+ Add an `onContextMenu` action to an element inside the `ContextMenuProvider`, and create a corresponding function that loads the menuItems array and then sets it to visible.
package/dist/cjs/main.js CHANGED
@@ -4,20 +4,20 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
 
6
6
  function _interopNamespaceDefault(e) {
7
- var n = Object.create(null);
8
- if (e) {
9
- Object.keys(e).forEach(function (k) {
10
- if (k !== 'default') {
11
- var d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: function () { return e[k]; }
15
- });
16
- }
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
17
15
  });
18
- }
19
- n.default = e;
20
- return Object.freeze(n);
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
21
  }
22
22
 
23
23
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
@@ -28,8 +28,7 @@ const ContextMenu = React.forwardRef(({ entries, visible, xPos, yPos }, ref) =>
28
28
  top: `${yPos}px`,
29
29
  left: `${xPos}px`,
30
30
  }, children: entries.map((e, i) => (jsxRuntime.jsx("div", { className: 'context-menu-item', onClick: () => {
31
- if (typeof e.action === 'function')
32
- e.action();
31
+ e.action && e.action();
33
32
  }, children: e.label ?? '' }, i))) }));
34
33
  });
35
34
 
@@ -1342,7 +1341,7 @@ if (process.env.NODE_ENV !== 'production') {
1342
1341
  const Spinner = /*#__PURE__*/React__namespace.forwardRef(({
1343
1342
  bsPrefix,
1344
1343
  variant,
1345
- animation,
1344
+ animation = 'border',
1346
1345
  size,
1347
1346
  // Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
1348
1347
  as: Component = 'div',
@@ -1505,79 +1504,46 @@ ExclamationCircleFill.defaultProps = {
1505
1504
  var ExclamationCircleFill$1 = ExclamationCircleFill;
1506
1505
 
1507
1506
  const TreeOfNodesContext = React.createContext({
1507
+ id: 'tree-of-nodes',
1508
1508
  nodeList: [],
1509
- });
1510
- const TreeOfNodes = ({ nodeList, roots,
1511
- // canAddRoot = false,
1512
- canRemoveRoot = false, canRenameRoot = false, canAddChildren = false, canRemoveChildren = false, canRenameChildren = false, selectedId, handleSelect = () => {
1513
- return;
1514
- }, onAdd, onRename, onRemove, }) => {
1515
- const [expandedNodes, setExpandedNodes] = React.useState([]);
1516
- // Change expansion and update context
1517
- const changeExpand = React.useCallback((eKey, force) => {
1518
- const newExpandedNodes = [...expandedNodes];
1519
- const ix = newExpandedNodes.findIndex((nKey) => nKey === eKey);
1520
- if ((ix === -1 && force === undefined) || force === true)
1521
- newExpandedNodes.push(eKey);
1522
- else
1523
- newExpandedNodes.splice(ix, 1);
1524
- setExpandedNodes(newExpandedNodes);
1525
- }, [expandedNodes]);
1526
- return (jsxRuntime.jsx(ContextMenuProvider, { children: jsxRuntime.jsx(TreeOfNodesContext.Provider, { value: {
1527
- nodeList,
1528
- selectedId,
1529
- handleSelect,
1530
- expandedNodes,
1531
- handleExpandClick: (r, f) => changeExpand(r, f),
1532
- onAddChild: onAdd,
1533
- onRename,
1534
- onRemove,
1535
- }, children: roots.map((r) => (jsxRuntime.jsx(TreeNode, { id: r, canRemove: canRemoveRoot, canAddChildren: canAddChildren, canRename: canRenameRoot, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }, r))) }) }));
1536
- };
1509
+ });
1537
1510
 
1538
1511
  const WordEntry = React.forwardRef(({ id, value, editing, saving, setValue, sendEscape, style }, ref) => {
1539
- const [currentStyle, setCurrentStyle] = React.useState({
1540
- display: 'inline-block',
1541
- });
1542
1512
  WordEntry.displayName = 'WordEntry';
1513
+ const currentStyle = React.useMemo(() => {
1514
+ return {
1515
+ display: 'inline-block',
1516
+ padding: '1px',
1517
+ paddingLeft: '3px',
1518
+ paddingRight: '3px',
1519
+ border: '1px',
1520
+ borderRadius: '3px',
1521
+ ...style,
1522
+ };
1523
+ }, [style]);
1543
1524
  const [currentValue, setCurrentValue] = React.useState(value ?? '');
1544
1525
  React.useEffect(() => {
1545
1526
  setCurrentValue(value ?? '');
1546
1527
  }, [value]);
1547
1528
  const returnData = React.useCallback(() => {
1548
- if (typeof setValue !== 'function')
1549
- return;
1550
- setValue(currentValue);
1529
+ setValue && setValue(currentValue);
1551
1530
  }, [currentValue, setValue]);
1552
1531
  const keyPress = React.useCallback((e) => {
1553
1532
  if (e.code === 'Escape') {
1554
1533
  setCurrentValue(value ?? '');
1555
- if (typeof sendEscape === 'function')
1556
- sendEscape();
1534
+ sendEscape && sendEscape();
1557
1535
  }
1558
1536
  else if (e.code === 'Enter') {
1559
1537
  returnData();
1560
1538
  }
1561
1539
  }, [returnData, sendEscape, value]);
1562
- React.useEffect(() => {
1563
- setCurrentStyle({
1564
- padding: '1px',
1565
- paddingLeft: '3px',
1566
- paddingRight: '3px',
1567
- border: '1px',
1568
- borderRadius: '3px',
1569
- display: 'inline-block',
1570
- ...style,
1571
- });
1572
- }, [style]);
1573
1540
  // Simple if not editing
1574
1541
  if (!editing) {
1575
- return jsxRuntime.jsx("span", { style: currentStyle, children: value });
1542
+ return (jsxRuntime.jsx("span", { style: currentStyle, id: String(id), children: value }));
1576
1543
  }
1577
- return (jsxRuntime.jsxs("div", { style: currentStyle, id: id, children: [saving && (jsxRuntime.jsx(Spinner$1, { id: id ? `${id}-spinner` : undefined, size: 'sm', animation: 'border' })), jsxRuntime.jsx("input", { id: id ? `${id}-input` : undefined, ref: ref, type: 'text', style: {
1544
+ return (jsxRuntime.jsxs("div", { style: currentStyle, id: `${id}-holder`, children: [saving && (jsxRuntime.jsx(Spinner$1, { id: `${id}-spinner`, size: 'sm', animation: 'border' })), jsxRuntime.jsx("input", { id: String(id), ref: ref, type: 'text', style: {
1578
1545
  lineHeight: '0.75rem',
1579
- border: editing ? '1px dotted black' : saving ? '' : '1px dotted grey',
1580
- margin: !saving && !editing ? '1px' : '',
1546
+ border: '1px dotted black',
1581
1547
  width: 'auto',
1582
1548
  minWidth: '0',
1583
1549
  }, value: currentValue, disabled: saving, onChange: (e) => setCurrentValue(e.currentTarget.value), onBlur: returnData, onKeyDownCapture: keyPress })] }));
@@ -1590,7 +1556,7 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1590
1556
  // Node information
1591
1557
  const thisNode = React.useMemo(() => treeContext.nodeList.find((n) => n.id === id), [id, treeContext.nodeList]);
1592
1558
  const childNodes = React.useMemo(() => treeContext.nodeList.filter((n) => n.parentId === id), [id, treeContext.nodeList]);
1593
- // Axios state
1559
+ // State
1594
1560
  const loading = React.useMemo(() => treeContext.nodeList.findIndex((n) => n.id === id) === -1, [id, treeContext.nodeList]);
1595
1561
  const [error, setError] = React.useState(false);
1596
1562
  const [errorText, setErrotText] = React.useState(null);
@@ -1638,14 +1604,12 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1638
1604
  }, [handleReturn, id, treeContext]);
1639
1605
  const deleteThis = React.useCallback(async () => {
1640
1606
  setUpdatingNode(true);
1641
- if (thisNode === undefined)
1642
- return;
1643
1607
  if (treeContext.onRemove) {
1644
1608
  handleReturn(await treeContext.onRemove(id));
1645
1609
  }
1646
1610
  setUpdatingNode(false);
1647
- }, [handleReturn, id, thisNode, treeContext]);
1648
- // // Context actions
1611
+ }, [handleReturn, id, treeContext]);
1612
+ // Context actions
1649
1613
  const addChild = React.useCallback(() => {
1650
1614
  if (menuContext.set) {
1651
1615
  menuContext.set({ visible: false });
@@ -1678,12 +1642,15 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1678
1642
  e.preventDefault();
1679
1643
  e.stopPropagation();
1680
1644
  const menuItems = [];
1681
- if (canAddChildren)
1645
+ if (canAddChildren) {
1682
1646
  menuItems.push({ label: 'Add', action: addChild });
1683
- if (canRename)
1647
+ }
1648
+ if (canRename) {
1684
1649
  menuItems.push({ label: 'Rename', action: renameThis });
1685
- if (canRemove && (childNodes === undefined || childNodes.length === 0))
1650
+ }
1651
+ if (canRemove && (childNodes === undefined || childNodes.length === 0)) {
1686
1652
  menuItems.push({ label: 'Delete', action: removeThis });
1653
+ }
1687
1654
  if (typeof menuContext.set === 'function') {
1688
1655
  menuContext.set({
1689
1656
  visible: true,
@@ -1703,11 +1670,12 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1703
1670
  renameThis,
1704
1671
  ]);
1705
1672
  // Return node
1706
- return (jsxRuntime.jsxs("div", { className: 'hierarchy-node', onClickCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onContextMenuCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onFocusCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), children: [error ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExclamationCircleFill$1, { color: 'var(--bs-danger)' }), ' ', errorText && errorText !== '' ? errorText : 'An unknown error has occured'] })) : loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Spinner$1, { size: 'sm', animation: 'border' }), ' ', "Loading..."] })) : (jsxRuntime.jsxs("span", { onContextMenu: showMenu, children: [childNodes !== undefined && childNodes.length > 0 ? (!expanded ? (jsxRuntime.jsx(CaretRightFill$1, { id: `hierarchynode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) })) : (jsxRuntime.jsx(CaretDownFill$1, { id: `hierarchynode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) }))) : (jsxRuntime.jsx(CaretRight$1, {})), thisNode !== undefined ? (jsxRuntime.jsx(WordEntry, { id: id ? `treenode-word-${id}` : undefined, ref: currentNameRef, value: thisNode.label, editing: renaming, saving: updatingNode, setValue: (ret) => renameNode(ret), style: {
1673
+ return (jsxRuntime.jsxs("div", { id: `${treeContext.id}-treenode-${id}`, className: 'treenode', onClickCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onContextMenuCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onFocusCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), children: [error ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExclamationCircleFill$1, { color: 'var(--bs-danger)' }), ' ', errorText && errorText !== '' ? errorText : 'An unknown error has occured'] })) : loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Spinner$1, { size: 'sm', animation: 'border' }), ' ', "Loading..."] })) : (jsxRuntime.jsxs("span", { onContextMenu: showMenu, children: [childNodes !== undefined && childNodes.length > 0 ? (!expanded ? (jsxRuntime.jsx(CaretRightFill$1, { role: 'button', "aria-expanded": false, id: `${treeContext.id}-treenode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) })) : (jsxRuntime.jsx(CaretDownFill$1, { role: 'button', "aria-expanded": true, id: `${treeContext.id}-treenode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) }))) : (jsxRuntime.jsx(CaretRight$1, { role: 'button', "aria-expanded": false, "aria-disabled": true })), thisNode && (jsxRuntime.jsx(WordEntry, { id: `${treeContext.id}-treenode-entry-${id}`, ref: currentNameRef, value: thisNode.label, editing: renaming, saving: updatingNode, setValue: (ret) => renameNode(ret), sendEscape: () => setRenaming(false), style: {
1707
1674
  border: currentBorder,
1708
1675
  margin: currentBorder === '' ? '1px' : '',
1709
1676
  backgroundColor: currentBorder === '' ? '' : 'lightgrey',
1710
- } })) : (jsxRuntime.jsx("i", { children: "No name" }))] })), childNodes !== undefined && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: childNodes.map((h) => (jsxRuntime.jsx("span", { style: {
1677
+ } }))] })), childNodes !== undefined &&
1678
+ childNodes.map((h, i) => (jsxRuntime.jsx("span", { id: `${treeContext.id}-treenode-child-${h.id}`, style: {
1711
1679
  marginLeft: '16px',
1712
1680
  display: 'block',
1713
1681
  maxHeight: expanded ? '99999px' : '0px',
@@ -1716,13 +1684,42 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1716
1684
  visibility: expanded ? 'inherit' : 'hidden',
1717
1685
  opacity: expanded ? 1 : 0,
1718
1686
  transition: 'visibility .3s, opacity .3s ease-in-out, max-height 0.3s ease-in-out',
1719
- }, children: jsxRuntime.jsx(TreeNode, { id: h.id, canRemove: canRemoveChildren, canRename: canRenameChildren, canAddChildren: canAddChildren, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }) }, h.id))) })), showNewNode && (jsxRuntime.jsx("div", { children: jsxRuntime.jsx(WordEntry, { style: { marginLeft: '14px' }, id: id ? `hierarchynode-new-${id}` : undefined, ref: newNameRef, editing: true, setValue: (ret) => {
1687
+ }, children: jsxRuntime.jsx(TreeNode, { id: h.id, canRemove: canRemoveChildren, canRename: canRenameChildren, canAddChildren: canAddChildren, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }) }, h.id))), showNewNode && (jsxRuntime.jsx("div", { children: jsxRuntime.jsx(WordEntry, { style: { marginLeft: '14px' }, id: `treenode-new-${id}`, ref: newNameRef, editing: true, setValue: (ret) => {
1720
1688
  confirmNewNode(ret);
1721
1689
  }, saving: savingNewNode, sendEscape: () => {
1722
1690
  setShowNewNode(false);
1723
1691
  } }) }))] }));
1724
1692
  };
1725
1693
 
1694
+ const TreeOfNodes = ({ id, nodeList, roots,
1695
+ // canAddRoot = false,
1696
+ canRemoveRoot = false, canRenameRoot = false, canAddChildren = false, canRemoveChildren = false, canRenameChildren = false, selectedId, handleSelect = () => {
1697
+ return;
1698
+ }, onAdd, onRename, onRemove, }) => {
1699
+ const [expandedNodes, setExpandedNodes] = React.useState([]);
1700
+ // Change expansion and update context
1701
+ const changeExpand = React.useCallback((eKey, force) => {
1702
+ const newExpandedNodes = [...expandedNodes];
1703
+ const ix = newExpandedNodes.findIndex((nKey) => nKey === eKey);
1704
+ if ((ix === -1 && force === undefined) || force === true)
1705
+ newExpandedNodes.push(eKey);
1706
+ else
1707
+ newExpandedNodes.splice(ix, 1);
1708
+ setExpandedNodes(newExpandedNodes);
1709
+ }, [expandedNodes]);
1710
+ return (jsxRuntime.jsx(ContextMenuProvider, { children: jsxRuntime.jsx(TreeOfNodesContext.Provider, { value: {
1711
+ id: id,
1712
+ nodeList,
1713
+ selectedId,
1714
+ handleSelect,
1715
+ expandedNodes,
1716
+ handleExpandClick: (r, f) => changeExpand(r, f),
1717
+ onAddChild: onAdd,
1718
+ onRename,
1719
+ onRemove,
1720
+ }, children: roots.map((r) => (jsxRuntime.jsx(TreeNode, { id: r, canRemove: canRemoveRoot, canAddChildren: canAddChildren, canRename: canRenameRoot, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }, r))) }) }));
1721
+ };
1722
+
1726
1723
  exports.ContextMenu = ContextMenu;
1727
1724
  exports.ContextMenuProvider = ContextMenuProvider;
1728
1725
  exports.TreeNode = TreeNode;
@@ -0,0 +1,4 @@
1
+ import { TreeNodeData } from 'components/TreeOfNodesContext';
2
+ export declare const mockNodes: TreeNodeData<{
3
+ value: number;
4
+ } | number | undefined>[];
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Key } from 'react';
2
+ import { iNodeUpdate, TreeNodeData } from './TreeOfNodesContext';
3
+ interface iTreeOfNodes<T> {
4
+ id: string;
5
+ nodeList: TreeNodeData<T>[];
6
+ roots: Key[];
7
+ selectedId?: Key;
8
+ handleSelect?: (ret: Key) => void;
9
+ onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
10
+ onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
11
+ onRemove?: (childId: Key) => Promise<iNodeUpdate>;
12
+ canAddRoot?: boolean;
13
+ canRemoveRoot?: boolean;
14
+ canRenameRoot?: boolean;
15
+ canAddChildren?: boolean;
16
+ canRemoveChildren?: boolean;
17
+ canRenameChildren?: boolean;
18
+ }
19
+ export declare const TreeOfNodes: <T extends unknown>({ id, nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: iTreeOfNodes<T>) => JSX.Element;
20
+ export {};
@@ -0,0 +1,28 @@
1
+ import { Key } from 'react';
2
+ export interface iNodeUpdate {
3
+ success: boolean;
4
+ ErrorText?: string;
5
+ }
6
+ export type TreeNodeBase = {
7
+ id: Key;
8
+ label: string;
9
+ parentId?: Key;
10
+ };
11
+ export type TreeNodeData<T> = {
12
+ id: Key;
13
+ label: string;
14
+ parentId?: Key;
15
+ data: T;
16
+ };
17
+ export type TreeOfNodesContextData<T> = {
18
+ id: string;
19
+ nodeList: TreeNodeData<T>[];
20
+ selectedId?: Key;
21
+ handleSelect?: (ret: Key) => void;
22
+ expandedNodes?: Key[];
23
+ handleExpandClick?: (ret: Key, force?: boolean) => void;
24
+ onAddChild?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
25
+ onRename?: (id: Key, newName: string) => Promise<iNodeUpdate>;
26
+ onRemove?: (id: Key) => Promise<iNodeUpdate>;
27
+ };
28
+ export declare const TreeOfNodesContext: import("react").Context<TreeOfNodesContextData<unknown>>;
@@ -1,6 +1,6 @@
1
- import React from 'react';
1
+ import React, { Key } from 'react';
2
2
  interface WordEntryProps {
3
- id?: string;
3
+ id: Key;
4
4
  value?: string;
5
5
  editing?: boolean;
6
6
  saving?: boolean;
@@ -1,7 +1,8 @@
1
1
  import { ContextMenu } from './ContextMenu';
2
2
  import { ContextMenuProvider } from './ContextMenuProvider';
3
3
  import { TreeNode } from './TreeNode';
4
- import { iNodeUpdate, TreeNodeBase, TreeNodeData, TreeOfNodes } from './TreeOfNodes';
4
+ import { TreeOfNodes } from './TreeOfNodes';
5
+ import { iNodeUpdate, TreeNodeBase, TreeNodeData } from './TreeOfNodesContext';
5
6
  import { WordEntry } from './WordEntry';
6
7
  export { ContextMenu, ContextMenuProvider, TreeNode, TreeOfNodes, WordEntry };
7
8
  export type { TreeNodeBase, TreeNodeData, iNodeUpdate };
File without changes
package/dist/main.js CHANGED
@@ -8,8 +8,7 @@ const ContextMenu = React__default.forwardRef(({ entries, visible, xPos, yPos },
8
8
  top: `${yPos}px`,
9
9
  left: `${xPos}px`,
10
10
  }, children: entries.map((e, i) => (jsx("div", { className: 'context-menu-item', onClick: () => {
11
- if (typeof e.action === 'function')
12
- e.action();
11
+ e.action && e.action();
13
12
  }, children: e.label ?? '' }, i))) }));
14
13
  });
15
14
 
@@ -1322,7 +1321,7 @@ if (process.env.NODE_ENV !== 'production') {
1322
1321
  const Spinner = /*#__PURE__*/React.forwardRef(({
1323
1322
  bsPrefix,
1324
1323
  variant,
1325
- animation,
1324
+ animation = 'border',
1326
1325
  size,
1327
1326
  // Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
1328
1327
  as: Component = 'div',
@@ -1485,79 +1484,46 @@ ExclamationCircleFill.defaultProps = {
1485
1484
  var ExclamationCircleFill$1 = ExclamationCircleFill;
1486
1485
 
1487
1486
  const TreeOfNodesContext = createContext({
1487
+ id: 'tree-of-nodes',
1488
1488
  nodeList: [],
1489
- });
1490
- const TreeOfNodes = ({ nodeList, roots,
1491
- // canAddRoot = false,
1492
- canRemoveRoot = false, canRenameRoot = false, canAddChildren = false, canRemoveChildren = false, canRenameChildren = false, selectedId, handleSelect = () => {
1493
- return;
1494
- }, onAdd, onRename, onRemove, }) => {
1495
- const [expandedNodes, setExpandedNodes] = useState([]);
1496
- // Change expansion and update context
1497
- const changeExpand = useCallback((eKey, force) => {
1498
- const newExpandedNodes = [...expandedNodes];
1499
- const ix = newExpandedNodes.findIndex((nKey) => nKey === eKey);
1500
- if ((ix === -1 && force === undefined) || force === true)
1501
- newExpandedNodes.push(eKey);
1502
- else
1503
- newExpandedNodes.splice(ix, 1);
1504
- setExpandedNodes(newExpandedNodes);
1505
- }, [expandedNodes]);
1506
- return (jsx(ContextMenuProvider, { children: jsx(TreeOfNodesContext.Provider, { value: {
1507
- nodeList,
1508
- selectedId,
1509
- handleSelect,
1510
- expandedNodes,
1511
- handleExpandClick: (r, f) => changeExpand(r, f),
1512
- onAddChild: onAdd,
1513
- onRename,
1514
- onRemove,
1515
- }, children: roots.map((r) => (jsx(TreeNode, { id: r, canRemove: canRemoveRoot, canAddChildren: canAddChildren, canRename: canRenameRoot, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }, r))) }) }));
1516
- };
1489
+ });
1517
1490
 
1518
1491
  const WordEntry = React__default.forwardRef(({ id, value, editing, saving, setValue, sendEscape, style }, ref) => {
1519
- const [currentStyle, setCurrentStyle] = useState({
1520
- display: 'inline-block',
1521
- });
1522
1492
  WordEntry.displayName = 'WordEntry';
1493
+ const currentStyle = useMemo(() => {
1494
+ return {
1495
+ display: 'inline-block',
1496
+ padding: '1px',
1497
+ paddingLeft: '3px',
1498
+ paddingRight: '3px',
1499
+ border: '1px',
1500
+ borderRadius: '3px',
1501
+ ...style,
1502
+ };
1503
+ }, [style]);
1523
1504
  const [currentValue, setCurrentValue] = useState(value ?? '');
1524
1505
  useEffect(() => {
1525
1506
  setCurrentValue(value ?? '');
1526
1507
  }, [value]);
1527
1508
  const returnData = useCallback(() => {
1528
- if (typeof setValue !== 'function')
1529
- return;
1530
- setValue(currentValue);
1509
+ setValue && setValue(currentValue);
1531
1510
  }, [currentValue, setValue]);
1532
1511
  const keyPress = useCallback((e) => {
1533
1512
  if (e.code === 'Escape') {
1534
1513
  setCurrentValue(value ?? '');
1535
- if (typeof sendEscape === 'function')
1536
- sendEscape();
1514
+ sendEscape && sendEscape();
1537
1515
  }
1538
1516
  else if (e.code === 'Enter') {
1539
1517
  returnData();
1540
1518
  }
1541
1519
  }, [returnData, sendEscape, value]);
1542
- useEffect(() => {
1543
- setCurrentStyle({
1544
- padding: '1px',
1545
- paddingLeft: '3px',
1546
- paddingRight: '3px',
1547
- border: '1px',
1548
- borderRadius: '3px',
1549
- display: 'inline-block',
1550
- ...style,
1551
- });
1552
- }, [style]);
1553
1520
  // Simple if not editing
1554
1521
  if (!editing) {
1555
- return jsx("span", { style: currentStyle, children: value });
1522
+ return (jsx("span", { style: currentStyle, id: String(id), children: value }));
1556
1523
  }
1557
- return (jsxs("div", { style: currentStyle, id: id, children: [saving && (jsx(Spinner$1, { id: id ? `${id}-spinner` : undefined, size: 'sm', animation: 'border' })), jsx("input", { id: id ? `${id}-input` : undefined, ref: ref, type: 'text', style: {
1524
+ return (jsxs("div", { style: currentStyle, id: `${id}-holder`, children: [saving && (jsx(Spinner$1, { id: `${id}-spinner`, size: 'sm', animation: 'border' })), jsx("input", { id: String(id), ref: ref, type: 'text', style: {
1558
1525
  lineHeight: '0.75rem',
1559
- border: editing ? '1px dotted black' : saving ? '' : '1px dotted grey',
1560
- margin: !saving && !editing ? '1px' : '',
1526
+ border: '1px dotted black',
1561
1527
  width: 'auto',
1562
1528
  minWidth: '0',
1563
1529
  }, value: currentValue, disabled: saving, onChange: (e) => setCurrentValue(e.currentTarget.value), onBlur: returnData, onKeyDownCapture: keyPress })] }));
@@ -1570,7 +1536,7 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1570
1536
  // Node information
1571
1537
  const thisNode = useMemo(() => treeContext.nodeList.find((n) => n.id === id), [id, treeContext.nodeList]);
1572
1538
  const childNodes = useMemo(() => treeContext.nodeList.filter((n) => n.parentId === id), [id, treeContext.nodeList]);
1573
- // Axios state
1539
+ // State
1574
1540
  const loading = useMemo(() => treeContext.nodeList.findIndex((n) => n.id === id) === -1, [id, treeContext.nodeList]);
1575
1541
  const [error, setError] = useState(false);
1576
1542
  const [errorText, setErrotText] = useState(null);
@@ -1618,14 +1584,12 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1618
1584
  }, [handleReturn, id, treeContext]);
1619
1585
  const deleteThis = useCallback(async () => {
1620
1586
  setUpdatingNode(true);
1621
- if (thisNode === undefined)
1622
- return;
1623
1587
  if (treeContext.onRemove) {
1624
1588
  handleReturn(await treeContext.onRemove(id));
1625
1589
  }
1626
1590
  setUpdatingNode(false);
1627
- }, [handleReturn, id, thisNode, treeContext]);
1628
- // // Context actions
1591
+ }, [handleReturn, id, treeContext]);
1592
+ // Context actions
1629
1593
  const addChild = useCallback(() => {
1630
1594
  if (menuContext.set) {
1631
1595
  menuContext.set({ visible: false });
@@ -1658,12 +1622,15 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1658
1622
  e.preventDefault();
1659
1623
  e.stopPropagation();
1660
1624
  const menuItems = [];
1661
- if (canAddChildren)
1625
+ if (canAddChildren) {
1662
1626
  menuItems.push({ label: 'Add', action: addChild });
1663
- if (canRename)
1627
+ }
1628
+ if (canRename) {
1664
1629
  menuItems.push({ label: 'Rename', action: renameThis });
1665
- if (canRemove && (childNodes === undefined || childNodes.length === 0))
1630
+ }
1631
+ if (canRemove && (childNodes === undefined || childNodes.length === 0)) {
1666
1632
  menuItems.push({ label: 'Delete', action: removeThis });
1633
+ }
1667
1634
  if (typeof menuContext.set === 'function') {
1668
1635
  menuContext.set({
1669
1636
  visible: true,
@@ -1683,11 +1650,12 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1683
1650
  renameThis,
1684
1651
  ]);
1685
1652
  // Return node
1686
- return (jsxs("div", { className: 'hierarchy-node', onClickCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onContextMenuCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onFocusCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), children: [error ? (jsxs(Fragment, { children: [jsx(ExclamationCircleFill$1, { color: 'var(--bs-danger)' }), ' ', errorText && errorText !== '' ? errorText : 'An unknown error has occured'] })) : loading ? (jsxs(Fragment, { children: [jsx(Spinner$1, { size: 'sm', animation: 'border' }), ' ', "Loading..."] })) : (jsxs("span", { onContextMenu: showMenu, children: [childNodes !== undefined && childNodes.length > 0 ? (!expanded ? (jsx(CaretRightFill$1, { id: `hierarchynode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) })) : (jsx(CaretDownFill$1, { id: `hierarchynode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) }))) : (jsx(CaretRight$1, {})), thisNode !== undefined ? (jsx(WordEntry, { id: id ? `treenode-word-${id}` : undefined, ref: currentNameRef, value: thisNode.label, editing: renaming, saving: updatingNode, setValue: (ret) => renameNode(ret), style: {
1653
+ return (jsxs("div", { id: `${treeContext.id}-treenode-${id}`, className: 'treenode', onClickCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onContextMenuCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), onFocusCapture: () => treeContext.handleSelect && treeContext.handleSelect(id), children: [error ? (jsxs(Fragment, { children: [jsx(ExclamationCircleFill$1, { color: 'var(--bs-danger)' }), ' ', errorText && errorText !== '' ? errorText : 'An unknown error has occured'] })) : loading ? (jsxs(Fragment, { children: [jsx(Spinner$1, { size: 'sm', animation: 'border' }), ' ', "Loading..."] })) : (jsxs("span", { onContextMenu: showMenu, children: [childNodes !== undefined && childNodes.length > 0 ? (!expanded ? (jsx(CaretRightFill$1, { role: 'button', "aria-expanded": false, id: `${treeContext.id}-treenode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) })) : (jsx(CaretDownFill$1, { role: 'button', "aria-expanded": true, id: `${treeContext.id}-treenode-caret-${id}`, onClick: () => treeContext.handleExpandClick && treeContext.handleExpandClick(id) }))) : (jsx(CaretRight$1, { role: 'button', "aria-expanded": false, "aria-disabled": true })), thisNode && (jsx(WordEntry, { id: `${treeContext.id}-treenode-entry-${id}`, ref: currentNameRef, value: thisNode.label, editing: renaming, saving: updatingNode, setValue: (ret) => renameNode(ret), sendEscape: () => setRenaming(false), style: {
1687
1654
  border: currentBorder,
1688
1655
  margin: currentBorder === '' ? '1px' : '',
1689
1656
  backgroundColor: currentBorder === '' ? '' : 'lightgrey',
1690
- } })) : (jsx("i", { children: "No name" }))] })), childNodes !== undefined && (jsx(Fragment, { children: childNodes.map((h) => (jsx("span", { style: {
1657
+ } }))] })), childNodes !== undefined &&
1658
+ childNodes.map((h, i) => (jsx("span", { id: `${treeContext.id}-treenode-child-${h.id}`, style: {
1691
1659
  marginLeft: '16px',
1692
1660
  display: 'block',
1693
1661
  maxHeight: expanded ? '99999px' : '0px',
@@ -1696,11 +1664,40 @@ const TreeNode = ({ id, canRemove, canRename, canAddChildren, canRemoveChildren,
1696
1664
  visibility: expanded ? 'inherit' : 'hidden',
1697
1665
  opacity: expanded ? 1 : 0,
1698
1666
  transition: 'visibility .3s, opacity .3s ease-in-out, max-height 0.3s ease-in-out',
1699
- }, children: jsx(TreeNode, { id: h.id, canRemove: canRemoveChildren, canRename: canRenameChildren, canAddChildren: canAddChildren, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }) }, h.id))) })), showNewNode && (jsx("div", { children: jsx(WordEntry, { style: { marginLeft: '14px' }, id: id ? `hierarchynode-new-${id}` : undefined, ref: newNameRef, editing: true, setValue: (ret) => {
1667
+ }, children: jsx(TreeNode, { id: h.id, canRemove: canRemoveChildren, canRename: canRenameChildren, canAddChildren: canAddChildren, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }) }, h.id))), showNewNode && (jsx("div", { children: jsx(WordEntry, { style: { marginLeft: '14px' }, id: `treenode-new-${id}`, ref: newNameRef, editing: true, setValue: (ret) => {
1700
1668
  confirmNewNode(ret);
1701
1669
  }, saving: savingNewNode, sendEscape: () => {
1702
1670
  setShowNewNode(false);
1703
1671
  } }) }))] }));
1704
1672
  };
1705
1673
 
1674
+ const TreeOfNodes = ({ id, nodeList, roots,
1675
+ // canAddRoot = false,
1676
+ canRemoveRoot = false, canRenameRoot = false, canAddChildren = false, canRemoveChildren = false, canRenameChildren = false, selectedId, handleSelect = () => {
1677
+ return;
1678
+ }, onAdd, onRename, onRemove, }) => {
1679
+ const [expandedNodes, setExpandedNodes] = useState([]);
1680
+ // Change expansion and update context
1681
+ const changeExpand = useCallback((eKey, force) => {
1682
+ const newExpandedNodes = [...expandedNodes];
1683
+ const ix = newExpandedNodes.findIndex((nKey) => nKey === eKey);
1684
+ if ((ix === -1 && force === undefined) || force === true)
1685
+ newExpandedNodes.push(eKey);
1686
+ else
1687
+ newExpandedNodes.splice(ix, 1);
1688
+ setExpandedNodes(newExpandedNodes);
1689
+ }, [expandedNodes]);
1690
+ return (jsx(ContextMenuProvider, { children: jsx(TreeOfNodesContext.Provider, { value: {
1691
+ id: id,
1692
+ nodeList,
1693
+ selectedId,
1694
+ handleSelect,
1695
+ expandedNodes,
1696
+ handleExpandClick: (r, f) => changeExpand(r, f),
1697
+ onAddChild: onAdd,
1698
+ onRename,
1699
+ onRemove,
1700
+ }, children: roots.map((r) => (jsx(TreeNode, { id: r, canRemove: canRemoveRoot, canAddChildren: canAddChildren, canRename: canRenameRoot, canRemoveChildren: canRemoveChildren, canRenameChildren: canRenameChildren }, r))) }) }));
1701
+ };
1702
+
1706
1703
  export { ContextMenu, ContextMenuProvider, TreeNode, TreeOfNodes, WordEntry };
@@ -0,0 +1,4 @@
1
+ import { TreeNodeData } from 'components/TreeOfNodesContext';
2
+ export declare const mockNodes: TreeNodeData<{
3
+ value: number;
4
+ } | number | undefined>[];
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Key } from 'react';
2
+ import { iNodeUpdate, TreeNodeData } from './TreeOfNodesContext';
3
+ interface iTreeOfNodes<T> {
4
+ id: string;
5
+ nodeList: TreeNodeData<T>[];
6
+ roots: Key[];
7
+ selectedId?: Key;
8
+ handleSelect?: (ret: Key) => void;
9
+ onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
10
+ onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
11
+ onRemove?: (childId: Key) => Promise<iNodeUpdate>;
12
+ canAddRoot?: boolean;
13
+ canRemoveRoot?: boolean;
14
+ canRenameRoot?: boolean;
15
+ canAddChildren?: boolean;
16
+ canRemoveChildren?: boolean;
17
+ canRenameChildren?: boolean;
18
+ }
19
+ export declare const TreeOfNodes: <T extends unknown>({ id, nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: iTreeOfNodes<T>) => JSX.Element;
20
+ export {};
@@ -0,0 +1,28 @@
1
+ import { Key } from 'react';
2
+ export interface iNodeUpdate {
3
+ success: boolean;
4
+ ErrorText?: string;
5
+ }
6
+ export type TreeNodeBase = {
7
+ id: Key;
8
+ label: string;
9
+ parentId?: Key;
10
+ };
11
+ export type TreeNodeData<T> = {
12
+ id: Key;
13
+ label: string;
14
+ parentId?: Key;
15
+ data: T;
16
+ };
17
+ export type TreeOfNodesContextData<T> = {
18
+ id: string;
19
+ nodeList: TreeNodeData<T>[];
20
+ selectedId?: Key;
21
+ handleSelect?: (ret: Key) => void;
22
+ expandedNodes?: Key[];
23
+ handleExpandClick?: (ret: Key, force?: boolean) => void;
24
+ onAddChild?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
25
+ onRename?: (id: Key, newName: string) => Promise<iNodeUpdate>;
26
+ onRemove?: (id: Key) => Promise<iNodeUpdate>;
27
+ };
28
+ export declare const TreeOfNodesContext: import("react").Context<TreeOfNodesContextData<unknown>>;
@@ -1,6 +1,6 @@
1
- import React from 'react';
1
+ import React, { Key } from 'react';
2
2
  interface WordEntryProps {
3
- id?: string;
3
+ id: Key;
4
4
  value?: string;
5
5
  editing?: boolean;
6
6
  saving?: boolean;
@@ -1,7 +1,8 @@
1
1
  import { ContextMenu } from './ContextMenu';
2
2
  import { ContextMenuProvider } from './ContextMenuProvider';
3
3
  import { TreeNode } from './TreeNode';
4
- import { iNodeUpdate, TreeNodeBase, TreeNodeData, TreeOfNodes } from './TreeOfNodes';
4
+ import { TreeOfNodes } from './TreeOfNodes';
5
+ import { iNodeUpdate, TreeNodeBase, TreeNodeData } from './TreeOfNodesContext';
5
6
  import { WordEntry } from './WordEntry';
6
7
  export { ContextMenu, ContextMenuProvider, TreeNode, TreeOfNodes, WordEntry };
7
8
  export type { TreeNodeBase, TreeNodeData, iNodeUpdate };
File without changes
@@ -1,75 +1,32 @@
1
- import React, { Key } from 'react';
1
+ import * as React from 'react';
2
2
 
3
- interface contextMenuProps {
4
- entries: iMenuItem[];
5
- visible: boolean;
6
- xPos: number;
7
- yPos: number;
8
- }
9
- interface iMenuItem {
10
- label?: string;
11
- action?: () => void;
12
- }
13
- interface iContextMenuProvider {
14
- children: React.ReactNode;
15
- }
16
- declare const ContextMenuProvider: ({ children }: iContextMenuProvider) => JSX.Element;
3
+ declare const ContextMenu: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
4
+ declare function ContextMenuProvider({ children }: {
5
+ children: any;
6
+ }): any;
7
+ declare function TreeNode({ id, canRemove, canRename, canAddChildren, canRemoveChildren, canRenameChildren, }: {
8
+ id: any;
9
+ canRemove: any;
10
+ canRename: any;
11
+ canAddChildren: any;
12
+ canRemoveChildren: any;
13
+ canRenameChildren: any;
14
+ }): any;
15
+ declare function TreeOfNodes({ id, nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: {
16
+ id: any;
17
+ nodeList: any;
18
+ roots: any;
19
+ canRemoveRoot?: boolean | undefined;
20
+ canRenameRoot?: boolean | undefined;
21
+ canAddChildren?: boolean | undefined;
22
+ canRemoveChildren?: boolean | undefined;
23
+ canRenameChildren?: boolean | undefined;
24
+ selectedId: any;
25
+ handleSelect?: (() => void) | undefined;
26
+ onAdd: any;
27
+ onRename: any;
28
+ onRemove: any;
29
+ }): any;
30
+ declare const WordEntry: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
17
31
 
18
- declare const ContextMenu: React.ForwardRefExoticComponent<contextMenuProps & React.RefAttributes<HTMLDivElement>>;
19
-
20
- interface TreeNodeProps {
21
- id: Key;
22
- indentLevel?: number;
23
- canRemove?: boolean;
24
- canRename?: boolean;
25
- canAddChildren?: boolean;
26
- canRemoveChildren?: boolean;
27
- canRenameChildren?: boolean;
28
- refresh?: () => void;
29
- }
30
- declare const TreeNode: ({ id, canRemove, canRename, canAddChildren, canRemoveChildren, canRenameChildren, }: TreeNodeProps) => JSX.Element;
31
-
32
- declare type TreeNodeBase = {
33
- id: Key;
34
- label: string;
35
- parentId?: Key;
36
- };
37
- declare type TreeNodeData<T> = {
38
- id: Key;
39
- label: string;
40
- parentId?: Key;
41
- data: T;
42
- };
43
- interface iNodeUpdate {
44
- success: boolean;
45
- ErrorText?: string;
46
- }
47
- interface iTreeOfNodes<T> {
48
- nodeList: TreeNodeData<T>[];
49
- roots: Key[];
50
- selectedId?: Key;
51
- handleSelect?: (ret: Key) => void;
52
- onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
53
- onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
54
- onRemove?: (childId: Key) => Promise<iNodeUpdate>;
55
- canAddRoot?: boolean;
56
- canRemoveRoot?: boolean;
57
- canRenameRoot?: boolean;
58
- canAddChildren?: boolean;
59
- canRemoveChildren?: boolean;
60
- canRenameChildren?: boolean;
61
- }
62
- declare const TreeOfNodes: <T extends unknown>({ nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: iTreeOfNodes<T>) => JSX.Element;
63
-
64
- interface WordEntryProps {
65
- id?: string;
66
- value?: string;
67
- editing?: boolean;
68
- saving?: boolean;
69
- setValue?: (ret: string) => void;
70
- sendEscape?: () => void;
71
- style?: React.CSSProperties;
72
- }
73
- declare const WordEntry: React.ForwardRefExoticComponent<WordEntryProps & React.RefAttributes<HTMLInputElement>>;
74
-
75
- export { ContextMenu, ContextMenuProvider, TreeNode, TreeNodeBase, TreeNodeData, TreeOfNodes, WordEntry, iNodeUpdate };
32
+ export { ContextMenu, ContextMenuProvider, TreeNode, TreeOfNodes, WordEntry };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asup/tree-of-nodes",
3
- "version": "0.9.1",
3
+ "version": "1.0.0",
4
4
  "description": "REACT Typescript Treeview component",
5
5
  "author": "Paul Thomas <@PaulDThomas>",
6
6
  "private": false,
@@ -51,6 +51,7 @@
51
51
  "eslint-plugin-react-hooks": "^4.6.0",
52
52
  "husky": "^8.0.1",
53
53
  "jest": "^29.2.2",
54
+ "jest-environment-jsdom": "^29.3.0",
54
55
  "lint-staged": "^13.0.3",
55
56
  "parcel": "^2.7.0",
56
57
  "postcss": "^8.4.18",
@@ -61,9 +62,11 @@
61
62
  "rollup-plugin-peer-deps-external": "^2.2.4",
62
63
  "rollup-plugin-postcss": "^4.0.2",
63
64
  "rollup-plugin-terser": "^7.0.2",
65
+ "rollup-plugin-typescript2": "^0.34.1",
64
66
  "ts-jest": "^29.0.3",
65
67
  "ts-node": "^10.9.1",
66
- "typescript": "^4.8.4"
68
+ "tslib": "^2.4.1",
69
+ "typescript": "^4.9.3"
67
70
  },
68
71
  "lint-staged": {
69
72
  "**/*": "prettier --write --ignore-unknown"
@@ -95,6 +98,6 @@
95
98
  "scripts": {
96
99
  "build": "rollup -c --configPlugin typescript",
97
100
  "start": "parcel demo/index.html --dist-dir demo/dist",
98
- "test": "jest"
101
+ "test": "jest --watch"
99
102
  }
100
103
  }
@@ -1,44 +0,0 @@
1
- import { Key } from 'react';
2
- export declare type TreeNodeBase = {
3
- id: Key;
4
- label: string;
5
- parentId?: Key;
6
- };
7
- export declare type TreeNodeData<T> = {
8
- id: Key;
9
- label: string;
10
- parentId?: Key;
11
- data: T;
12
- };
13
- export interface iNodeUpdate {
14
- success: boolean;
15
- ErrorText?: string;
16
- }
17
- interface iTreeOfNodes<T> {
18
- nodeList: TreeNodeData<T>[];
19
- roots: Key[];
20
- selectedId?: Key;
21
- handleSelect?: (ret: Key) => void;
22
- onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
23
- onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
24
- onRemove?: (childId: Key) => Promise<iNodeUpdate>;
25
- canAddRoot?: boolean;
26
- canRemoveRoot?: boolean;
27
- canRenameRoot?: boolean;
28
- canAddChildren?: boolean;
29
- canRemoveChildren?: boolean;
30
- canRenameChildren?: boolean;
31
- }
32
- declare type TreeOfNodesContextData<T> = {
33
- nodeList: TreeNodeData<T>[];
34
- selectedId?: Key;
35
- handleSelect?: (ret: Key) => void;
36
- expandedNodes?: Key[];
37
- handleExpandClick?: (ret: Key, force?: boolean) => void;
38
- onAddChild?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
39
- onRename?: (id: Key, newName: string) => Promise<iNodeUpdate>;
40
- onRemove?: (id: Key) => Promise<iNodeUpdate>;
41
- };
42
- export declare const TreeOfNodesContext: import("react").Context<TreeOfNodesContextData<unknown>>;
43
- export declare const TreeOfNodes: <T extends unknown>({ nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: iTreeOfNodes<T>) => JSX.Element;
44
- export {};
@@ -1,44 +0,0 @@
1
- import { Key } from 'react';
2
- export declare type TreeNodeBase = {
3
- id: Key;
4
- label: string;
5
- parentId?: Key;
6
- };
7
- export declare type TreeNodeData<T> = {
8
- id: Key;
9
- label: string;
10
- parentId?: Key;
11
- data: T;
12
- };
13
- export interface iNodeUpdate {
14
- success: boolean;
15
- ErrorText?: string;
16
- }
17
- interface iTreeOfNodes<T> {
18
- nodeList: TreeNodeData<T>[];
19
- roots: Key[];
20
- selectedId?: Key;
21
- handleSelect?: (ret: Key) => void;
22
- onAdd?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
23
- onRename?: (childId: Key, newName: string) => Promise<iNodeUpdate>;
24
- onRemove?: (childId: Key) => Promise<iNodeUpdate>;
25
- canAddRoot?: boolean;
26
- canRemoveRoot?: boolean;
27
- canRenameRoot?: boolean;
28
- canAddChildren?: boolean;
29
- canRemoveChildren?: boolean;
30
- canRenameChildren?: boolean;
31
- }
32
- declare type TreeOfNodesContextData<T> = {
33
- nodeList: TreeNodeData<T>[];
34
- selectedId?: Key;
35
- handleSelect?: (ret: Key) => void;
36
- expandedNodes?: Key[];
37
- handleExpandClick?: (ret: Key, force?: boolean) => void;
38
- onAddChild?: (parentId: Key, newName: string) => Promise<iNodeUpdate>;
39
- onRename?: (id: Key, newName: string) => Promise<iNodeUpdate>;
40
- onRemove?: (id: Key) => Promise<iNodeUpdate>;
41
- };
42
- export declare const TreeOfNodesContext: import("react").Context<TreeOfNodesContextData<unknown>>;
43
- export declare const TreeOfNodes: <T extends unknown>({ nodeList, roots, canRemoveRoot, canRenameRoot, canAddChildren, canRemoveChildren, canRenameChildren, selectedId, handleSelect, onAdd, onRename, onRemove, }: iTreeOfNodes<T>) => JSX.Element;
44
- export {};