├── packages
├── example
│ ├── .gitignore
│ ├── src
│ │ ├── styles.css
│ │ ├── app.ts
│ │ ├── components
│ │ │ ├── layout.ts
│ │ │ └── tree-view.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── .editorconfig
│ ├── index.html
│ ├── tslint.json
│ ├── package.json
│ └── tsconfig.json
└── mithril-tree-component
│ ├── .gitignore
│ ├── src
│ ├── models
│ │ ├── tree-item.ts
│ │ ├── index.ts
│ │ ├── tree-state.ts
│ │ └── tree-options.ts
│ ├── declarations
│ │ ├── css.d.ts
│ │ └── svg.d.ts
│ ├── index.ts
│ ├── utils
│ │ └── index.ts
│ ├── tree-item.ts
│ ├── styles
│ │ └── tree-container.css
│ └── tree-container.ts
│ ├── img
│ └── mithril-tree-component-animation.gif
│ ├── cssnano.config.js
│ ├── .npmignore
│ ├── .editorconfig
│ ├── tslint.json
│ ├── rollup.config.js
│ ├── package.json
│ ├── tsconfig.json
│ └── README.md
├── lerna.json
├── .vscode
├── spellright.dict
├── settings.json
└── launch.json
├── .prettierrc
├── docs
├── index.html
├── app.f6c559f6.js
├── app.1bb0a54e.js
└── app.6fe1815b.js
├── LICENSE
├── package.json
├── .gitignore
└── README.md
/packages/example/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | .cache
--------------------------------------------------------------------------------
/packages/mithril-tree-component/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | .rpt2_cache
3 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "version": "0.0.0"
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/spellright.dict:
--------------------------------------------------------------------------------
1 | onclick
2 | Callback
3 | Lerna
4 | async
5 | pnpm
6 | unpkg
7 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/models/tree-item.ts:
--------------------------------------------------------------------------------
1 | export interface ITreeItem {
2 | [key: string]: any;
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "singleQuote": true,
5 | "printWidth": 120,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/declarations/css.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.css" {
2 | const content: string;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/declarations/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const content: string;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "spellright.language": "en",
3 | "spellright.documentTypes": [
4 | "markdown",
5 | "latex",
6 | "plaintext"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './models';
2 | export * from './utils';
3 | export * from './tree-container';
4 | export * from './tree-item';
5 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './tree-item';
2 | export { ITreeOptions, TreeItemAction, TreeItemUpdateAction, ITreeItemViewComponent } from './tree-options';
3 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/img/mithril-tree-component-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikvullings/mithril-tree-component/HEAD/packages/mithril-tree-component/img/mithril-tree-component-animation.gif
--------------------------------------------------------------------------------
/packages/example/src/styles.css:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | -webkit-box-sizing: border-box;
5 | -moz-box-sizing: border-box;
6 | box-sizing: border-box;
7 | }
8 |
9 | .main {
10 | padding: 10px;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/cssnano.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: [
3 | 'default',
4 | {
5 | calc: false,
6 | discardComments: {
7 | removeAll: true
8 | }
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/mithril-tree-component/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .editorconfig
3 | .gitignore
4 | .cache
5 | src
6 | node_modules
7 | package-lock.json
8 | yarn.lock
9 | tsconfig.json
10 | tslint.json
11 | *.logs
12 | .rpt2_cache
13 | cssnano.config.js
14 | rollup.config.js
15 | img
--------------------------------------------------------------------------------
/packages/example/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = false
10 |
11 | # 2 space indentation
12 | [**.*]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = false
10 |
11 | # 2 space indentation
12 | [**.*]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/packages/example/src/app.ts:
--------------------------------------------------------------------------------
1 | import 'materialize-css/dist/css/materialize.min.css';
2 | import './styles.css';
3 | import m, { RouteDefs } from 'mithril';
4 | import { TreeView } from './components/tree-view';
5 | import { Layout } from './components/layout';
6 |
7 | const routingTable: RouteDefs = {
8 | '/': {
9 | render: () => m(Layout, m(TreeView)),
10 | },
11 | };
12 |
13 | m.route(document.body, '/', routingTable);
14 |
--------------------------------------------------------------------------------
/packages/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Mithril-tree-component example
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | Mithril-tree-component example
--------------------------------------------------------------------------------
/packages/example/src/components/layout.ts:
--------------------------------------------------------------------------------
1 | import m, { Vnode } from 'mithril';
2 |
3 | export const Layout = () => ({
4 | view: (vnode: Vnode) =>
5 | m('container', [
6 | m(
7 | 'nav',
8 | m('.nav-wrapper', [
9 | m(
10 | 'a.brand-logo',
11 | { style: 'margin-left: 20px' },
12 | m('span', { style: 'margin-top: 10px; margin-left: -10px;' }, 'MITHRIL TREE COMPONENT')
13 | ),
14 | ])
15 | ),
16 | m('section.main', vnode.children),
17 | ]),
18 | });
19 |
--------------------------------------------------------------------------------
/packages/example/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended"],
4 | "jsRules": {},
5 | "rules": {
6 | "no-console": false,
7 | "no-debugger": false,
8 | "quotemark": [true, "single"],
9 | "trailing-comma": [
10 | true,
11 | {
12 | "multiline": {
13 | "objects": "always",
14 | "arrays": "always",
15 | "functions": "never",
16 | "typeLiterals": "ignore"
17 | },
18 | "esSpecCompliant": true
19 | }
20 | ],
21 | "object-literal-sort-keys": false,
22 | "ordered-imports": false,
23 | "arrow-parens": [false, "ban-single-arg-parens"]
24 | },
25 | "rulesDirectory": []
26 | }
27 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended"],
4 | "jsRules": {},
5 | "rules": {
6 | "no-console": false,
7 | "no-debugger": false,
8 | "quotemark": [true, "single"],
9 | "trailing-comma": [
10 | true,
11 | {
12 | "multiline": {
13 | "objects": "always",
14 | "arrays": "always",
15 | "functions": "never",
16 | "typeLiterals": "ignore"
17 | },
18 | "esSpecCompliant": true
19 | }
20 | ],
21 | "object-literal-sort-keys": false,
22 | "ordered-imports": false,
23 | "arrow-parens": [false, "ban-single-arg-parens"]
24 | },
25 | "rulesDirectory": []
26 | }
27 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:62608",
12 | "webRoot": "${workspaceFolder}/packages/example/dist",
13 | "skipFiles": ["jquery.js"],
14 | "smartStep": true,
15 | "sourceMapPathOverrides": {
16 | "../node_modules/mithril-tree-component/dist/*": "${webRoot}/../../mithril-tree-component/dist/*"
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/models/tree-state.ts:
--------------------------------------------------------------------------------
1 | import { IInternalTreeOptions } from './tree-options';
2 | import { Attributes } from 'mithril';
3 | import { ITreeItem } from '.';
4 |
5 | export interface ITreeState {
6 | tree?: ITreeItem[];
7 | /** Name of the parent ID property (default 'parentId') */
8 | parentId: string;
9 | /** ID of the selected tree item */
10 | selectedId?: string | number;
11 | /** ID of the tree item that is being dragged */
12 | dragId?: string | number;
13 | /** Options for the tree */
14 | options: IInternalTreeOptions;
15 | /** Options for dragging */
16 | dragOptions: Attributes;
17 | /** Width of the item */
18 | width: number;
19 | /** When dragging, set this to true */
20 | isDragging: boolean;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/example/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert an item array to a tree. Assumes each item has a parentId.
3 | * @param items Items
4 | */
5 | export const unflatten = (
6 | entities: T[],
7 | // parent = {} as T & ITree,
8 | // tree = [] as Array>
9 | parent = { id: null } as { id: string | number | null; children?: T[] },
10 | tree = [] as Array
11 | ) => {
12 | const children = (parent.id
13 | ? entities.filter(entity => entity.parentId === parent.id)
14 | : entities.filter(entity => !entity.parentId)) as Array;
15 |
16 | if (children.length > 0) {
17 | if (!parent.id) {
18 | tree = children;
19 | } else {
20 | parent.children = children;
21 | }
22 | children.map(child => unflatten(entities, child));
23 | }
24 |
25 | return tree;
26 | };
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Erik Vullings
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mithril-tree-component",
3 | "version": "0.0.1",
4 | "description": "A tree component for the Mitrhil framework.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/erikvullings/mithril-tree-component.git"
8 | },
9 | "keywords": [
10 | "mithril",
11 | "tree",
12 | "component",
13 | "typescript"
14 | ],
15 | "author": "Erik Vullings (http://www.tno.nl)",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/erikvullings/mithril-tree-component/issues"
19 | },
20 | "homepage": "https://github.com/erikvullings/mithril-tree-component#readme",
21 | "scripts": {
22 | "postinstall": "lerna run link",
23 | "build": "lerna run build",
24 | "clean:local": "rimraf ./docs",
25 | "clean": "npm run clean:local && lerna run --parallel clean",
26 | "start": "lerna run --parallel start",
27 | "deploy": "lerna run --parallel deploy",
28 | "patch-release": "lerna run patch-release",
29 | "minor-release": "lerna run minor-release"
30 | },
31 | "devDependencies": {
32 | "lerna": "^3.22.1",
33 | "rimraf": "^3.0.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 | .rpt2_cache
63 |
--------------------------------------------------------------------------------
/packages/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mithril-tree-component-example",
3 | "private": true,
4 | "version": "0.0.1",
5 | "description": "Example project, showing how to use the Mitrhil tree component.",
6 | "main": "index.js",
7 | "scripts": {
8 | "clean": "rimraf ./.cache ./dist",
9 | "link": "pnpm link mithril-tree-component",
10 | "start": "parcel index.html",
11 | "build": "parcel build index.html --out-dir ../../docs --public-url https://erikvullings.github.io/mithril-tree-component",
12 | "deploy": "npm run build"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/erikvullings/mithril-tree-component.git"
17 | },
18 | "keywords": [
19 | "mithril",
20 | "tree",
21 | "component",
22 | "typescript"
23 | ],
24 | "author": "Erik Vullings (http://www.tno.nl)",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/erikvullings/mithril-tree-component/issues"
28 | },
29 | "homepage": "https://github.com/erikvullings/mithril-tree-component#readme",
30 | "dependencies": {
31 | "materialize-css": "^1.0.0",
32 | "mithril": "^2.0.4",
33 | "mithril-tree-component": "^0.7.0"
34 | },
35 | "devDependencies": {
36 | "@types/mithril": "github:MithrilJS/mithril.d.ts#v2",
37 | "autoprefixer": "^10.0.1",
38 | "cssnano": "^4.1.10",
39 | "parcel-bundler": "^1.12.4",
40 | "rimraf": "^3.0.2",
41 | "typescript": "^4.0.5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import m, { FactoryComponent, Attributes } from 'mithril';
2 | import { TreeItemAction } from '..';
3 |
4 | /**
5 | * Create a GUID
6 | * @see https://stackoverflow.com/a/2117523/319711
7 | *
8 | * @returns RFC4122 version 4 compliant GUID
9 | */
10 | export const uuid4 = () => {
11 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
12 | // tslint:disable-next-line:no-bitwise
13 | const r = (Math.random() * 16) | 0;
14 | // tslint:disable-next-line:no-bitwise
15 | const v = c === 'x' ? r : (r & 0x3) | 0x8;
16 | return v.toString(16);
17 | });
18 | };
19 |
20 | /** Move an item in an array from index to another index */
21 | export const move = (arr: T[], from: number, to: number) =>
22 | arr ? arr.splice(to, 0, arr.splice(from, 1)[0]) : undefined;
23 |
24 | export interface ITreeButtonOptions extends Attributes {
25 | buttonName: TreeItemAction;
26 | }
27 |
28 | export const TreeButton: FactoryComponent = () => {
29 | const textSymbol = (buttonName: TreeItemAction) => {
30 | switch (buttonName) {
31 | case 'add_child':
32 | case 'create':
33 | return '✚';
34 | case 'delete':
35 | return '✖';
36 | case 'expand_more':
37 | return '▶';
38 | case 'expand_less':
39 | return '◢';
40 | }
41 | };
42 | const classNames = (buttonName: TreeItemAction) => {
43 | switch (buttonName) {
44 | case 'expand_more':
45 | case 'expand_less':
46 | return '.mtc__clickable.mtc__collapse-expand-item';
47 | default:
48 | return '.mtc__act';
49 | }
50 | };
51 | return {
52 | view: ({ attrs: { buttonName, ...params } }) => m(`${classNames(buttonName)}`, params, textSymbol(buttonName)),
53 | };
54 | };
55 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import sourceMaps from 'rollup-plugin-sourcemaps';
4 | import typescript from 'rollup-plugin-typescript2';
5 | import postcss from 'rollup-plugin-postcss';
6 | import json from 'rollup-plugin-json';
7 | import cssnext from 'postcss-cssnext';
8 | import cssnano from 'cssnano';
9 | import { terser } from 'rollup-plugin-terser';
10 |
11 | const pkg = require('./package.json');
12 | const production = !process.env.ROLLUP_WATCH;
13 |
14 | export default {
15 | input: `src/index.ts`,
16 | watch: 'src/**',
17 | context: 'null',
18 | moduleContext: 'null',
19 | output: [
20 | {
21 | file: pkg.module,
22 | format: 'es',
23 | sourcemap: true,
24 | },
25 | {
26 | file: pkg.main,
27 | format: 'iife',
28 | name: 'TreeContainer',
29 | sourcemap: true,
30 | globals: {
31 | mithril: 'm',
32 | },
33 | },
34 | ],
35 | // Indicate here external modules you don't want to include in your bundle
36 | external: ['mithril'],
37 | // external: [...Object.keys(pkg.dependencies || {})],
38 | watch: {
39 | include: 'src/**',
40 | },
41 | plugins: [
42 | // Allow json resolution
43 | json(),
44 | postcss({
45 | extensions: ['.css'],
46 | plugins: [cssnext({ warnForDuplicates: false }), cssnano()],
47 | }),
48 | // Compile TypeScript files
49 | typescript({
50 | rollupCommonJSResolveHack: true,
51 | typescript: require('typescript'),
52 | }),
53 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
54 | commonjs(),
55 | // Allow node_modules resolution, so you can use 'external' to control
56 | // which external modules to include in the bundle
57 | // https://github.com/rollup/rollup-plugin-node-resolve#usage
58 | resolve({
59 | customResolveOptions: {
60 | moduleDirectory: 'node_modules',
61 | },
62 | }),
63 | // Resolve source maps to the original source
64 | sourceMaps(),
65 | // minifies generated bundles
66 | production && terser(),
67 | ],
68 | };
69 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mithril-tree-component",
3 | "version": "0.7.1",
4 | "description": "A tree component for the Mitrhil framework.",
5 | "main": "dist/mithril-tree-component.js",
6 | "module": "dist/mithril-tree-component.mjs",
7 | "browser": "dist/mithril-tree-component.mjs",
8 | "typings": "dist/index.d.ts",
9 | "scripts": {
10 | "link": "pnpm link",
11 | "start": "rollup -c -w",
12 | "build": "npm run build:production",
13 | "clean": "rimraf ./dist ./node_modules/.ignored",
14 | "build:production": "npm run clean && rollup -c",
15 | "build:domain": "typedoc --out ../../docs/typedoc src",
16 | "patch-release": "npm run build && npm version patch --force -m \"Patch release\" && npm publish && git push --follow-tags",
17 | "minor-release": "npm run build && npm version minor --force -m \"Minor release\" && npm publish && git push --follow-tags",
18 | "major-release": "npm run build && npm version major --force -m \"Major release\" && npm publish && git push --follow-tags"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/erikvullings/mithril-tree-component.git"
23 | },
24 | "keywords": [
25 | "mithril",
26 | "tree",
27 | "component",
28 | "typescript"
29 | ],
30 | "author": "Erik Vullings (http://www.tno.nl)",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/erikvullings/mithril-tree-component/issues"
34 | },
35 | "homepage": "https://github.com/erikvullings/mithril-tree-component#readme",
36 | "devDependencies": {
37 | "@types/mithril": "github:MithrilJS/mithril.d.ts#v2",
38 | "autoprefixer": "^10.0.1",
39 | "rollup": "^2.32.1",
40 | "rollup-plugin-commonjs": "^10.1.0",
41 | "rollup-plugin-json": "^4.0.0",
42 | "rollup-plugin-node-resolve": "^5.2.0",
43 | "rollup-plugin-postcss": "^3.1.8",
44 | "rollup-plugin-sourcemaps": "^0.6.3",
45 | "rollup-plugin-terser": "^7.0.2",
46 | "rollup-plugin-typescript2": "^0.28.0",
47 | "cssnano": "^4.1.10",
48 | "postcss-cssnext": "^3.1.0",
49 | "tslib": "^2.0.3",
50 | "typedoc": "^0.19.2",
51 | "typescript": "^4.0.5",
52 | "rimraf": "^3.0.2",
53 | "set-value": ">=3.0.2",
54 | "mixin-deep": ">=2.0.1",
55 | "lodash.template": ">=4.5.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/models/tree-options.ts:
--------------------------------------------------------------------------------
1 | import { Component, Attributes } from 'mithril';
2 | import { ITreeItem } from '.';
3 |
4 | /** Indicates the type of action is performed on the tree item. */
5 | export type TreeItemAction = 'create' | 'delete' | 'add_child' | 'expand_more' | 'expand_less';
6 |
7 | /** Indicates the type of UPDATE action is performed on the tree item. */
8 | export type TreeItemUpdateAction = 'edit' | 'move';
9 |
10 | export interface ITreeItemViewComponent {
11 | width: number;
12 | treeItem: ITreeItem;
13 | depth: number;
14 | }
15 |
16 | export interface ITreeOptions {
17 | tree: ITreeItem[];
18 | /** If provided, this component is used to display the tree item. */
19 | treeItemView: Component;
20 | /** Name of the name property, e.g. how the tree item is displayed in the tree (default 'name') */
21 | name: string;
22 | /** Name of the ID property (default 'id') */
23 | id: string;
24 | /** Name of the parent ID property (default 'parentId') */
25 | parentId: string;
26 | /** Name of the open property, e.g. to display or hide the children (default 'isOpen') */
27 | isOpen: string | undefined | ((id: string, action: 'get' | 'set', value?: boolean) => void | boolean);
28 | /**
29 | * At what level do you prevent creating new children: 1 is only children, 2 is grandchildren, etc.
30 | * Default is Number.MAX_SAFE_INTEGER. NOTE: It does not prevent you to move items with children.
31 | */
32 | maxDepth: number;
33 | /** If true (default), you can have multiple root nodes */
34 | multipleRoots: boolean;
35 | /** If enabled, turn on logging */
36 | logging: boolean;
37 | /** When a tree item is selected, this function is invoked */
38 | onSelect: (treeItem: ITreeItem, isSelected: boolean) => void | Promise;
39 | /** When a tree item is opened (expanded) or closed */
40 | onToggle: (treeItem: ITreeItem, isExpanded: boolean) => void | Promise;
41 | /** Before a tree item is created, this function is invoked. When it returns false, the action is cancelled. */
42 | onBeforeCreate: (treeItem: ITreeItem) => boolean | void | Promise;
43 | /** When a tree item has been created, this function is invoked */
44 | onCreate: (treeItem: ITreeItem) => void | Promise;
45 | /** Before a tree item is deleted, this function is invoked. When it returns false, the action is cancelled. */
46 | onBeforeDelete: (treeItem: ITreeItem) => boolean | void | Promise;
47 | /** When a tree item has been deleted, this function is invoked */
48 | onDelete: (treeItem: ITreeItem) => void | Promise;
49 | /** Before a tree item has been updated, this function is invoked. When it returns false, the action is cancelled. */
50 | onBeforeUpdate: (
51 | treeItem: ITreeItem,
52 | action?: TreeItemUpdateAction,
53 | newParent?: ITreeItem
54 | ) => boolean | void | Promise;
55 | /** When a tree item has been updated, this function is invoked */
56 | onUpdate: (treeItem: ITreeItem, action?: TreeItemUpdateAction, newParent?: ITreeItem) => void | Promise;
57 | /**
58 | * Factory function that can be used to create new items. If there is no parent, the depth is -1.
59 | * If parent treeItem is missing, a root item should be created.
60 | */
61 | create: (parent?: ITreeItem, depth?: number, width?: number) => ITreeItem | Promise;
62 | /** Does the tree support editing, e.g. creating, deleting or updating. */
63 | editable: Partial<{
64 | /** Allow creating of new items. */
65 | canCreate: boolean;
66 | /** Allow deleting of items. */
67 | canDelete: boolean;
68 | /** Allow deleting of items that are parents (so all children would be deleted too). */
69 | canDeleteParent: boolean;
70 | /** Allow updating of items. */
71 | canUpdate: boolean;
72 | }>;
73 | /**
74 | * Component to display icons to create, delete, etc.
75 | * The component will receive an onclick attribute to perform its function.
76 | */
77 | button: (name: TreeItemAction) => Component;
78 | /** When the tree is empty, what text do you want to show. Default 'Create your first item' */
79 | placeholder: string;
80 | }
81 |
82 | export interface IInternalTreeOptions extends ITreeOptions {
83 | /** Internal function: retrieves the tree item based on its id */
84 | _find: (id: string | number) => ITreeItem | undefined;
85 | _findChildren: (treeItem: ITreeItem) => ITreeItem[];
86 | /** Internal function: creates a sibling tree item */
87 | _createItem: (siblingId?: string | number, width?: number) => void;
88 | _deleteItem: (id?: string | number) => void;
89 | _hasChildren: (treeItem: ITreeItem) => boolean;
90 | _addChildren: (treeItem: ITreeItem, width?: number) => void;
91 | _depth: (treeItem: ITreeItem, curDepth?: number) => number;
92 | _isExpanded: (treeItem: ITreeItem) => boolean;
93 | }
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mithril-tree-component
2 |
3 | A tree component for [Mithril](https://mithril.js.org) that supports drag-and-drop, as well as selecting, creating and deleting items. You can play with it [here](https://erikvullings.github.io/mithril-tree-component/#!/).
4 |
5 | 
6 |
7 | **Functionality:**
8 |
9 | - Drag-and-drop to move items (if `editable.canUpdate` is true).
10 | - Create and delete tree items (if `editable.canDelete` and `editable.canDeleteParent` is true).
11 | - Configurable properties for:
12 | - `id` property: unique id of the item.
13 | - `parentId` property: id of the parent.
14 | - `name` property: display title. Alternatively, provide your own component.
15 | - `maxDepth`: when specified, and editable.canCreate is true, do not add children that would exceed this depth, where depth is 0 for root items, 1 for children, etc.
16 | - `isOpen`: to indicate whether the tree should show the children. If not provided, the open/close state is maintained internally. This slightly affects the behaviour of the tree, e.g. after creating items, the parent is not automatically opened.
17 | - `create`: can be used to add your own TreeItem creation logic.
18 | - Callback events:
19 | - `onSelect`: when a tree item is selected.
20 | - `onBefore`[Create | Update | Delete]: can be used to intercept (and block) tree item actions. If the onBeforeX call returns false, the action is stopped.
21 | - `on[Create | Update | Delete]`: when the creation is done.
22 | - When using async functions or promises, please make sure to call `m.redraw()` when you are done.
23 |
24 | This repository contains two projects:
25 |
26 | - An example project, showcasing the usage of the component.
27 | - The mithril-tree-component itself.
28 |
29 | ## Installation
30 |
31 | The tree component is available on [npm](https://www.npmjs.com/package/mithril-tree-component) or can be used directly from [unpkg](https://unpkg.com/mithril-tree-component).
32 |
33 | ```bash
34 | npm i mithril-tree-component
35 | ```
36 |
37 | ## Usage
38 |
39 | ```bash
40 | npm i mithril-tree-component
41 | ```
42 |
43 | From the [example project](../example). There you can also find some CSS styles.
44 |
45 | ```ts
46 | import m from 'mithril';
47 | import { unflatten } from '../utils';
48 | import { TreeContainer, ITreeOptions, ITreeItem, uuid4 } from 'mithril-tree-component';
49 |
50 | interface IMyTree extends ITreeItem {
51 | id: number | string;
52 | parentId: number | string;
53 | title: string;
54 | }
55 |
56 | export const TreeView = () => {
57 | const data: IMyTree[] = [
58 | { id: 1, parentId: 0, title: 'My id is 1' },
59 | { id: 2, parentId: 1, title: 'My id is 2' },
60 | { id: 3, parentId: 1, title: 'My id is 3' },
61 | { id: 4, parentId: 2, title: 'My id is 4' },
62 | { id: 5, parentId: 0, title: 'My id is 5' },
63 | { id: 6, parentId: 0, title: 'My id is 6' },
64 | { id: 7, parentId: 4, title: 'My id is 7' },
65 | ];
66 | const tree = unflatten(data);
67 | const options = {
68 | id: 'id',
69 | parentId: 'parentId',
70 | isOpen: 'isOpen',
71 | name: 'title',
72 | onSelect: (ti, isSelected) => console.log(`On ${isSelected ? 'select' : 'unselect'}: ${ti.title}`),
73 | onBeforeCreate: ti => console.log(`On before create ${ti.title}`),
74 | onCreate: ti => console.log(`On create ${ti.title}`),
75 | onBeforeDelete: ti => console.log(`On before delete ${ti.title}`),
76 | onDelete: ti => console.log(`On delete ${ti.title}`),
77 | onBeforeUpdate: (ti, action, newParent) =>
78 | console.log(`On before ${action} update ${ti.title} to ${newParent ? newParent.title : ''}.`),
79 | onUpdate: ti => console.log(`On update ${ti.title}`),
80 | create: (parent?: IMyTree) => {
81 | const item = {} as IMyTree;
82 | item.id = uuid4();
83 | if (parent) {
84 | item.parentId = parent.id;
85 | }
86 | item.title = `Created at ${new Date().toLocaleTimeString()}`;
87 | return item as ITreeItem;
88 | },
89 | editable: { canCreate: true, canDelete: true, canUpdate: true, canDeleteParent: false },
90 | } as ITreeOptions;
91 | return {
92 | view: () =>
93 | m('.row', [
94 | m('.col.s6', [m('h3', 'Mithril-tree-component'), m(TreeContainer, { tree, options })]),
95 | m('.col.s6', [m('h3', 'Tree data'), m('pre', m('code', JSON.stringify(tree, null, 2)))]),
96 | ]),
97 | };
98 | };
99 | ```
100 |
101 | ## Development
102 |
103 | I prefer to use [pnpm](https://pnpm.js.org/) to install all libs only once on my PC, so if you aren't using it yet, please try it out.
104 |
105 | ```bash
106 | npm i -g pnpm
107 | pnpm m i
108 | npm start
109 | ```
110 |
111 | ## Contributing
112 |
113 | Pull requests and stars are always welcome.
114 |
115 | For bugs and feature requests, please create an issue.
116 |
117 | 1. Fork it!
118 | 2. Create your feature branch: `git checkout -b my-new-feature`
119 | 3. Commit your changes: `git commit -am 'Add some feature'`
120 | 4. Push to the branch: `git push origin my-new-feature`
121 | 5. Submit a pull request :D
122 |
--------------------------------------------------------------------------------
/packages/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target":
5 | "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
6 | "module":
7 | "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
8 | "lib": [
9 | "dom",
10 | "es5",
11 | "es2015.promise",
12 | "es2017"
13 | ] /* Specify library files to be included in the compilation. */,
14 | // "allowJs": true, /* Allow javascript files to be compiled. */
15 | // "checkJs": true, /* Report errors in .js files. */
16 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
17 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
18 | "sourceMap": true /* Generates corresponding '.map' file. */,
19 | // "outFile": "./", /* Concatenate and emit output to single file. */
20 | "outDir": "./dist" /* Redirect output structure to the directory. */,
21 | "rootDir":
22 | "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
23 | // "removeComments": true, /* Do not emit comments to output. */
24 | // "noEmit": true, /* Do not emit outputs. */
25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
28 |
29 | /* Strict Type-Checking Options */
30 | "strict": true /* Enable all strict type-checking options. */,
31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
32 | // "strictNullChecks": true, /* Enable strict null checks. */
33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
37 |
38 | /* Additional Checks */
39 | // "noUnusedLocals": true, /* Report errors on unused locals. */
40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
43 |
44 | /* Module Resolution Options */
45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 | // "typeRoots": [], /* List of folders to include type definitions from. */
50 | // "types": [], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 |
55 | /* Source Map Options */
56 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
57 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
60 |
61 | /* Experimental Options */
62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
64 | },
65 | "parcelTsPluginOptions": {
66 | // If true type-checking is disabled
67 | "transpileOnly": false
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target":
5 | "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
6 | "module":
7 | "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
8 | "lib": [
9 | "dom",
10 | "es5",
11 | "es2015.promise",
12 | "es2017"
13 | ] /* Specify library files to be included in the compilation. */,
14 | // "allowJs": true, /* Allow javascript files to be compiled. */
15 | // "checkJs": true, /* Report errors in .js files. */
16 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
17 | "declaration": true, /* Generates corresponding '.d.ts' file. */
18 | "sourceMap": true /* Generates corresponding '.map' file. */,
19 | // "outFile": "./", /* Concatenate and emit output to single file. */
20 | "outDir": "./dist" /* Redirect output structure to the directory. */,
21 | "rootDir":
22 | "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
23 | "removeComments": false, /* Do not emit comments to output. */
24 | // "noEmit": true, /* Do not emit outputs. */
25 | "importHelpers": true, /* Import emit helpers from 'tslib'. */
26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
28 |
29 | /* Strict Type-Checking Options */
30 | "skipLibCheck": true,
31 | "strict": true /* Enable all strict type-checking options. */,
32 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
33 | "strictNullChecks": true, /* Enable strict null checks. */
34 | "strictFunctionTypes": true, /* Enable strict checking of function types. */
35 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
36 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
38 |
39 | /* Additional Checks */
40 | "noUnusedLocals": true, /* Report errors on unused locals. */
41 | "noUnusedParameters": true, /* Report errors on unused parameters. */
42 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
44 |
45 | /* Module Resolution Options */
46 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 |
56 | /* Source Map Options */
57 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 |
62 | /* Experimental Options */
63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 | },
66 | "parcelTsPluginOptions": {
67 | // If true type-checking is disabled
68 | "transpileOnly": false
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/tree-item.ts:
--------------------------------------------------------------------------------
1 | import m, { FactoryComponent, Attributes } from 'mithril';
2 | import { ITreeItem } from './models';
3 | import { IInternalTreeOptions } from './models/tree-options';
4 | import { TreeButton } from './utils';
5 |
6 | export const TreeItemIdPrefix = 'tree-item-';
7 |
8 | interface ITreeItemAttributes {
9 | width: number;
10 | item: ITreeItem;
11 | options: IInternalTreeOptions;
12 | selectedId?: string | number;
13 | dragOptions: Attributes;
14 | }
15 |
16 | export const TreeItem: FactoryComponent = () => {
17 | const tiState = {} as {
18 | open: (treeItem: ITreeItem, isExpanded: boolean) => void;
19 | toggle: (treeItem: ITreeItem, onToggle?: (treeItem: ITreeItem, isExpanded: boolean) => void) => void;
20 | };
21 |
22 | return {
23 | oninit: ({ attrs }) => {
24 | const { options } = attrs;
25 | const { isOpen = 'isOpen', id, _hasChildren } = options;
26 |
27 | tiState.toggle = (treeItem: ITreeItem, onToggle?: (treeItem: ITreeItem, isExpanded: boolean) => void) => {
28 | if (_hasChildren(treeItem)) {
29 | if (typeof isOpen === 'function') {
30 | isOpen(treeItem[id], 'set', !isOpen(treeItem[id], 'get'));
31 | } else {
32 | treeItem[isOpen] = !treeItem[isOpen];
33 | }
34 | onToggle && onToggle(treeItem, typeof isOpen === 'function' ? isOpen(treeItem[id], 'get') : treeItem[isOpen]);
35 | }
36 | };
37 | tiState.open = (treeItem: ITreeItem, isExpanded: boolean) => {
38 | if (isExpanded) {
39 | return;
40 | }
41 | if (typeof isOpen === 'function') {
42 | isOpen(treeItem[id], 'set', true);
43 | } else {
44 | treeItem[isOpen] = true;
45 | }
46 | };
47 | },
48 | view: ({ attrs: { item, options, dragOptions, selectedId, width } }) => {
49 | const {
50 | id,
51 | treeItemView,
52 | _findChildren,
53 | _isExpanded,
54 | _addChildren,
55 | _hasChildren,
56 | _depth,
57 | onSelect,
58 | onToggle,
59 | onDelete,
60 | editable: { canUpdate, canCreate, canDelete, canDeleteParent },
61 | maxDepth,
62 | } = options;
63 | const { toggle, open } = tiState;
64 | const isExpanded = _isExpanded(item);
65 | const hasChildren = _hasChildren(item);
66 | const depth = _depth(item);
67 | return m(
68 | `li${canUpdate ? '.mtc__draggable' : ''}[id=${TreeItemIdPrefix}${item[id]}][draggable=${canUpdate}]`,
69 | dragOptions,
70 | [
71 | m(
72 | '.mtc__item',
73 | {
74 | onclick: (ev: MouseEvent) => {
75 | ev.stopPropagation();
76 | selectedId !== item[id] && onSelect(item, true);
77 | },
78 | },
79 | [
80 | m(
81 | '.mtc__header.mtc__clearfix',
82 | {
83 | class: `${selectedId === item[id] ? 'active' : ''}`,
84 | },
85 | [
86 | hasChildren
87 | ? m(TreeButton, {
88 | buttonName: isExpanded ? 'expand_less' : 'expand_more',
89 | onclick: () => toggle(item, onToggle),
90 | })
91 | : undefined,
92 | m(
93 | '.mtc__item-title',
94 | {
95 | class: `${canUpdate ? 'mtc__moveable' : ''} ${hasChildren ? '' : 'mtc__childless-item'}`,
96 | style: `max-width: ${width}px`,
97 | },
98 | m(treeItemView, { treeItem: item, depth, width })
99 | ),
100 | ],
101 | m('.mtc__act-group', [
102 | canDelete && (canDeleteParent || !hasChildren)
103 | ? m(TreeButton, {
104 | buttonName: 'delete',
105 | onclick: () => onDelete(item),
106 | })
107 | : '',
108 | canCreate && depth < maxDepth
109 | ? m(TreeButton, {
110 | buttonName: 'add_child',
111 | onclick: (ev: MouseEvent) => {
112 | ev.stopPropagation();
113 | _addChildren(item, width);
114 | open(item, isExpanded);
115 | },
116 | })
117 | : '',
118 | ])
119 | ),
120 | isExpanded
121 | ? m('ul.mtc__item-body', [
122 | // ...item[children].map((i: ITreeItem) =>
123 | ..._findChildren(item).map((i: ITreeItem) =>
124 | m(TreeItem, {
125 | width: width - 12,
126 | item: i,
127 | options,
128 | dragOptions,
129 | selectedId,
130 | key: i[id],
131 | })
132 | ),
133 | ])
134 | : '',
135 | ]
136 | ),
137 | ]
138 | );
139 | },
140 | };
141 | };
142 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/styles/tree-container.css:
--------------------------------------------------------------------------------
1 | .mtc .mtc__clearfix::after {
2 | content: '';
3 | clear: both;
4 | display: table;
5 | }
6 |
7 | .mtc {
8 | padding-left: 0;
9 | margin: 0.5rem 0 1rem 0;
10 | border: 1px solid #e0e0e0;
11 | border-radius: 2px;
12 | overflow: hidden;
13 | position: relative;
14 | white-space: nowrap;
15 | }
16 | .mtc.mtc__empty {
17 | padding: 1em 0;
18 | }
19 | .mtc .mtc__clickable {
20 | cursor: pointer;
21 | margin: 0 2px;
22 | }
23 |
24 | .mtc .mtc__collapse-expand-item {
25 | margin: 0 0.6rem;
26 | }
27 |
28 | .mtc .mtc__childless-item {
29 | margin-left: 2rem;
30 | }
31 |
32 | .mtc .mtc__indent {
33 | margin: 0 1.5rem;
34 | }
35 |
36 | .mtc .mtc__moveable {
37 | cursor: move;
38 | }
39 |
40 | .mtc .mtc__item {
41 | cursor: pointer;
42 | }
43 |
44 | .mtc .mtc__header {
45 | line-height: 2rem;
46 | width: 100%;
47 | }
48 |
49 | .mtc .mtc__act-group,
50 | .mtc .mtc__header div {
51 | display: inline-block;
52 | overflow: hidden;
53 | text-overflow: ellipsis;
54 | vertical-align: middle;
55 | }
56 |
57 | .mtc .mtc__header.active {
58 | background: #e0e0e0;
59 | }
60 |
61 | .mtc .mtc__header:focus {
62 | color: blue;
63 | background-color: #cf5c3f;
64 | }
65 |
66 | .mtc .mtc__header a {
67 | float: left;
68 | color: #f2f2f2;
69 | text-align: center;
70 | padding: 14px 16px;
71 | text-decoration: none;
72 | font-size: 17px;
73 | }
74 |
75 | /* .mtc .mtc__branch .mtc__act, */
76 | .mtc .mtc__item .mtc__act-group {
77 | visibility: hidden;
78 | opacity: 0;
79 | transition: visibility 0s, opacity 0.5s linear;
80 | float: right;
81 | display: none;
82 | }
83 | .mtc .mtc__item .mtc__act-group {
84 | float: right;
85 | }
86 | .mtc .mtc__header:hover .mtc__act-group {
87 | visibility: visible;
88 | opacity: 1;
89 | display: inline-block;
90 | }
91 |
92 | .mtc .mtc__act {
93 | font-weight: bold;
94 | cursor: pointer;
95 | display: inline-block;
96 | padding: 0 0.3rem;
97 | }
98 | .mtc .mtc__act i {
99 | font-weight: normal;
100 | cursor: pointer;
101 | display: inline-block;
102 | padding: 0 0.3rem;
103 | }
104 | .mtc .mtc__act:hover {
105 | background: #e0e0e0;
106 | }
107 | /*
108 | .mtc .mtc__branch .mtc__act {
109 | visibility: hidden;
110 | opacity: 0;
111 | transition: visibility 0s, opacity 0.5s linear;
112 | display: none;
113 | } */
114 |
115 | .mtc .mtc__branch:hover .mtc__act {
116 | visibility: visible;
117 | opacity: 1;
118 | display: inline-block;
119 | }
120 |
121 | .mtc ul.mtc__item-body {
122 | padding-left: 0.8rem;
123 | }
124 |
125 | .mtc ul.mtc__item-body > li {
126 | white-space: nowrap;
127 | }
128 |
129 | .mtc span.mtc__item-title {
130 | margin-right: 1rem;
131 | }
132 |
133 | .mtc .mtc__draggable {
134 | cursor: move; /* fallback if grab cursor is unsupported */
135 | cursor: grab;
136 | cursor: -moz-grab;
137 | cursor: -webkit-grab;
138 | }
139 |
140 | /* (Optional) Apply a "closed-hand" cursor during drag operation. */
141 | .mtc .mtc__draggable:active {
142 | cursor: grabbing;
143 | cursor: -moz-grabbing;
144 | cursor: -webkit-grabbing;
145 | }
146 |
147 | .mtc .mtc__as_child {
148 | /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#1e5799+0,2989d8+50,207cca+51,7db9e8+100&0+0,0.5+51,0+100 */
149 | background: -moz-linear-gradient(
150 | top,
151 | rgba(30, 87, 153, 0) 0%,
152 | rgba(41, 137, 216, 0.49) 50%,
153 | rgba(32, 124, 202, 0.5) 51%,
154 | rgba(125, 185, 232, 0) 100%
155 | ); /* FF3.6-15 */
156 | background: -webkit-linear-gradient(
157 | top,
158 | rgba(30, 87, 153, 0) 0%,
159 | rgba(41, 137, 216, 0.49) 50%,
160 | rgba(32, 124, 202, 0.5) 51%,
161 | rgba(125, 185, 232, 0) 100%
162 | ); /* Chrome10-25,Safari5.1-6 */
163 | background: linear-gradient(
164 | to bottom,
165 | rgba(30, 87, 153, 0) 0%,
166 | rgba(41, 137, 216, 0.49) 50%,
167 | rgba(32, 124, 202, 0.5) 51%,
168 | rgba(125, 185, 232, 0) 100%
169 | ); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
170 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#001e5799', endColorstr='#007db9e8',GradientType=0 ); /* IE6-9 */
171 | }
172 |
173 | .mtc .mtc__below {
174 | /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#1e5799+100,1e5799+100,1e5799+100&0+0,0.7+100 */
175 | background: -moz-linear-gradient(top, rgba(30, 87, 153, 0) 0%, rgba(30, 87, 153, 0.7) 100%); /* FF3.6-15 */
176 | background: -webkit-linear-gradient(
177 | top,
178 | rgba(30, 87, 153, 0) 0%,
179 | rgba(30, 87, 153, 0.7) 100%
180 | ); /* Chrome10-25,Safari5.1-6 */
181 | background: linear-gradient(
182 | to bottom,
183 | rgba(30, 87, 153, 0) 0%,
184 | rgba(30, 87, 153, 0.7) 100%
185 | ); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
186 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#001e5799', endColorstr='#b31e5799',GradientType=0 ); /* IE6-9 */
187 | }
188 |
189 | .mtc .mtc__above {
190 | /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#1e5799+0,1e5799+0,1e5799+0&0.7+0,0+100 */
191 | background: -moz-linear-gradient(top, rgba(30, 87, 153, 0.7) 0%, rgba(30, 87, 153, 0) 100%); /* FF3.6-15 */
192 | background: -webkit-linear-gradient(
193 | top,
194 | rgba(30, 87, 153, 0.7) 0%,
195 | rgba(30, 87, 153, 0) 100%
196 | ); /* Chrome10-25,Safari5.1-6 */
197 | background: linear-gradient(
198 | to bottom,
199 | rgba(30, 87, 153, 0.7) 0%,
200 | rgba(30, 87, 153, 0) 100%
201 | ); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
202 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b31e5799', endColorstr='#001e5799',GradientType=0 ); /* IE6-9 */
203 | }
204 |
--------------------------------------------------------------------------------
/packages/example/src/components/tree-view.ts:
--------------------------------------------------------------------------------
1 | import m, { Component } from 'mithril';
2 | import { TreeContainer, ITreeOptions, ITreeItem, uuid4, ITreeItemViewComponent } from 'mithril-tree-component';
3 |
4 | interface IMyTree extends ITreeItem {
5 | id: number | string;
6 | parentId: number | string;
7 | title: string;
8 | description: string;
9 | }
10 |
11 | const isOpen = (() => {
12 | const store: Record = {};
13 | return (id: string, action: 'get' | 'set', value?: boolean) => {
14 | if (action === 'get') {
15 | return store.hasOwnProperty(id) ? store[id] : false;
16 | } else if (typeof value !== 'undefined') {
17 | store[id] = value;
18 | }
19 | };
20 | })();
21 |
22 | export const TreeView = () => {
23 | let selectedId: string | number = 5;
24 |
25 | const data: IMyTree[] = [
26 | {
27 | id: 1,
28 | parentId: 0,
29 | title: 'My id is 1',
30 | description: 'Description of item 1.',
31 | },
32 | {
33 | id: 2,
34 | parentId: 1,
35 | title: 'My id is 2',
36 | description: 'Description of item 2.',
37 | },
38 | {
39 | id: 3,
40 | parentId: 1,
41 | title: 'My id is 3',
42 | description: 'Description of item 3.',
43 | },
44 | {
45 | id: 4,
46 | parentId: 2,
47 | title: 'I have a very long title which should be displayed using ellipses if everything works as expected',
48 | description: 'Description of item 4.',
49 | },
50 | {
51 | id: 5,
52 | parentId: 0,
53 | title: 'My id is 5 - I am not a drop target',
54 | description: 'Items cannot be dropped on me.',
55 | },
56 | {
57 | id: 6,
58 | parentId: 0,
59 | title: 'My id is 6',
60 | description: 'Description of item 6.',
61 | },
62 | {
63 | id: 7,
64 | parentId: 0,
65 | title: 'My id is 7',
66 | description: 'Description of item 7.',
67 | },
68 | {
69 | id: 8,
70 | parentId: 4,
71 | title: 'My id is 8',
72 | description: 'Description of item 8.',
73 | },
74 | ];
75 | const emptyTree: IMyTree[] = [];
76 | const tree = data;
77 | const options = {
78 | logging: true,
79 | id: 'id',
80 | parentId: 'parentId',
81 | // isOpen: "isOpen",
82 | isOpen,
83 | name: 'title',
84 | onToggle: (ti, isExpanded) => console.log(`On toggle: "${ti.title}" is ${isExpanded ? '' : 'not '}expanded.`),
85 | onSelect: (ti, isSelected) => {
86 | selectedId = ti.id;
87 | console.log(`On ${isSelected ? 'select' : 'unselect'}: ${ti.title}`);
88 | },
89 | onBeforeCreate: (ti) => console.log(`On before create ${ti.title}`),
90 | onCreate: (ti) => {
91 | console.log(`On create ${ti.title}`);
92 | // selectedId = ti.id;
93 | },
94 | onBeforeDelete: (ti) => console.log(`On before delete ${ti.title}`),
95 | onDelete: (ti) => console.log(`On delete ${ti.title}`),
96 | onBeforeUpdate: (ti, action, newParent) => {
97 | console.log(`On before ${action} update ${ti.title} to ${newParent ? newParent.title : ''}.`);
98 | const result = newParent && newParent.id === 5 ? false : true;
99 | console.warn(result ? 'Drop allowed' : 'Drop not allowed');
100 | return result;
101 | },
102 | onUpdate: (ti) => console.log(`On update ${ti.title}`),
103 | create: (parent?: IMyTree) => {
104 | const item = {} as IMyTree;
105 | item.id = uuid4();
106 | if (parent) {
107 | item.parentId = parent.id;
108 | }
109 | item.title = `Created at ${new Date().toLocaleTimeString()}`;
110 | return item as IMyTree;
111 | },
112 | editable: {
113 | canCreate: false,
114 | canDelete: false,
115 | canUpdate: false,
116 | canDeleteParent: false,
117 | },
118 | } as Partial;
119 |
120 | const optionsCRUD = {
121 | ...options,
122 | placeholder: 'Create a new tree item',
123 | editable: {
124 | canCreate: true,
125 | canDelete: true,
126 | canUpdate: true,
127 | canDeleteParent: false,
128 | },
129 | };
130 |
131 | const optionsOwnView = {
132 | ...options,
133 | maxDepth: 3,
134 | treeItemView: {
135 | view: ({
136 | attrs: {
137 | depth,
138 | treeItem: { title, description },
139 | width,
140 | },
141 | }) =>
142 | m(
143 | 'div',
144 | { style: `max-width: ${width - 32}px` },
145 | m('div', { style: 'font-weight: bold; margin-right: 1rem' }, `Depth ${depth}: ${title}`),
146 | m('div', { style: 'font-style: italic;' }, description || '...')
147 | ),
148 | } as Component,
149 | } as ITreeOptions;
150 |
151 | const optionsCRUDisOpen = {
152 | ...options,
153 | isOpen: undefined,
154 | editable: {
155 | canCreate: true,
156 | canDelete: true,
157 | canUpdate: true,
158 | canDeleteParent: false,
159 | },
160 | } as ITreeOptions;
161 |
162 | const optionsAsync = {
163 | ...options,
164 | isOpen: undefined,
165 | editable: {
166 | canCreate: true,
167 | canDelete: true,
168 | canUpdate: true,
169 | canDeleteParent: false,
170 | },
171 | onBeforeDelete: (ti) => {
172 | return new Promise((resolve) => {
173 | setTimeout(() => {
174 | console.log(`On before delete ${ti.title}`);
175 | resolve(true);
176 | }, 3000);
177 | });
178 | },
179 | onBeforeCreate: (ti) => {
180 | return new Promise((resolve) => {
181 | setTimeout(() => {
182 | console.log(`On before create ${ti.title}`);
183 | resolve(true);
184 | }, 3000);
185 | });
186 | },
187 | onDelete: (ti) => {
188 | console.log(`On delete ${ti.title}`);
189 | m.redraw(); // As the delete action is done async, force a redraw when done.
190 | },
191 | onCreate: (ti) => {
192 | console.log(`On create ${ti.title}`);
193 | m.redraw(); // As the delete action is done async, force a redraw when done.
194 | },
195 | } as ITreeOptions;
196 |
197 | return {
198 | view: () => {
199 | // console.log('Drawing the view...');
200 | return m('.row', [
201 | m('.col.s6', [
202 | m('h3', 'CRUD'),
203 | m(TreeContainer, { tree, options: optionsCRUD, selectedId }),
204 | m('h3', 'Readonly'),
205 | m(TreeContainer, { tree, options }),
206 | m('h3', 'Own view, maxDepth 3'),
207 | m(TreeContainer, { tree, options: optionsOwnView }),
208 | m('h3', 'CRUD, isOpen undefined'),
209 | m(TreeContainer, { tree, options: optionsCRUDisOpen }),
210 | m('h3', 'CRUD, async. create and delete'),
211 | m(TreeContainer, { tree, options: optionsAsync }),
212 | m('h3', 'CRUD, empty tree'),
213 | m(TreeContainer, { tree: emptyTree, options: optionsCRUD }),
214 | ]),
215 | m('.col.s6', [
216 | m(
217 | 'button.waves-effect.waves-light.btn',
218 | {
219 | onclick: () => {
220 | setTimeout(() => {
221 | data[0].title = `Updated at ${new Date().toTimeString()}`;
222 | m.redraw();
223 | }, 1000);
224 | },
225 | },
226 | 'Update first node with timeout'
227 | ),
228 | m('h3', 'Tree data'),
229 | m('pre', m('code', JSON.stringify(tree, null, 2))),
230 | ]),
231 | ]);
232 | },
233 | };
234 | };
235 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/README.md:
--------------------------------------------------------------------------------
1 | # mithril-tree-component
2 |
3 | A tree component for [Mithril](https://mithril.js.org) that supports drag-and-drop, as well as selecting, creating and deleting items. You can play with it [here](https://erikvullings.github.io/mithril-tree-component/#!/).
4 |
5 | ## Functionality
6 |
7 | - Drag-and-drop to move items (if `editable.canUpdate` is true).
8 | - Create and delete tree items (if `editable.canDelete` and `editable.canDeleteParent` is true).
9 | - Configurable properties for:
10 | - `id` property: unique id of the item.
11 | - `parentId` property: id of the parent.
12 | - `name` property: display title. Alternatively, provide your own component.
13 | - `maxDepth`: when specified, and editable.canCreate is true, do not add children that would exceed this depth, where depth is 0 for root items, 1 for children, etc.
14 | - `create`: function to be used to add your own TreeItem creation logic.
15 | - `selectedId`: can be set in the `TreeContainer` to select a tree item automatically.
16 | - `isOpen`: to indicate whether the tree should show the children. By default, when nothing is set, the `treeItem.isOpen` property is used. If `isOpen` is a string, `treeItem[isOpen]` is used instead. In case `isOpen` is `undefined`, the open/close state is maintained internally. Finally, you can also use a function `(id: string, action: 'get' | 'set', value?: boolean) => boolean | void`, in which case you can maintain the open/close state externally, e.g. for synchronization between different components:
17 |
18 | ```ts
19 | const isOpen = (() => {
20 | const store: Record = {};
21 | return (id: string, action: 'get' | 'set', value?: boolean) => {
22 | if (action === 'get') {
23 | return store.hasOwnProperty(id) ? store[id] : false;
24 | } else if (typeof value !== 'undefined') {
25 | store[id] = value;
26 | }
27 | };
28 | })();
29 | ```
30 |
31 | - Callback events:
32 | - `onSelect`: when a tree item is selected.
33 | - `onToggle`: when a tree item is expanded or closed.
34 | - `onBefore`[Create | Update | Delete]: can be used to intercept (and block) tree item actions. If the onBeforeX call returns false, the action is stopped.
35 | - `on[Create | Update | Delete]`: when the creation is done.
36 | - When using async functions or promises, please make sure to call `m.redraw()` when you are done.
37 |
38 | This repository contains two projects:
39 |
40 | - An example project, showcasing the usage of the component.
41 | - The mithril-tree-component itself.
42 |
43 | ## Changes
44 |
45 | ### 0.7.1 No breaking changes
46 |
47 | - `selectedId`: can be set in the `TreeContainer` to select a tree item automatically. In this case, you need to handle the `onSelect` event yourself in order to select the item, as in the example.
48 | - When creating a new item, this item is selected automatically.
49 |
50 | ### 0.7.0 No breaking changes
51 |
52 | - Added `onToggle` callback event.
53 | - Added option to maintain the open/close tree state externally by setting the `isOpen` property to a function.
54 |
55 | ### 0.6.4 No breaking changes
56 |
57 | - Fixed: Do not crash when `onCreate` is not defined.
58 |
59 | ### 0.6.1 No breaking changes
60 |
61 | - Fixed `onBeforeUpdate` checks, so the code using this library can determine whether a drop is valid.
62 |
63 | ### 0.6.0 No breaking changes
64 |
65 | - Improved drag-n-drop behaviour: drop above or below an item to create a sibling, drop on an item to create a child. Also the cursor now indicates correctly `no-drop` zones.
66 | - Long lines use `text-overflow: ellipsis`.
67 | - Adding a child is now done by clicking its parent '+' sign (instead of underneath). The advantage is that the tree does not become longer anymore.
68 | - The add '+' and delete 'x' action buttons are swapped, so when adding multiple children, the add symbol keeps the same position.
69 |
70 | ### 0.5.2
71 |
72 | - Publishing two libraries: `mithril-tree-component.js` and `mithril-tree-component.mjs`, the latter using ES modules.
73 | - Integrated `css` inside the component, so you do not need to load a separate stylesheet.
74 | - Using BEM naming convention for the `css`, and prefix every class with `mtc-` to make their names more unique.
75 | - Improved usability:
76 | - display a placeholder for the empty tree.
77 | - only show the action buttons when hovering, and only then, create vertical space for them (so the tree is more continuous).
78 |
79 | ### 0.4.0
80 |
81 | This version of the tree component:
82 |
83 | - Does no longer require a tree-layout: input is a flat Array of items, and the `children` are resolved by processing all items based on the `parentId` property. So the `children` property is no longer used.
84 | - Does not mutate the tree components anymore if you keep the `isOpen` property undefined.
85 |
86 | ### 0.3.6
87 |
88 | - maxDepth: can be set to limit the ability to create children.
89 | - treeItemView: to provide your own view component
90 | - open or close state when displaying children: can be maintained internally, so different components do not share open/close state.
91 |
92 | ## Usage
93 |
94 | ```bash
95 | npm i mithril-tree-component
96 | ```
97 |
98 | From the [example project](../example). There you can also find some CSS styles.
99 |
100 | ```ts
101 | import m from 'mithril';
102 | import { unflatten } from '../utils';
103 | import { TreeContainer, ITreeOptions, ITreeItem, uuid4 } from 'mithril-tree-component';
104 |
105 | interface IMyTree extends ITreeItem {
106 | id: number | string;
107 | parentId: number | string;
108 | title: string;
109 | }
110 |
111 | export const TreeView = () => {
112 | const data: IMyTree[] = [
113 | { id: 1, parentId: 0, title: 'My id is 1' },
114 | { id: 2, parentId: 1, title: 'My id is 2' },
115 | { id: 3, parentId: 1, title: 'My id is 3' },
116 | { id: 4, parentId: 2, title: 'My id is 4' },
117 | { id: 5, parentId: 0, title: 'My id is 5' },
118 | { id: 6, parentId: 0, title: 'My id is 6' },
119 | { id: 7, parentId: 4, title: 'My id is 7' },
120 | ];
121 | const tree = unflatten(data);
122 | const options = {
123 | id: 'id',
124 | parentId: 'parentId',
125 | isOpen: 'isOpen',
126 | name: 'title',
127 | onSelect: (ti, isSelected) => console.log(`On ${isSelected ? 'select' : 'unselect'}: ${ti.title}`),
128 | onBeforeCreate: ti => console.log(`On before create ${ti.title}`),
129 | onCreate: ti => console.log(`On create ${ti.title}`),
130 | onBeforeDelete: ti => console.log(`On before delete ${ti.title}`),
131 | onDelete: ti => console.log(`On delete ${ti.title}`),
132 | onBeforeUpdate: (ti, action, newParent) =>
133 | console.log(`On before ${action} update ${ti.title} to ${newParent ? newParent.title : ''}.`),
134 | onUpdate: ti => console.log(`On update ${ti.title}`),
135 | create: (parent?: IMyTree) => {
136 | const item = {} as IMyTree;
137 | item.id = uuid4();
138 | if (parent) {
139 | item.parentId = parent.id;
140 | }
141 | item.title = `Created at ${new Date().toLocaleTimeString()}`;
142 | return item as ITreeItem;
143 | },
144 | editable: { canCreate: true, canDelete: true, canUpdate: true, canDeleteParent: false },
145 | } as ITreeOptions;
146 | return {
147 | view: () =>
148 | m('.row', [
149 | m('.col.s6', [m('h3', 'Mithril-tree-component'), m(TreeContainer, { tree, options })]),
150 | m('.col.s6', [m('h3', 'Tree data'), m('pre', m('code', JSON.stringify(tree, null, 2)))]),
151 | ]),
152 | };
153 | };
154 | ```
155 |
156 | ## Build instructions
157 |
158 | This repository uses [Lerna](https://lernajs.io) to manage multiple projects (packages) in one repository. To compile and run both packages, proceed as follows (from the root):
159 |
160 | ```bash
161 | npm i
162 | npm start
163 | ```
164 |
--------------------------------------------------------------------------------
/packages/mithril-tree-component/src/tree-container.ts:
--------------------------------------------------------------------------------
1 | import './styles/tree-container.css';
2 | import m, { FactoryComponent, Attributes, VnodeDOM } from 'mithril';
3 | import { TreeItem, TreeItemIdPrefix } from './tree-item';
4 | import { IInternalTreeOptions } from './models/tree-options';
5 | import { ITreeItem, ITreeOptions, TreeItemUpdateAction } from './models';
6 | import { uuid4, TreeButton, move } from './utils';
7 | import { ITreeState } from './models/tree-state';
8 |
9 | export let log: (...args: any[]) => void = () => undefined;
10 |
11 | export const TreeContainer: FactoryComponent<{
12 | tree: ITreeItem[];
13 | options: Partial;
14 | /** Optional id of the tree item that is selected */
15 | selectedId?: string | number;
16 | }> = () => {
17 | const state = {
18 | selectedId: '',
19 | dragId: '',
20 | } as ITreeState;
21 |
22 | const setDefaultOptions = (options: Partial) => {
23 | // const { options } = state;
24 | const wrapper = (
25 | defaultFn: (treeItem: ITreeItem, action?: TreeItemUpdateAction, newParent?: ITreeItem) => void,
26 | beforeFn?: (
27 | treeItem: ITreeItem,
28 | action?: TreeItemUpdateAction,
29 | newParent?: ITreeItem
30 | ) => boolean | void | Promise,
31 | afterFn?: (treeItem: ITreeItem, action?: TreeItemUpdateAction, newParent?: ITreeItem) => void | Promise
32 | ) => async (treeItem: ITreeItem, action?: TreeItemUpdateAction, newParent?: ITreeItem) => {
33 | if (beforeFn) {
34 | const before = beforeFn(treeItem, action, newParent);
35 | const isAsync = typeof before !== 'boolean';
36 | const result = isAsync ? await before : before;
37 | if (result === false) {
38 | return;
39 | }
40 | }
41 | await Promise.resolve(defaultFn(treeItem, action, newParent));
42 | if (afterFn) {
43 | await Promise.resolve(afterFn(treeItem, action, newParent));
44 | }
45 | };
46 | const opts = {
47 | id: 'id',
48 | parentId: 'parentId',
49 | name: 'name',
50 | isOpen: 'isOpen',
51 | maxDepth: Number.MAX_SAFE_INTEGER,
52 | multipleRoots: true,
53 | logging: false,
54 | editable: {
55 | canCreate: false,
56 | canDelete: false,
57 | canUpdate: false,
58 | canDeleteParent: false,
59 | },
60 | placeholder: 'Create your first item',
61 | ...options,
62 | } as IInternalTreeOptions;
63 |
64 | if (opts.logging) {
65 | log = console.log;
66 | }
67 | if (!opts.isOpen) {
68 | opts.isOpen = (() => {
69 | const store: Record = {};
70 | return (id: string, action: 'get' | 'set', value?: boolean): boolean | void => {
71 | if (action === 'get') {
72 | return store.hasOwnProperty(id) ? store[id] : false;
73 | } else if (typeof value !== 'undefined') {
74 | store[id] = value;
75 | }
76 | };
77 | })();
78 | }
79 | const { id, parentId, name, isOpen } = opts;
80 |
81 | /** Recursively find a tree item */
82 | const find = (tId: string | number = '', partialTree = state.tree) => {
83 | if (!tId || !partialTree) {
84 | return undefined;
85 | }
86 | let found: ITreeItem | undefined;
87 | partialTree.some((treeItem) => {
88 | if (treeItem[id] === tId) {
89 | found = treeItem;
90 | return true;
91 | }
92 | return false;
93 | });
94 | return found;
95 | };
96 |
97 | /** Recursively delete a tree item and all its children */
98 | const deleteTreeItem = (tId: string | number = '', partialTree = state.tree) => {
99 | if (!tId || !partialTree) {
100 | return false;
101 | }
102 | let found = false;
103 | partialTree.some((treeItem, i) => {
104 | if (treeItem[id] === tId) {
105 | partialTree.splice(i, 1);
106 | findChildren(treeItem).forEach((ti) => deleteTreeItem(ti[id]));
107 | found = true;
108 | return true;
109 | }
110 | return false;
111 | });
112 | return found;
113 | };
114 |
115 | /** Recursively update a tree item and all its children */
116 | const updateTreeItem = (updatedTreeItem: ITreeItem, partialTree = state.tree) => {
117 | if (!partialTree) {
118 | return false;
119 | }
120 | let found = false;
121 | partialTree.some((treeItem, i) => {
122 | if (treeItem[id] === updatedTreeItem[id]) {
123 | partialTree[i] = updatedTreeItem;
124 | found = true;
125 | return true;
126 | }
127 | return false;
128 | });
129 | return found;
130 | };
131 |
132 | const depth = (treeItem: ITreeItem, curDepth = 0): number => {
133 | const pId = treeItem[parentId];
134 | return pId ? depth(find(pId) as ITreeItem, curDepth + 1) : curDepth;
135 | };
136 |
137 | const onCreate = wrapper(
138 | (ti: ITreeItem) => {
139 | if (state.tree) {
140 | state.tree.push(ti);
141 | }
142 | },
143 | opts.onBeforeCreate,
144 | (treeItem: ITreeItem) => {
145 | if (opts.onCreate) {
146 | opts.onCreate(treeItem);
147 | }
148 | onSelect(treeItem, true);
149 | }
150 | );
151 |
152 | /** Create a new tree item. */
153 | const createTreeItem = (pId: string | number = '', width: number) => {
154 | const create = (w: number) => {
155 | if (options.create) {
156 | const parent = find(pId);
157 | const d = parent ? depth(parent) : -1;
158 | return options.create(parent, d, w);
159 | }
160 | const item = {} as ITreeItem;
161 | item[id] = uuid4();
162 | item[parentId] = pId;
163 | item[name] = 'New...';
164 | return item;
165 | };
166 | onCreate(create(width));
167 | };
168 |
169 | const onDelete = wrapper((ti: ITreeItem) => deleteTreeItem(ti[id]), opts.onBeforeDelete, opts.onDelete);
170 |
171 | const onUpdate = wrapper(
172 | (ti: ITreeItem, _: TreeItemUpdateAction = 'edit', __?: ITreeItem) => updateTreeItem(ti),
173 | opts.onBeforeUpdate,
174 | opts.onUpdate
175 | );
176 |
177 | const onSelect = (ti: ITreeItem, isSelected: boolean) => {
178 | state.selectedId = isSelected ? ti[id] : '';
179 | if (opts.onSelect) {
180 | opts.onSelect(ti, isSelected);
181 | }
182 | };
183 |
184 | const onToggle = (ti: ITreeItem, isExpanded: boolean) => {
185 | if (opts.onToggle) {
186 | opts.onToggle(ti, isExpanded);
187 | }
188 | };
189 |
190 | const treeItemView = opts.treeItemView || {
191 | view: ({ attrs: { treeItem } }) => treeItem[name],
192 | };
193 |
194 | /** The drop location indicates the new position of the dropped element: above, below or as a child */
195 | const computeDropLocation = (target: HTMLElement, ev: DragEvent) => {
196 | const { top, height } = target.getBoundingClientRect();
197 | const y = ev.clientY - top;
198 | const deltaZone = height / 3;
199 | return y < deltaZone ? 'above' : y < 2 * deltaZone ? 'as_child' : 'below';
200 | };
201 |
202 | const convertId = (cid: string | number) => (isNaN(+cid) ? cid : +cid);
203 |
204 | const dndTreeItems = (target: HTMLElement, ev: DragEvent) => {
205 | if (ev.dataTransfer) {
206 | const sourceId = convertId(state.dragId || ev.dataTransfer.getData('text').replace(TreeItemIdPrefix, ''));
207 | const targetId = convertId((findId(target) || '').replace(TreeItemIdPrefix, ''));
208 | const tiSource = find(sourceId);
209 | const tiTarget = find(targetId);
210 | return { tiSource, tiTarget, sourceId, targetId };
211 | }
212 | return {
213 | tiSource: undefined,
214 | tiTarget: undefined,
215 | sourceId: undefined,
216 | targetId: undefined,
217 | };
218 | };
219 |
220 | const isValidTarget = (
221 | target: HTMLElement,
222 | ev: DragEvent,
223 | dropLocation: 'above' | 'below' | 'as_child' = computeDropLocation(target, ev)
224 | ) => {
225 | const { sourceId, targetId, tiSource, tiTarget } = dndTreeItems(target, ev);
226 | const parent = dropLocation === 'as_child' || !tiTarget ? tiTarget : find(tiTarget[parentId]);
227 | return (
228 | targetId !== sourceId &&
229 | (!opts.onBeforeUpdate || (tiSource && opts.onBeforeUpdate(tiSource, 'move', parent) === true))
230 | );
231 | };
232 |
233 | const dragOpts = {
234 | ondrop: (ev: DragEvent) => {
235 | if (!ev.dataTransfer || !ev.target) {
236 | return false;
237 | }
238 | const target = ev.target as HTMLElement;
239 | const parent = findParent(target);
240 | if (!parent) {
241 | return;
242 | }
243 | parent.classList.remove('mtc__above', 'mtc__below', 'mtc__as_child');
244 | state.isDragging = false;
245 | ev.preventDefault(); // do not open a link
246 | const { sourceId, targetId, tiSource, tiTarget } = dndTreeItems(target, ev);
247 | const dropLocation = computeDropLocation(parent, ev);
248 | if (!isValidTarget(target, ev, dropLocation)) {
249 | return false;
250 | }
251 | log(`Dropping ${sourceId} ${dropLocation} ${targetId}`);
252 | if (tiSource && tiTarget) {
253 | tiSource[parentId] = tiTarget[dropLocation === 'as_child' ? id : parentId];
254 | if (state.tree) {
255 | const sourceIndex = state.tree && state.tree.indexOf(tiSource);
256 | const targetIndex = state.tree && state.tree.indexOf(tiTarget);
257 | const newIndex = Math.max(
258 | 0,
259 | dropLocation === 'above'
260 | ? targetIndex - 1
261 | : dropLocation === 'below'
262 | ? targetIndex + 1
263 | : state.tree.length - 1
264 | );
265 | move(state.tree, sourceIndex, newIndex);
266 | }
267 | if (dropLocation === 'as_child' && isOpen) {
268 | if (typeof isOpen === 'function') {
269 | isOpen(tiTarget[id], 'set', true);
270 | } else {
271 | tiTarget[isOpen] = true;
272 | }
273 | }
274 | if (opts.onUpdate) {
275 | opts.onUpdate(tiSource, 'move', tiTarget);
276 | }
277 | return true;
278 | } else {
279 | return false;
280 | }
281 | },
282 | ondragover: (ev: DragEvent) => {
283 | (ev as any).redraw = false;
284 | const target = ev.target as HTMLElement;
285 | const parent = findParent(target);
286 | if (parent) {
287 | parent.classList.remove('mtc__above', 'mtc__below', 'mtc__as_child');
288 | const dropLocation = computeDropLocation(parent, ev);
289 | if (isValidTarget(target, ev, dropLocation)) {
290 | ev.preventDefault();
291 | parent.classList.add('mtc__' + dropLocation);
292 | target.style.cursor = isValidTarget(target, ev, dropLocation) ? 'inherit' : 'no_drop';
293 | }
294 | } else {
295 | target.style.cursor = 'no_drop';
296 | }
297 | },
298 | // ondragenter: (ev: DragEvent) => {
299 | // const target = ev.target as HTMLElement;
300 | // const { sourceId, targetId, tiSource, tiTarget } = dndTreeItems(target, ev);
301 | // const disallowDrop =
302 | // targetId === sourceId ||
303 | // (tiSource && opts.onBeforeUpdate && opts.onBeforeUpdate(tiSource, 'move', tiTarget) === false);
304 | // target.style.cursor = disallowDrop ? 'no-drop' : '';
305 | // },
306 | ondragleave: (ev: DragEvent) => {
307 | const target = ev.target as HTMLElement;
308 | if (target && target.style) {
309 | target.style.cursor = 'inherit';
310 | }
311 | const parent = findParent(target);
312 | if (parent) {
313 | parent.classList.remove('mtc__above', 'mtc__below', 'mtc__as_child');
314 | }
315 | },
316 | ondragstart: (ev: DragEvent) => {
317 | const target = ev.target;
318 | (ev as any).redraw = false;
319 | state.dragId = '';
320 | if (target && ev.dataTransfer) {
321 | state.isDragging = true;
322 | ev.dataTransfer.setData('text', (target as any).id);
323 | ev.dataTransfer.effectAllowed = 'move';
324 | state.dragId = (target as any).id.replace(TreeItemIdPrefix, '');
325 | log('Drag start: ' + ev.dataTransfer.getData('text'));
326 | }
327 | },
328 | } as Attributes;
329 |
330 | const hasChildren = (treeItem: ITreeItem) => state.tree && state.tree.some((ti) => ti[parentId] === treeItem[id]);
331 |
332 | const addChildren = (treeItem: ITreeItem, width: number) => {
333 | createTreeItem(treeItem[id], width);
334 | };
335 |
336 | const isExpanded = (treeItem: ITreeItem) =>
337 | hasChildren(treeItem) && (typeof isOpen === 'function' ? isOpen(treeItem[id], 'get') : treeItem[isOpen]);
338 |
339 | const findChildren = (treeItem: ITreeItem) =>
340 | state.tree ? state.tree.filter((ti) => ti[parentId] === treeItem[id]) : [];
341 |
342 | return {
343 | dragOptions: dragOpts,
344 | options: {
345 | ...opts,
346 | treeItemView,
347 | onSelect,
348 | onToggle,
349 | onCreate,
350 | onDelete,
351 | onUpdate,
352 | _findChildren: findChildren,
353 | _find: find,
354 | _deleteItem: deleteTreeItem,
355 | _createItem: createTreeItem,
356 | _hasChildren: hasChildren,
357 | _addChildren: addChildren,
358 | _depth: depth,
359 | _isExpanded: isExpanded,
360 | } as IInternalTreeOptions,
361 | };
362 | };
363 |
364 | /** Find the ID of the first parent element. */
365 | const findId = (el: HTMLElement | null): string | null =>
366 | el ? (el.id ? el.id : el.parentElement ? findId(el.parentElement) : null) : null;
367 |
368 | /** Find the ID of the first parent element. */
369 | const findParent = (el: HTMLElement | null): HTMLElement | null =>
370 | el ? (el.id ? el : el.parentElement ? findParent(el.parentElement) : null) : null;
371 |
372 | const setTopWidth:
373 | | ((this: {}, vnode: VnodeDOM<{ tree: ITreeItem[]; options: Partial }, {}>) => any)
374 | | undefined = ({ dom }) => {
375 | state.width = dom.clientWidth - 64;
376 | };
377 |
378 | return {
379 | oninit: ({ attrs: { options: treeOptions } }) => {
380 | const { options, dragOptions } = setDefaultOptions(treeOptions);
381 | state.options = options;
382 | state.dragOptions = dragOptions;
383 | state.parentId = options.parentId;
384 | },
385 | onupdate: setTopWidth,
386 | oncreate: setTopWidth,
387 | view: ({ attrs: { tree, selectedId } }) => {
388 | state.tree = tree;
389 | const { options, dragOptions, parentId, width } = state;
390 | if (!state.tree || !options || !dragOptions) {
391 | return undefined;
392 | }
393 | const {
394 | _createItem,
395 | placeholder,
396 | editable: { canUpdate, canCreate },
397 | id,
398 | multipleRoots,
399 | } = options;
400 | const isEmpty = state.tree.length === 0;
401 | return m(
402 | '.mtc.mtc__container',
403 | isEmpty
404 | ? m(
405 | '.mtc__empty',
406 | m(
407 | '.mtc__act.mtc__header',
408 | {
409 | onclick: () => _createItem(),
410 | },
411 | [m('div', '✚'), m('i', placeholder)]
412 | )
413 | )
414 | : m(
415 | `[draggable=${canUpdate}]`,
416 | { ...dragOptions },
417 | m('ul.mtc__branch', [
418 | ...state.tree
419 | .filter((item) => !item[parentId])
420 | .map((item) =>
421 | m(TreeItem, {
422 | item,
423 | width,
424 | options,
425 | dragOptions,
426 | selectedId: selectedId || state.selectedId,
427 | key: item[id],
428 | })
429 | ),
430 | canCreate && multipleRoots
431 | ? m(
432 | 'li.mtc__new_root',
433 | { key: -1 },
434 | m(
435 | '.mtc__item.mtc__clickable',
436 | m(
437 | '.mtc__indent',
438 | m(TreeButton, {
439 | buttonName: 'create',
440 | onclick: () => _createItem(),
441 | })
442 | )
443 | )
444 | )
445 | : m.fragment({ key: -1 }, ''),
446 | ])
447 | )
448 | );
449 | },
450 | };
451 | };
452 |
--------------------------------------------------------------------------------
/docs/app.f6c559f6.js:
--------------------------------------------------------------------------------
1 | parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c0&&(l.className=i.join(" ")),o[e]={tag:n,attrs:l}}function u(e,n){var r=n.attrs,o=t.normalizeChildren(n.children),a=i.call(r,"class"),u=a?r.class:r.className;if(n.tag=e.tag,n.attrs=null,n.children=void 0,!l(e.attrs)&&!l(r)){var s={};for(var f in r)i.call(r,f)&&(s[f]=r[f]);r=s}for(var f in e.attrs)i.call(e.attrs,f)&&"className"!==f&&!i.call(r,f)&&(r[f]=e.attrs[f]);for(var f in null==u&&null==e.attrs.className||(r.className=null!=u?null!=e.attrs.className?String(e.attrs.className)+" "+String(u):u:null!=e.attrs.className?e.attrs.className:null),a&&(r.class=null),r)if(i.call(r,f)&&"key"!==f){n.attrs=r;break}return Array.isArray(o)&&1===o.length&&null!=o[0]&&"#"===o[0].tag?n.text=o[0].children:n.children=o,n}function s(e){if(null==e||"string"!=typeof e&&"function"!=typeof e&&"function"!=typeof e.view)throw Error("The selector must be either a string or a component.");var r=n.apply(1,arguments);return"string"==typeof e&&(r.children=t.normalizeChildren(r.children),"["!==e)?u(o[e]||a(e),r):(r.tag=e,r)}s.trust=function(e){return null==e&&(e=""),t("<",void 0,void 0,e,void 0,void 0)},s.fragment=function(){var e=n.apply(0,arguments);return e.tag="[",e.children=t.normalizeChildren(e.children),e};var f=function(){return s.apply(this,arguments)};if(f.m=s,f.trust=s.trust,f.fragment=s.fragment,(c=function(e){if(!(this instanceof c))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=u(n,!0),i=u(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function u(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(l){r(l)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(o){r&&r(o)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new c(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},c.prototype.catch=function(e){return this.then(null,e)},c.prototype.finally=function(e){return this.then(function(t){return c.resolve(e()).then(function(){return t})},function(t){return c.resolve(e()).then(function(){return c.reject(t)})})},c.resolve=function(e){return e instanceof c?e:new c(function(t){t(e)})},c.reject=function(e){return new c(function(t,n){n(e)})},c.all=function(e){return new c(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(a){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(a){o(a)}},u&&null!=s?c.send(s):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}},v=p(window,c),h=function(e){var n,r=e.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function s(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function p(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)x(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&z>=l;)if(w=t[C],k=n[z],null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&v(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}for(;C>=a&&z>=l;)if(d=t[a],p=n[l],null==d)a++;else if(null==p)l++;else{if(d.key!==p.key)break;a++,l++,d!==p&&v(e,d,p,r,y(t,a,o),i)}for(;C>=a&&z>=l;){if(null==d)a++;else if(null==p)l++;else if(null==w)C--;else if(null==k)z--;else{if(l===z)break;if(d.key!==k.key||w.key!==p.key)break;S=y(t,a,o),g(e,m(w),S),w!==p&&v(e,w,p,r,S,i),++l<=--z&&g(e,m(d),o),d!==k&&v(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[z],d=t[a],p=n[l]}for(;C>=a&&z>=l;){if(null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&v(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}w=t[C],k=n[z]}if(l>z)x(t,a,C+1);else if(a>C)s(e,n,l,z+1,r,o,i);else{var E,j,P=o,A=z-l+1,N=new Array(A),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==E&&(E=h(t,a,C+1)),null!=(k=n[$])){var R=E[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(N)).length-1,$=z;$>=l;$--)p=n[$],-1===N[$-l]?f(e,p,r,i,o):j[O]===$-l?O--:g(e,m(p),o),null!=p.dom&&(o=n[$].dom);else for($=z;$>=l;$--)p=n[$],-1===N[$-l]&&f(e,p,r,i,o),null!=p.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&x(t,l,t.length),n.length>L&&s(e,n,l,n.length,r,o,i)}}}function v(e,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&T(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(e,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){p(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var E=/[A-Z]/g;function j(e){return"-"+e.toLowerCase()}function P(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(E,j)}function A(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(P(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(P(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(P(r))}}function N(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new N,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return N.prototype=Object.create(null),N.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(e,n){if(!e)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=e.namespaceURI;null==e.vnodes&&(e.textContent=""),n=t.normalizeChildren(Array.isArray(n)?n:[n]),p(e,e.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),e.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var f=0;f-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=b(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var s=b(e.slice(o+1));for(var u in s)n[u]=s[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var f=d(a);f&&(t+="?"+f);var c=d(u);if(c&&(t+="#"+c),n){var p=o?o.state:null,v=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(p,v,l.prefix+t):e.history.pushState(p,v,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function s(){var t=l.getPath(),n={},r=i(t,n,n),s=e.history.state;if(null!=s)for(var f in s)n[f]=s[f];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i0&&a[a.length-1])&&(6===i[0]||2===i[0])){o=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]li{white-space:nowrap}.mtc span.mtc__item-title{margin-right:1rem}.mtc .mtc__draggable{cursor:move;cursor:grab;cursor:-webkit-grab}.mtc .mtc__draggable:active{cursor:grabbing;cursor:-webkit-grabbing}.mtc .mtc__as_child{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),color-stop(50%,rgba(41,137,216,.49)),color-stop(51%,rgba(32,124,202,.5)),to(rgba(125,185,232,0)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(41,137,216,.49) 50%,rgba(32,124,202,.5) 51%,rgba(125,185,232,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(0, 125, 185, 0.9098)",GradientType=0)}.mtc .mtc__below{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),to(rgba(30,87,153,.7)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(30,87,153,.7));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(179, 30, 87, 0.6)",GradientType=0)}.mtc .mtc__above{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,.7)),to(rgba(30,87,153,0)));background:linear-gradient(180deg,rgba(30,87,153,.7) 0,rgba(30,87,153,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(179, 30, 87, 0.6)",endColorstr="rgba(0, 30, 87, 0.6)",GradientType=0)}');var c="tree-item-",l=function(){var t={isOpen:!1};return{oninit:function(e){var r=e.attrs.options,n=r.isOpen,a=r._hasChildren;t.toggle=function(e){a(e)&&(n?e[n]=!e[n]:t.isOpen=!t.isOpen)},t.open=function(e,r){r||(n?e[n]=!0:t.isOpen||(t.isOpen=!0))}},view:function(r){var n=r.attrs,a=n.item,i=n.options,c=n.dragOptions,d=n.selectedId,u=n.width,s=i.id,f=i.treeItemView,m=i._findChildren,p=i._isExpanded,_=i._addChildren,g=i._hasChildren,b=i._depth,v=i.onSelect,h=i.onDelete,x=i.editable,y=x.canUpdate,w=x.canCreate,I=x.canDelete,k=x.canDeleteParent,O=i.maxDepth,T=t.toggle,C=t.open,D=t.isOpen,S=p(a,D),E=g(a),N=b(a);return(0,e.default)("li"+(y?".mtc__draggable":"")+"[id=tree-item-"+a[s]+"][draggable="+y+"]",c,[(0,e.default)(".mtc__item",{onclick:function(e){e.stopPropagation(),v(a,d!==a[s])}},[(0,e.default)(".mtc__header.mtc__clearfix",{class:d===a[s]?"active":""},[E?(0,e.default)(o,{buttonName:S?"expand_less":"expand_more",onclick:function(){return T(a)}}):void 0,(0,e.default)(".mtc__item-title",{class:(y?"mtc__moveable":"")+" "+(E?"":"mtc__childless-item"),style:"max-width: "+u+"px"},(0,e.default)(f,{treeItem:a,depth:N,width:u}))],(0,e.default)(".mtc__act-group",[!I||!k&&E?"":(0,e.default)(o,{buttonName:"delete",onclick:function(){return h(a)}}),w&&N0&&(l.className=i.join(" ")),o[e]={tag:n,attrs:l}}function u(e,n){var r=n.attrs,o=t.normalizeChildren(n.children),a=i.call(r,"class"),u=a?r.class:r.className;if(n.tag=e.tag,n.attrs=null,n.children=void 0,!l(e.attrs)&&!l(r)){var s={};for(var f in r)i.call(r,f)&&(s[f]=r[f]);r=s}for(var f in e.attrs)i.call(e.attrs,f)&&"className"!==f&&!i.call(r,f)&&(r[f]=e.attrs[f]);for(var f in null==u&&null==e.attrs.className||(r.className=null!=u?null!=e.attrs.className?String(e.attrs.className)+" "+String(u):u:null!=e.attrs.className?e.attrs.className:null),a&&(r.class=null),r)if(i.call(r,f)&&"key"!==f){n.attrs=r;break}return Array.isArray(o)&&1===o.length&&null!=o[0]&&"#"===o[0].tag?n.text=o[0].children:n.children=o,n}function s(e){if(null==e||"string"!=typeof e&&"function"!=typeof e&&"function"!=typeof e.view)throw Error("The selector must be either a string or a component.");var r=n.apply(1,arguments);return"string"==typeof e&&(r.children=t.normalizeChildren(r.children),"["!==e)?u(o[e]||a(e),r):(r.tag=e,r)}s.trust=function(e){return null==e&&(e=""),t("<",void 0,void 0,e,void 0,void 0)},s.fragment=function(){var e=n.apply(0,arguments);return e.tag="[",e.children=t.normalizeChildren(e.children),e};var f=function(){return s.apply(this,arguments)};if(f.m=s,f.trust=s.trust,f.fragment=s.fragment,(c=function(e){if(!(this instanceof c))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=u(n,!0),i=u(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function u(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(l){r(l)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(o){r&&r(o)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new c(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},c.prototype.catch=function(e){return this.then(null,e)},c.prototype.finally=function(e){return this.then(function(t){return c.resolve(e()).then(function(){return t})},function(t){return c.resolve(e()).then(function(){return c.reject(t)})})},c.resolve=function(e){return e instanceof c?e:new c(function(t){t(e)})},c.reject=function(e){return new c(function(t,n){n(e)})},c.all=function(e){return new c(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(a){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(a){o(a)}},u&&null!=s?c.send(s):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}},v=p(window,c),h=function(e){var n,r=e.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function s(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function p(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)x(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&z>=l;)if(w=t[C],k=n[z],null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&v(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}for(;C>=a&&z>=l;)if(d=t[a],p=n[l],null==d)a++;else if(null==p)l++;else{if(d.key!==p.key)break;a++,l++,d!==p&&v(e,d,p,r,y(t,a,o),i)}for(;C>=a&&z>=l;){if(null==d)a++;else if(null==p)l++;else if(null==w)C--;else if(null==k)z--;else{if(l===z)break;if(d.key!==k.key||w.key!==p.key)break;S=y(t,a,o),g(e,m(w),S),w!==p&&v(e,w,p,r,S,i),++l<=--z&&g(e,m(d),o),d!==k&&v(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[z],d=t[a],p=n[l]}for(;C>=a&&z>=l;){if(null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&v(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}w=t[C],k=n[z]}if(l>z)x(t,a,C+1);else if(a>C)s(e,n,l,z+1,r,o,i);else{var E,j,P=o,A=z-l+1,N=new Array(A),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==E&&(E=h(t,a,C+1)),null!=(k=n[$])){var R=E[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(N)).length-1,$=z;$>=l;$--)p=n[$],-1===N[$-l]?f(e,p,r,i,o):j[O]===$-l?O--:g(e,m(p),o),null!=p.dom&&(o=n[$].dom);else for($=z;$>=l;$--)p=n[$],-1===N[$-l]&&f(e,p,r,i,o),null!=p.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&x(t,l,t.length),n.length>L&&s(e,n,l,n.length,r,o,i)}}}function v(e,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&T(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(e,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){p(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var E=/[A-Z]/g;function j(e){return"-"+e.toLowerCase()}function P(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(E,j)}function A(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(P(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(P(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(P(r))}}function N(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new N,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return N.prototype=Object.create(null),N.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(e,n){if(!e)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=e.namespaceURI;null==e.vnodes&&(e.textContent=""),n=t.normalizeChildren(Array.isArray(n)?n:[n]),p(e,e.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),e.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var f=0;f-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=b(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var s=b(e.slice(o+1));for(var u in s)n[u]=s[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var f=d(a);f&&(t+="?"+f);var c=d(u);if(c&&(t+="#"+c),n){var p=o?o.state:null,v=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(p,v,l.prefix+t):e.history.pushState(p,v,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function s(){var t=l.getPath(),n={},r=i(t,n,n),s=e.history.state;if(null!=s)for(var f in s)n[f]=s[f];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i0&&a[a.length-1])&&(6===i[0]||2===i[0])){o=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]li{white-space:nowrap}.mtc span.mtc__item-title{margin-right:1rem}.mtc .mtc__draggable{cursor:move;cursor:grab;cursor:-webkit-grab}.mtc .mtc__draggable:active{cursor:grabbing;cursor:-webkit-grabbing}.mtc .mtc__as_child{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),color-stop(50%,rgba(41,137,216,.49)),color-stop(51%,rgba(32,124,202,.5)),to(rgba(125,185,232,0)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(41,137,216,.49) 50%,rgba(32,124,202,.5) 51%,rgba(125,185,232,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(0, 125, 185, 0.9098)",GradientType=0)}.mtc .mtc__below{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),to(rgba(30,87,153,.7)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(30,87,153,.7));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(179, 30, 87, 0.6)",GradientType=0)}.mtc .mtc__above{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,.7)),to(rgba(30,87,153,0)));background:linear-gradient(180deg,rgba(30,87,153,.7) 0,rgba(30,87,153,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(179, 30, 87, 0.6)",endColorstr="rgba(0, 30, 87, 0.6)",GradientType=0)}');var c="tree-item-",l=function(){var t={isOpen:!1};return{oninit:function(e){var r=e.attrs.options,n=r.isOpen,a=r._hasChildren;t.toggle=function(e){a(e)&&(n?e[n]=!e[n]:t.isOpen=!t.isOpen)},t.open=function(e,r){r||(n?e[n]=!0:t.isOpen||(t.isOpen=!0))}},view:function(r){var n=r.attrs,a=n.item,i=n.options,c=n.dragOptions,d=n.selectedId,u=n.width,s=i.id,f=i.treeItemView,m=i._findChildren,p=i._isExpanded,_=i._addChildren,g=i._hasChildren,b=i._depth,v=i.onSelect,h=i.onDelete,x=i.editable,y=x.canUpdate,w=x.canCreate,I=x.canDelete,k=x.canDeleteParent,O=i.maxDepth,T=t.toggle,C=t.open,D=t.isOpen,S=p(a,D),E=g(a),N=b(a);return(0,e.default)("li"+(y?".mtc__draggable":"")+"[id=tree-item-"+a[s]+"][draggable="+y+"]",c,[(0,e.default)(".mtc__item",{onclick:function(e){e.stopPropagation(),v(a,d!==a[s])}},[(0,e.default)(".mtc__header.mtc__clearfix",{class:d===a[s]?"active":""},[E?(0,e.default)(o,{buttonName:S?"expand_less":"expand_more",onclick:function(){return T(a)}}):void 0,(0,e.default)(".mtc__item-title",{class:(y?"mtc__moveable":"")+" "+(E?"":"mtc__childless-item"),style:"max-width: "+u+"px"},(0,e.default)(f,{treeItem:a,depth:N,width:u}))],(0,e.default)(".mtc__act-group",[!I||!k&&E?"":(0,e.default)(o,{buttonName:"delete",onclick:function(){return h(a)}}),w&&N0&&(n.className=s.join(" ")),l[r]={tag:t,attrs:n}}function i(a,e){var l=e.attrs,n=r.normalizeChildren(e.children),i=t.call(l,"class"),c=i?l.class:l.className;if(e.tag=a.tag,e.attrs=null,e.children=void 0,!s(a.attrs)&&!s(l)){var o={};for(var u in l)t.call(l,u)&&(o[u]=l[u]);l=o}for(var u in a.attrs)t.call(a.attrs,u)&&"className"!==u&&!t.call(l,u)&&(l[u]=a.attrs[u]);for(var u in null==c&&null==a.attrs.className||(l.className=null!=c?null!=a.attrs.className?String(a.attrs.className)+" "+String(c):c:null!=a.attrs.className?a.attrs.className:null),i&&(l.class=null),l)if(t.call(l,u)&&"key"!==u){e.attrs=l;break}return Array.isArray(n)&&1===n.length&&null!=n[0]&&"#"===n[0].tag?e.text=n[0].children:e.children=n,e}function c(e){if(null==e||"string"!=typeof e&&"function"!=typeof e&&"function"!=typeof e.view)throw Error("The selector must be either a string or a component.");var t=a.apply(1,arguments);return"string"==typeof e&&(t.children=r.normalizeChildren(t.children),"["!==e)?i(l[e]||n(e),t):(t.tag=e,t)}module.exports=c;
9 | },{"../render/vnode":"y51D","./hyperscriptVnode":"skWM"}],"b5QZ":[function(require,module,exports) {
10 | "use strict";var e=require("../render/vnode");module.exports=function(r){return null==r&&(r=""),e("<",void 0,void 0,r,void 0,void 0)};
11 | },{"../render/vnode":"y51D"}],"HdGc":[function(require,module,exports) {
12 | "use strict";var r=require("../render/vnode"),e=require("./hyperscriptVnode");module.exports=function(){var n=e.apply(0,arguments);return n.tag="[",n.children=r.normalizeChildren(n.children),n};
13 | },{"../render/vnode":"y51D","./hyperscriptVnode":"skWM"}],"R2AL":[function(require,module,exports) {
14 | "use strict";var r=require("./render/hyperscript");r.trust=require("./render/trust"),r.fragment=require("./render/fragment"),module.exports=r;
15 | },{"./render/hyperscript":"l8ze","./render/trust":"b5QZ","./render/fragment":"HdGc"}],"EYVZ":[function(require,module,exports) {
16 | "use strict";var n=function(t){if(!(this instanceof n))throw new Error("Promise must be called with `new`");if("function"!=typeof t)throw new TypeError("executor must be a function");var e=this,r=[],o=[],i=s(r,!0),c=s(o,!1),u=e._instance={resolvers:r,rejectors:o},f="function"==typeof setImmediate?setImmediate:setTimeout;function s(n,t){return function i(s){var h;try{if(!t||null==s||"object"!=typeof s&&"function"!=typeof s||"function"!=typeof(h=s.then))f(function(){t||0!==n.length||console.error("Possible unhandled promise rejection:",s);for(var e=0;e0||n(e)}}var r=e(c);try{n(e(i),r)}catch(o){r(o)}}l(t)};n.prototype.then=function(t,e){var r,o,i=this._instance;function c(n,t,e,c){t.push(function(t){if("function"!=typeof n)e(t);else try{r(n(t))}catch(i){o&&o(i)}}),"function"==typeof i.retry&&c===i.state&&i.retry()}var u=new n(function(n,t){r=n,o=t});return c(t,i.resolvers,r,!0),c(e,i.rejectors,o,!1),u},n.prototype.catch=function(n){return this.then(null,n)},n.prototype.finally=function(t){return this.then(function(e){return n.resolve(t()).then(function(){return e})},function(e){return n.resolve(t()).then(function(){return n.reject(e)})})},n.resolve=function(t){return t instanceof n?t:new n(function(n){n(t)})},n.reject=function(t){return new n(function(n,e){e(t)})},n.all=function(t){return new n(function(n,e){var r=t.length,o=0,i=[];if(0===t.length)n([]);else for(var c=0;c'+t.children+"",i=i.firstChild):i.innerHTML=t.children,t.dom=i.firstChild,t.domSize=i.childNodes.length,t.instance=[];for(var a,u=l.createDocumentFragment();a=i.firstChild;)t.instance.push(a),u.appendChild(a);b(e,u,o)}function v(e,t,n,l,o,r){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,l,o,r);else if(null==n||0===n.length)x(e,t,0,t.length);else{var i=null!=t[0]&&null!=t[0].key,a=null!=n[0]&&null!=n[0].key,u=0,d=0;if(!i)for(;d=d&&$>=u&&(b=t[E],w=n[$],b.key===w.key);)b!==w&&m(e,b,w,l,o,r),null!=w.dom&&(o=w.dom),E--,$--;for(;E>=d&&$>=u&&(c=t[d],v=n[u],c.key===v.key);)d++,u++,c!==v&&m(e,c,v,l,p(t,d,o),r);for(;E>=d&&$>=u&&u!==$&&c.key===w.key&&b.key===v.key;)y(e,b,S=p(t,d,o)),b!==v&&m(e,b,v,l,S,r),++u<=--$&&y(e,c,o),c!==w&&m(e,c,w,l,o,r),null!=w.dom&&(o=w.dom),d++,b=t[--E],w=n[$],c=t[d],v=n[u];for(;E>=d&&$>=u&&b.key===w.key;)b!==w&&m(e,b,w,l,o,r),null!=w.dom&&(o=w.dom),$--,b=t[--E],w=n[$];if(u>$)x(e,t,d,E+1);else if(d>E)s(e,n,u,$+1,l,o,r);else{var z,C,A=o,L=$-u+1,N=new Array(L),T=0,j=0,I=2147483647,M=0;for(j=0;j=u;j--){null==z&&(z=h(t,d,E+1));var O=z[(w=n[j]).key];null!=O&&(I=O>>1)+(l>>>1)+(n&l&1);e[t[a]]0&&(g[o]=t[n-1]),t[n]=o)}}n=t.length,l=t[n-1];for(;n-- >0;)t[n]=l,l=g[l];return g.length=0,t}(N)).length-1,j=$;j>=u;j--)v=n[j],-1===N[j-u]?f(e,v,l,r,o):C[T]===j-u?T--:y(e,v,o),null!=v.dom&&(o=n[j].dom);else for(j=$;j>=u;j--)v=n[j],-1===N[j-u]&&f(e,v,l,r,o),null!=v.dom&&(o=n[j].dom)}}else{var D=t.lengthD&&x(e,t,u,t.length),n.length>D&&s(e,n,u,n.length,l,o,r)}}}function m(t,n,l,o,i,u){var s=n.tag;if(s===l.tag){if(l.state=n.state,l.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,e.attrs=t.attrs,e.children=t.children,e.text=t.text,!0}(l,n))return;if("string"==typeof s)switch(null!=l.attrs&&F(l.attrs,l,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,l);break;case"<":!function(e,t,n,l,o){t.children!==n.children?(S(e,t),c(e,n,l,o)):(n.dom=t.dom,n.domSize=t.domSize,n.instance=t.instance)}(t,n,l,u,i);break;case"[":!function(e,t,n,l,o,r){v(e,t.children,n.children,l,o,r);var i=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var N=/[A-Z]/g;function T(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(N,T)}function I(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var l in e.style.cssText="",n){null!=(o=n[l])&&e.style.setProperty(j(l),String(o))}else{for(var l in n){var o;null!=(o=n[l])&&(o=String(o))!==String(t[l])&&e.style.setProperty(j(l),o)}for(var l in t)null!=t[l]&&null==n[l]&&e.style.removeProperty(j(l))}}function M(){this._=n}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new M,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function D(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function F(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return M.prototype=Object.create(null),M.prototype.handleEvent=function(e){var t,n=this["on"+e.type];"function"==typeof n?t=n.call(e.currentTarget,e):"function"==typeof n.handleEvent&&n.handleEvent(e),this._&&!1!==e.redraw&&(0,this._)(),!1===t&&(e.preventDefault(),e.stopPropagation())},function(t,l,o){if(!t)throw new TypeError("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],i=u(),a=t.namespaceURI;null==t.vnodes&&(t.textContent=""),l=e.normalizeChildren(Array.isArray(l)?l:[l]);var s=n;try{n="function"==typeof o?o:void 0,v(t,t.vnodes,l,r,null,"http://www.w3.org/1999/xhtml"===a?void 0:a)}finally{n=s}t.vnodes=l,null!=i&&u()!==i&&"function"==typeof i.focus&&i.focus();for(var f=0;f=0&&(t.splice(c,2),e(r,[],l)),null!=o&&(t.push(r,o),e(r,n(o),l))},redraw:l}};
26 | },{"../render/vnode":"y51D"}],"JmBI":[function(require,module,exports) {
27 | "use strict";var e=require("./render");module.exports=require("./api/mount-redraw")(e,requestAnimationFrame,console);
28 | },{"./render":"knD4","./api/mount-redraw":"Lt2a"}],"e1pw":[function(require,module,exports) {
29 | "use strict";module.exports=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var o in e)r(o,e[o]);return t.join("&");function r(e,o){if(Array.isArray(o))for(var n=0;n=0&&(p+=n.slice(i,s)),o>=0&&(p+=(i<0?"?":"&")+d.slice(o,g));var x=e(c);return x&&(p+=(i<0&&o<0?"?":"&")+x),l>=0&&(p+=n.slice(l)),f>=0&&(p+=(l<0?"":"&")+d.slice(f)),p};
34 | },{"../querystring/build":"e1pw","./assign":"TIRB"}],"Nv5j":[function(require,module,exports) {
35 | "use strict";var e=require("../pathname/build");module.exports=function(t,n,r){var o=0;function a(e){return new n(e)}function i(t){return function(o,i){"string"!=typeof o?(i=o,o=o.url):null==i&&(i={});var s=new n(function(n,r){t(e(o,i.params),i,function(e){if("function"==typeof i.type)if(Array.isArray(e))for(var t=0;t=200&&t.target.status<300||304===t.target.status||/^file:\/\//i.test(e),s=t.target.response;if("json"===p?t.target.responseType||"function"==typeof n.extract||(s=JSON.parse(t.target.responseText)):p&&"text"!==p||null==s&&(s=t.target.responseText),"function"==typeof n.extract?(s=n.extract(t.target,n),i=!0):"function"==typeof n.deserialize&&(s=n.deserialize(s)),i)r(s);else{try{a=t.target.responseText}catch(u){a=s}var c=new Error(a);c.code=t.target.status,c.response=s,o(c)}}catch(u){o(u)}},"function"==typeof n.config&&(f=n.config(f,n,e)||f)!==d&&(a=f.abort,f.abort=function(){l=!0,a.call(this)}),null==c?f.send():"function"==typeof n.serialize?f.send(n.serialize(c)):c instanceof t.FormData?f.send(c):f.send(JSON.stringify(c))}),jsonp:i(function(e,n,r,a){var i=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+o++,s=t.document.createElement("script");t[i]=function(e){delete t[i],s.parentNode.removeChild(s),r(e)},s.onerror=function(){delete t[i],s.parentNode.removeChild(s),a(new Error("JSONP request failed"))},s.src=e+(e.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(i),t.document.documentElement.appendChild(s)})}};
36 | },{"../pathname/build":"bE7V"}],"JA2T":[function(require,module,exports) {
37 | "use strict";var e=require("./promise/promise"),r=require("./mount-redraw");module.exports=require("./request/request")(window,e,r.redraw);
38 | },{"./promise/promise":"PcbK","./mount-redraw":"JmBI","./request/request":"Nv5j"}],"Pflx":[function(require,module,exports) {
39 | "use strict";module.exports=function(e){if(""===e||null==e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var r=e.split("&"),t={},l={},n=0;n-1&&a.pop();for(var u=0;u1&&"/"===l[l.length-1]&&(l=l.slice(0,-1))):l="/",{path:l,params:t<0?{}:e(r.slice(t+1,n))}};
42 | },{"../querystring/parse":"Pflx"}],"LcIX":[function(require,module,exports) {
43 | "use strict";var r=require("./parse");module.exports=function(e){var t=r(e),n=Object.keys(t.params),a=[],u=new RegExp("^"+t.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,function(r,e,t){return null==e?"\\"+r:(a.push({k:e,r:"..."===t}),"..."===t?"(.*)":"."===t?"([^/]+)\\.":"([^/]+)"+(t||""))})+"$");return function(r){for(var e=0;e0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]li{white-space:nowrap}.mtc span.mtc__item-title{margin-right:1rem}.mtc .mtc__draggable{cursor:move;cursor:grab;cursor:-webkit-grab}.mtc .mtc__draggable:active{cursor:grabbing;cursor:-webkit-grabbing}.mtc .mtc__as_child{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),color-stop(50%,rgba(41,137,216,.49)),color-stop(51%,rgba(32,124,202,.5)),to(rgba(125,185,232,0)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(41,137,216,.49) 50%,rgba(32,124,202,.5) 51%,rgba(125,185,232,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(0, 125, 185, 0.9098)",GradientType=0)}.mtc .mtc__below{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,0)),to(rgba(30,87,153,.7)));background:linear-gradient(180deg,rgba(30,87,153,0) 0,rgba(30,87,153,.7));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(0, 30, 87, 0.6)",endColorstr="rgba(179, 30, 87, 0.6)",GradientType=0)}.mtc .mtc__above{background:-webkit-gradient(linear,left top,left bottom,from(rgba(30,87,153,.7)),to(rgba(30,87,153,0)));background:linear-gradient(180deg,rgba(30,87,153,.7) 0,rgba(30,87,153,0));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgba(179, 30, 87, 0.6)",endColorstr="rgba(0, 30, 87, 0.6)",GradientType=0)}');var d="tree-item-",u=function(){var t={};return{oninit:function(e){var r=e.attrs.options,n=r.isOpen,a=void 0===n?"isOpen":n,o=r.id,i=r._hasChildren;t.toggle=function(e,t){i(e)&&("function"==typeof a?a(e[o],"set",!a(e[o],"get")):e[a]=!e[a],t&&t(e,"function"==typeof a?a(e[o],"get"):e[a]))},t.open=function(e,t){t||("function"==typeof a?a(e[o],"set",!0):e[a]=!0)}},view:function(r){var n=r.attrs,a=n.item,i=n.options,c=n.dragOptions,d=n.selectedId,s=n.width,f=i.id,m=i.treeItemView,p=i._findChildren,g=i._isExpanded,_=i._addChildren,b=i._hasChildren,v=i._depth,h=i.onSelect,x=i.onToggle,y=i.onDelete,w=i.editable,I=w.canUpdate,k=w.canCreate,T=w.canDelete,O=w.canDeleteParent,C=i.maxDepth,D=t.toggle,S=t.open,E=g(a),P=b(a),N=v(a);return(0,e.default)("li"+(I?".mtc__draggable":"")+"[id=tree-item-"+a[f]+"][draggable="+I+"]",c,[(0,e.default)(".mtc__item",{onclick:function(e){e.stopPropagation(),h(a,d!==a[f])}},[(0,e.default)(".mtc__header.mtc__clearfix",{class:d===a[f]?"active":""},[P?(0,e.default)(l,{buttonName:E?"expand_less":"expand_more",onclick:function(){return D(a,x)}}):void 0,(0,e.default)(".mtc__item-title",{class:(I?"mtc__moveable":"")+" "+(P?"":"mtc__childless-item"),style:"max-width: "+s+"px"},(0,e.default)(m,{treeItem:a,depth:N,width:s}))],(0,e.default)(".mtc__act-group",[!T||!O&&P?"":(0,e.default)(l,{buttonName:"delete",onclick:function(){return y(a)}}),k&&N