aio-tree 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 (4) hide show
  1. package/index.css +55 -0
  2. package/index.d.ts +92 -0
  3. package/index.js +334 -0
  4. package/package.json +21 -0
package/index.css ADDED
@@ -0,0 +1,55 @@
1
+ .aio-input-tree-option {
2
+ gap: 6px;
3
+ position: relative;
4
+ padding: 0 6px;
5
+ height: 3em;
6
+
7
+ }
8
+
9
+ .aio-input-tree-option.auto-height {
10
+ height: fit-content;
11
+ min-height: 3em;
12
+
13
+ }
14
+
15
+ .aio-input-tree-option .aio-input-before {
16
+ height: -webkit-fill-available;
17
+
18
+ }
19
+
20
+ .aio-input-tree {
21
+ font-size: inherit;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+ .aio-input-tree-row{
26
+ display:flex;
27
+ align-items: center;
28
+ }
29
+ .aio-input-tree-text{
30
+ flex:1;
31
+ }
32
+ .aio-input-tree-rtl {
33
+ direction: rtl;
34
+ }
35
+
36
+ .aio-input-tree-rtl .aio-input-toggle-icon {
37
+ transform: scaleX(-1);
38
+ }
39
+
40
+ .aio-input-select.aio-input-tree-options-button {
41
+ background: none;
42
+ border: none;
43
+ }
44
+
45
+ .aio-input-tree-body {
46
+ position: relative;
47
+ display: flex;
48
+ flex-direction: column;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .aio-input-tree-body .aio-input-tree-option {
53
+ transition-timing-function: cubic-bezier(.16, 2.02, .84, .96) !important;
54
+ transition: height var(--aio-input-transition);
55
+ }
package/index.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ import { FC, ReactNode } from "react";
2
+ import './index.css';
3
+ export type I_treeRef = React.RefObject<ReturnType<typeof useTree>>;
4
+ export type AI_Tree = {
5
+ value: any[];
6
+ indent?: number;
7
+ onToggle?: (openDic: {
8
+ [id: string]: boolean;
9
+ }) => void;
10
+ setChilds?: (treeOptionDetail: I_treeOptionDetail, childs: undefined | any[]) => void;
11
+ onChange?: (v: any) => void;
12
+ treeOption?: I_treeOptionProp;
13
+ attrs?: any;
14
+ className?: string;
15
+ rtl?: boolean;
16
+ style?: any;
17
+ treeRef?: I_treeRef;
18
+ };
19
+ export type I_treeOptionProp = {
20
+ value: (detail: I_treeOptionDetail) => any;
21
+ text: (detail: I_treeOptionDetail) => ReactNode;
22
+ show?: (detail: I_treeOptionDetail) => boolean;
23
+ childs: (detail: I_treeOptionDetail) => any[] | undefined;
24
+ toggleIcon?: (detail: I_treeOptionDetail) => false | {
25
+ close?: ReactNode;
26
+ open?: ReactNode;
27
+ leaf?: ReactNode;
28
+ };
29
+ style?: (detail: I_treeOptionDetail) => any;
30
+ className?: (detail: I_treeOptionDetail) => string;
31
+ attrs?: (detail: I_treeOptionDetail) => any;
32
+ onClick?: (detail: I_treeOptionDetail) => void;
33
+ };
34
+ export type I_treeOptionDetail = {
35
+ text: ReactNode;
36
+ row: any;
37
+ value: any;
38
+ childs?: any[];
39
+ index: number;
40
+ isFirstChild: boolean;
41
+ isLastChild: boolean;
42
+ toggleIcon?: false | {
43
+ close?: ReactNode;
44
+ open?: ReactNode;
45
+ leaf?: ReactNode;
46
+ };
47
+ toggle: () => void;
48
+ change: (newRow: any) => void;
49
+ level: number;
50
+ parentOptions: I_treeOptionDetail[];
51
+ open?: boolean;
52
+ isRoot: boolean;
53
+ isLeaf: boolean;
54
+ values: any[];
55
+ attrs: any;
56
+ };
57
+ export declare const AITree: FC<AI_Tree>;
58
+ export declare const useTree: (hookProps: {
59
+ treeOptionProp?: I_treeOptionProp;
60
+ onChange?: (v: any) => void;
61
+ mainValueRef: React.RefObject<any[]>;
62
+ }) => {
63
+ toggle: (treeOptionDetail: I_treeOptionDetail, state?: boolean) => void;
64
+ openDic: any;
65
+ isOpen: (treeOptionDetail?: I_treeOptionDetail) => boolean;
66
+ change: (row: any, newRow: any) => void;
67
+ getChilds: (treeOptionDetail: I_treeOptionDetail) => undefined | any[];
68
+ setChilds: (treeOptionDetail: I_treeOptionDetail, childs: undefined | any[], setChilds?: AI_Tree["setChilds"]) => void;
69
+ getOptions: (p: {
70
+ rows: any[];
71
+ level: number;
72
+ parentOptions: I_treeOptionDetail[];
73
+ }) => I_treeOptionDetail[];
74
+ getTreeOptionDetailByValues: (values: any[], rows: any[]) => I_treeOptionDetail;
75
+ };
76
+ type AI_Indent = {
77
+ level: number;
78
+ width: number;
79
+ rtl: boolean;
80
+ isLastChild: boolean;
81
+ isParentLastChild: boolean;
82
+ isLeaf: boolean;
83
+ open?: boolean;
84
+ onToggle?: () => void;
85
+ toggleIcon?: false | {
86
+ close?: ReactNode;
87
+ open?: ReactNode;
88
+ leaf?: ReactNode;
89
+ };
90
+ };
91
+ export declare const Indent: FC<AI_Indent>;
92
+ export {};
package/index.js ADDED
@@ -0,0 +1,334 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
3
+ import './index.css';
4
+ export const AITree = (props) => {
5
+ const mainValueRef = useRef(props.value);
6
+ mainValueRef.current = props.value;
7
+ const tree = useTree({ treeOptionProp: props.treeOption, onChange: props.onChange, mainValueRef });
8
+ if (props.treeRef) {
9
+ props.treeRef.current = tree;
10
+ }
11
+ let Attrs = AddToAttrs(props.attrs, { className: ['aio-input-tree', props.className, props.rtl ? 'aio-input-tree-rtl' : undefined], style: props.style });
12
+ useEffect(() => {
13
+ if (props.onToggle) {
14
+ props.onToggle(tree.openDic);
15
+ }
16
+ }, [tree.openDic]);
17
+ return (_jsx(TreeProvider, { value: { rootProps: props, tree }, children: _jsx("div", Object.assign({}, Attrs, { children: _jsx(TreeBody, { rows: props.value, level: 0, parentOptions: [] }) })) }));
18
+ };
19
+ const TreeContext = createContext({});
20
+ const TreeProvider = ({ value, children }) => {
21
+ return _jsx(TreeContext.Provider, { value: value, children: children });
22
+ };
23
+ const useTreeProvider = () => useContext(TreeContext);
24
+ const TreeBody = (props) => {
25
+ let { tree } = useTreeProvider();
26
+ let { rows, level, parentOptions } = props;
27
+ let list = tree.getOptions({ rows, level, parentOptions });
28
+ function getClassName() {
29
+ let className = 'aio-input-tree-body';
30
+ if (level === 0) {
31
+ className += ' aio-input-tree-root';
32
+ }
33
+ const directParent = parentOptions.length ? parentOptions[parentOptions.length - 1] : undefined;
34
+ if (tree.isOpen(directParent)) {
35
+ className += ' open';
36
+ }
37
+ className += ` aio-input-tree-body-level-${level}`;
38
+ return className;
39
+ }
40
+ return (_jsx("div", { className: getClassName(), children: list.map((treeOptionDetail) => {
41
+ return _jsx(TreeNode, { treeOptionDetail: treeOptionDetail }, treeOptionDetail.value);
42
+ }) }));
43
+ };
44
+ const TreeNode = ({ treeOptionDetail }) => {
45
+ let { tree } = useTreeProvider();
46
+ const { level, parentOptions, open } = treeOptionDetail;
47
+ const childs = tree.getChilds(treeOptionDetail);
48
+ return (_jsxs(_Fragment, { children: [_jsx(TreeRow, { treeOptionDetail: treeOptionDetail }), Array.isArray(childs) && open !== false &&
49
+ _jsx(TreeBody, { parentOptions: [...parentOptions, treeOptionDetail], rows: childs, level: level + 1 })] }));
50
+ };
51
+ const TreeRow = ({ treeOptionDetail }) => {
52
+ let { tree, rootProps } = useTreeProvider();
53
+ const { parentOptions, level, isLastChild, text, open } = treeOptionDetail;
54
+ let childs = tree.getChilds(treeOptionDetail);
55
+ const directParent = parentOptions.length ? parentOptions[parentOptions.length - 1] : undefined;
56
+ const indent = treeOptionDetail.toggleIcon !== false && (_jsx(Indent, { width: rootProps.indent || 24, level: level, isLastChild: isLastChild, isLeaf: !childs, isParentLastChild: !!(directParent === null || directParent === void 0 ? void 0 : directParent.isLastChild), rtl: !!rootProps.rtl, toggleIcon: treeOptionDetail.toggleIcon, open: open, onToggle: () => tree.toggle(treeOptionDetail) }));
57
+ return (_jsxs("div", Object.assign({}, treeOptionDetail.attrs, { children: [!!indent && indent, _jsx("div", { className: "aio-input-tree-text", children: text })] })));
58
+ };
59
+ export const useTree = (hookProps) => {
60
+ let [openDic, setOpenDic] = useState({}), openDicRef = useRef(openDic);
61
+ openDicRef.current = openDic;
62
+ const dicRef = useRef({});
63
+ function SetOpen(id, state) {
64
+ const newOpenDic = Object.assign(Object.assign({}, openDicRef.current), { [id]: state });
65
+ setOpenDic(newOpenDic);
66
+ }
67
+ function toggle(treeOptionDetail, state) {
68
+ const open = state === undefined ? treeOptionDetail.open : state;
69
+ const newOpen = !open;
70
+ SetOpen(treeOptionDetail.value, newOpen);
71
+ }
72
+ const isOpen = (treeOptionDetail) => {
73
+ if (!treeOptionDetail) {
74
+ return true;
75
+ }
76
+ const { parentOptions, value } = treeOptionDetail;
77
+ for (let i = 0; i < parentOptions.length; i++) {
78
+ const parentOption = parentOptions[i];
79
+ if (openDicRef.current[parentOption.value] !== true) {
80
+ return false;
81
+ }
82
+ }
83
+ return openDicRef.current[value] === true;
84
+ };
85
+ function change(row, newRow) {
86
+ for (let prop in newRow) {
87
+ row[prop] = newRow[prop];
88
+ }
89
+ if (hookProps.onChange) {
90
+ hookProps.onChange(hookProps.mainValueRef.current);
91
+ }
92
+ }
93
+ function getChilds(treeOptionDetail) {
94
+ var _a;
95
+ const { row } = treeOptionDetail;
96
+ try {
97
+ if (!!((_a = hookProps.treeOptionProp) === null || _a === void 0 ? void 0 : _a.childs)) {
98
+ return hookProps.treeOptionProp.childs(treeOptionDetail);
99
+ }
100
+ else {
101
+ return row.childs;
102
+ }
103
+ }
104
+ catch (_b) {
105
+ return;
106
+ }
107
+ }
108
+ function setChilds(treeOptionDetail, childs, setChilds) {
109
+ try {
110
+ if (setChilds) {
111
+ setChilds(treeOptionDetail, childs);
112
+ }
113
+ else {
114
+ treeOptionDetail.row.childs = childs;
115
+ }
116
+ }
117
+ catch (_a) { }
118
+ }
119
+ function getOptions(p) {
120
+ let { level, parentOptions, rows } = p;
121
+ let list = [];
122
+ for (let i = 0; i < rows.length; i++) {
123
+ let row = rows[i];
124
+ const treeOptionDetail = {
125
+ text: null,
126
+ row,
127
+ toggleIcon: undefined,
128
+ childs: undefined,
129
+ level,
130
+ index: i,
131
+ parentOptions,
132
+ open: false,
133
+ toggle: () => { },
134
+ change: () => { },
135
+ value: undefined,
136
+ isFirstChild: false,
137
+ isLastChild: false,
138
+ isRoot: level === 0,
139
+ isLeaf: false,
140
+ attrs: undefined,
141
+ values: parentOptions.map((o) => o.value)
142
+ };
143
+ const value = getOptionValue(row, 'value', treeOptionDetail);
144
+ treeOptionDetail.value = value;
145
+ treeOptionDetail.values.push(value);
146
+ treeOptionDetail.open = isOpen(treeOptionDetail);
147
+ const childs = getOptionValue(row, 'childs', treeOptionDetail);
148
+ treeOptionDetail.childs = childs;
149
+ treeOptionDetail.isLeaf = !childs;
150
+ treeOptionDetail.toggle = (state) => toggle(treeOptionDetail, state);
151
+ treeOptionDetail.change = (newRow) => change(row, newRow);
152
+ treeOptionDetail.toggleIcon = getOptionValue(row, 'toggleIcon', treeOptionDetail);
153
+ const show = getOptionValue(row, 'show', treeOptionDetail, true);
154
+ if (show === false) {
155
+ continue;
156
+ }
157
+ treeOptionDetail.text = getOptionValue(row, 'text', treeOptionDetail);
158
+ dicRef.current = Object.assign(Object.assign({}, dicRef.current), { [treeOptionDetail.value.toString()]: treeOptionDetail });
159
+ const attrs = getOptionValue(row, 'attrs', treeOptionDetail);
160
+ const className = getOptionValue(row, 'className', treeOptionDetail);
161
+ const style = getOptionValue(row, 'style', treeOptionDetail);
162
+ const onClick = () => {
163
+ var _a, _b;
164
+ if ((_a = hookProps.treeOptionProp) === null || _a === void 0 ? void 0 : _a.onClick) {
165
+ (_b = hookProps.treeOptionProp) === null || _b === void 0 ? void 0 : _b.onClick(treeOptionDetail);
166
+ }
167
+ else if (row.onClick) {
168
+ row.onClick(treeOptionDetail);
169
+ }
170
+ };
171
+ const Attrs = AddToAttrs(Object.assign(Object.assign({}, attrs), { onClick }), { className: [className, 'aio-input-tree-row'], style });
172
+ treeOptionDetail.attrs = Attrs;
173
+ list.push(treeOptionDetail);
174
+ }
175
+ if (list.length) {
176
+ list[0].isFirstChild = true;
177
+ list[list.length - 1].isLastChild = true;
178
+ }
179
+ return list;
180
+ }
181
+ const getOptionValue = (row, key, treeOptionDetail, def) => {
182
+ let res;
183
+ if (hookProps.treeOptionProp && hookProps.treeOptionProp[key]) {
184
+ res = hookProps.treeOptionProp[key](treeOptionDetail);
185
+ }
186
+ if (res !== undefined && res !== null) {
187
+ return res;
188
+ }
189
+ if (typeof row === 'object' && !Array.isArray(row)) {
190
+ res = row[key];
191
+ }
192
+ if (res !== undefined && res !== null) {
193
+ return res;
194
+ }
195
+ return def;
196
+ };
197
+ const getTreeOptionDetailByValue = (value, rows, level, parentOptions) => {
198
+ const res = getOptions({ rows, parentOptions, level });
199
+ return res.find((o) => o.value === value);
200
+ };
201
+ const getTreeOptionDetailByValues = (values, rows) => {
202
+ let res = getTreeOptionDetailByValue(values[0], rows, 0, []);
203
+ if (!res) {
204
+ return;
205
+ }
206
+ for (let i = 1; i < values.length; i++) {
207
+ if (!res || !res.childs) {
208
+ return;
209
+ }
210
+ res = getTreeOptionDetailByValue(values[i], res.childs, i, [...res.parentOptions, res]);
211
+ }
212
+ return res;
213
+ };
214
+ return {
215
+ toggle, openDic, isOpen,
216
+ change, getChilds, setChilds, getOptions, getTreeOptionDetailByValues
217
+ };
218
+ };
219
+ export const Indent = (props) => {
220
+ const { width, level, open, rtl, isLastChild, isParentLastChild, isLeaf } = props;
221
+ const [indentPathes, setIndentPathes] = useState(getIndentIcons);
222
+ useEffect(() => { setIndentPathes(getIndentIcons()); }, [level, isLastChild, isParentLastChild]);
223
+ const [toggleIcon, setToggleIcon] = useState(null);
224
+ useEffect(() => { setToggleIcon(getToggleIcon()); }, [open]);
225
+ function getIndentIcon(rowIndex) {
226
+ if (!level) {
227
+ return null;
228
+ }
229
+ let pathes = [];
230
+ if (rowIndex === level - 1) {
231
+ //horizontal line
232
+ pathes.push(_jsx("div", { style: { [rtl ? 'right' : 'left']: width / 2, width: width / 2, height: 0, top: '50%', borderBottom: '1px dotted', position: 'absolute' } }, 'hl'));
233
+ //vertical direct line
234
+ pathes.push(_jsx("div", { style: { left: width / 2, width: 0, height: isLastChild ? '50%' : '100%', top: 0, borderLeft: '1px dotted', position: 'absolute' } }, 'vdl'));
235
+ }
236
+ else {
237
+ //vertical connet line
238
+ if (!isParentLastChild) {
239
+ pathes.push(_jsx("div", { style: { left: width / 2, width: 0, height: '100%', top: 0, borderLeft: '1px dotted', position: 'absolute' } }, 'vl'));
240
+ }
241
+ }
242
+ return (_jsx("div", { className: 'ai-indent-line', style: { width, height: '100%' }, children: pathes }));
243
+ }
244
+ const getToggleSvg = () => {
245
+ const { toggleIcon = {} } = props;
246
+ if (toggleIcon === false) {
247
+ return false;
248
+ }
249
+ const getIcon = new GetSvg('em').getIcon;
250
+ if (isLeaf) {
251
+ return toggleIcon.leaf || getIcon('mdiCircleSmall', 1);
252
+ }
253
+ if (open) {
254
+ return toggleIcon.open || getIcon('mdiChevronDown', 1);
255
+ }
256
+ return toggleIcon.close || getIcon(rtl ? 'mdiChevronLeft' : 'mdiChevronRight', 1);
257
+ };
258
+ function getIndentIcons() {
259
+ const list = new Array(level).fill(0);
260
+ return list.map((_, i) => {
261
+ return _jsx("div", { className: `ai-indent`, style: { width }, children: getIndentIcon(i) }, i);
262
+ });
263
+ }
264
+ const getToggleIcon = () => {
265
+ return (_jsx("div", { className: "ai-toggle", style: { width }, onClick: (e) => { e.stopPropagation(); if (props.onToggle) {
266
+ props.onToggle();
267
+ } }, children: _jsx("div", { className: `ai-toggle-icon${isLeaf ? ' ai-leaf-icon' : ''}`, children: toggleSvg }) }));
268
+ };
269
+ const toggleSvg = getToggleSvg();
270
+ if (toggleSvg === false) {
271
+ return null;
272
+ }
273
+ return (_jsxs("div", { className: `ai-indents`, children: [indentPathes, toggleIcon] }));
274
+ };
275
+ function classListToString(classes) {
276
+ let list = [];
277
+ const getClasses = (cls) => {
278
+ if (Array.isArray(cls)) {
279
+ return cls.filter((o) => !!o && typeof o === 'string');
280
+ }
281
+ else if (!!cls && typeof cls === 'string') {
282
+ return cls.split(' ');
283
+ }
284
+ return [];
285
+ };
286
+ for (let i = 0; i < classes.length; i++) {
287
+ const cls = classes[i];
288
+ list = [...list, ...getClasses(cls)];
289
+ }
290
+ return list.length ? list.join(' ') : '';
291
+ }
292
+ function AddToAttrs(attrs, p) {
293
+ attrs = attrs || {};
294
+ const className = classListToString([attrs.className, p === null || p === void 0 ? void 0 : p.className]);
295
+ let newStyle = Object.assign(Object.assign({}, attrs.style), p === null || p === void 0 ? void 0 : p.style);
296
+ const res = Object.assign(Object.assign(Object.assign({}, attrs), { className, style: newStyle }), p === null || p === void 0 ? void 0 : p.attrs);
297
+ return res;
298
+ }
299
+ class GetSvg {
300
+ constructor(unit) {
301
+ this.getStyle = (color) => ({ fill: color || "currentcolor" });
302
+ this.getSvgStyle = (size) => {
303
+ size = size || 1;
304
+ return { width: `${size * 1.5}${this.unit}`, height: `${size * 1.5}${this.unit}` };
305
+ };
306
+ this.fixSvgContent = (content, size, p) => {
307
+ const { spin } = p || {};
308
+ let res = null;
309
+ if (spin) {
310
+ res = (_jsxs(_Fragment, { children: [_jsx("style", { children: `@keyframes spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }` }), _jsx("g", { style: { animation: `${spin}s linear 0s infinite normal none running spin`, transformOrigin: 'center center' }, children: content })] }));
311
+ }
312
+ else {
313
+ res = content;
314
+ }
315
+ return (_jsx("svg", { viewBox: "0 0 24 24", role: "presentation", style: this.getSvgStyle(size), children: res }));
316
+ };
317
+ this.getIcon = (path, size, p) => {
318
+ const { color } = p || {};
319
+ const fn = this[path];
320
+ if (!fn) {
321
+ alert(`${path} is not valid`);
322
+ return null;
323
+ }
324
+ const content = fn(color);
325
+ return this.fixSvgContent(content, size, p);
326
+ };
327
+ this.mdiChevronDown = (color) => (_jsx(_Fragment, { children: _jsx("path", { d: "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z", style: this.getStyle(color) }) }));
328
+ this.mdiChevronUp = (color) => (_jsx(_Fragment, { children: _jsx("path", { d: "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z", style: this.getStyle(color) }) }));
329
+ this.mdiChevronLeft = (color) => (_jsx(_Fragment, { children: _jsx("path", { d: "M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z", style: this.getStyle(color) }) }));
330
+ this.mdiChevronRight = (color) => (_jsx(_Fragment, { children: _jsx("path", { d: "M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z", style: this.getStyle(color) }) }));
331
+ this.mdiCircleSmall = (color) => (_jsx(_Fragment, { children: _jsx("path", { d: "M12,10A2,2 0 0,0 10,12C10,13.11 10.9,14 12,14C13.11,14 14,13.11 14,12A2,2 0 0,0 12,10Z", style: this.getStyle(color) }) }));
332
+ this.unit = unit || 'rem';
333
+ }
334
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aio-tree",
3
+ "version": "1.0.0",
4
+ "description": "tree hierarchy",
5
+ "homepage": "https://github.com/mohammadFeiz/aio-tree#readme",
6
+ "types": "index.d.ts",
7
+ "bugs": {
8
+ "url": "https://github.com/mohammadFeiz/aio-tree/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/mohammadFeiz/aio-tree.git"
13
+ },
14
+ "license": "ISC",
15
+ "author": "mohammad sharif feiz feiz.ms@gmail.com",
16
+ "type": "commonjs",
17
+ "main": "index.js",
18
+ "scripts": {
19
+ "test": "echo \"Error: no test specified\" && exit 1"
20
+ }
21
+ }