| import m from 'mithril'; |
| import {classNames} from '../classnames'; |
| import {globals} from '../globals'; |
| import {Button} from './button'; |
| import {hasChildren} from './utils'; |
| |
| export enum TreeLayout { |
| // Classic heirachical tree layout with no columnar alignment. |
| // Example: |
| // foo: bar |
| // ├ baz: qux |
| // └ quux: corge |
| // grault: garply |
| Tree = 'tree', |
| |
| // Heirachical tree layout but right values are horizontally aligned. |
| // Example: |
| // foo bar |
| // ├ baz qux |
| // └ quux corge |
| // grault garply |
| Grid = 'grid', |
| } |
| |
| interface TreeAttrs { |
| // The style of layout. |
| // Defaults to grid. |
| layout?: TreeLayout; |
| // Space delimited class list applied to our tree element. |
| className?: string; |
| } |
| |
| export class Tree implements m.ClassComponent<TreeAttrs> { |
| view({attrs, children}: m.Vnode<TreeAttrs>): m.Children { |
| const { |
| layout: style = TreeLayout.Grid, |
| className = '', |
| } = attrs; |
| |
| if (style === TreeLayout.Grid) { |
| return m('.pf-ptree-grid', {class: className}, children); |
| } else if (style === TreeLayout.Tree) { |
| return m('.pf-ptree', {class: className}, children); |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| interface TreeNodeAttrs { |
| // Content to display on the left hand column. |
| // If omitted, this side will be blank. |
| left?: m.Child; |
| // Content to display on the right hand column. |
| // If omitted, this side will be left blank. |
| right?: m.Child; |
| // Whether this node is collapsed or not. |
| // If omitted, collapsed state 'uncontrolled' - i.e. controlled internally. |
| collapsed?: boolean; |
| // Called when the collapsed state is changed, mainly used in controlled mode. |
| onCollapseChanged?: (collapsed: boolean, attrs: TreeNodeAttrs) => void; |
| } |
| |
| export class TreeNode implements m.ClassComponent<TreeNodeAttrs> { |
| private collapsed = false; |
| view(vnode: m.CVnode<TreeNodeAttrs>): m.Children { |
| return [ |
| m( |
| '.pf-tree-node', |
| this.renderLeft(vnode), |
| this.renderRight(vnode), |
| ), |
| hasChildren(vnode) && this.renderChildren(vnode), |
| ]; |
| } |
| |
| private renderLeft(vnode: m.CVnode<TreeNodeAttrs>) { |
| const { |
| attrs: {left}, |
| } = vnode; |
| |
| return m( |
| '.pf-tree-left', |
| left, |
| hasChildren(vnode) && this.renderCollapseButton(vnode), |
| ); |
| } |
| |
| private renderRight({attrs: {right}}: m.CVnode<TreeNodeAttrs>) { |
| return m('.pf-tree-right', right); |
| } |
| |
| private renderChildren(vnode: m.CVnode<TreeNodeAttrs>) { |
| const {children} = vnode; |
| |
| return m( |
| '.pf-tree-children', |
| { |
| class: classNames(this.isCollapsed(vnode) && 'pf-pgrid-hidden'), |
| }, |
| children, |
| ); |
| } |
| |
| private renderCollapseButton(vnode: m.Vnode<TreeNodeAttrs>) { |
| const {attrs, attrs: {onCollapseChanged = () => {}}} = vnode; |
| |
| return m(Button, { |
| icon: this.isCollapsed(vnode) ? 'chevron_right' : 'expand_more', |
| minimal: true, |
| compact: true, |
| onclick: () => { |
| this.collapsed = !this.isCollapsed(vnode); |
| onCollapseChanged(this.collapsed, attrs); |
| globals.rafScheduler.scheduleFullRedraw(); |
| }, |
| }); |
| } |
| |
| private isCollapsed({attrs}: m.Vnode<TreeNodeAttrs>): boolean { |
| // If collapsed is omitted, use our local collapsed state instead. |
| const { |
| collapsed = this.collapsed, |
| } = attrs; |
| |
| return collapsed; |
| } |
| } |