simple-tree-component is a zero-dependency, pure JavaScript/TypeScript UI tree component for modern browsers. It provides three modes of operation: a standalone tree view, a single-select dropdown, and a multi-select dropdown. The component is published on npm as simple-tree-component (current version: 1.3.2) under the MIT license. Author: Christian Kotzbauer.
The component supports hierarchical node data with features including search/filtering, keyboard navigation, checkboxes (with optional recursive selection), drag-and-drop reordering, read-only mode, custom styling, and an event-based API.
- Language: TypeScript 5.4.5 (strict mode, target ES2015)
- Module bundler: Rollup 4.59.0 with
@rollup/plugin-typescript - Output format: UMD bundle (
dist/simple-tree-component.js) - Minification: Terser 5.46.0
- Styles: Sass 1.97.3 (SCSS syntax) with PostCSS 8.5.8 + Autoprefixer 10.4.27
- Testing: Jest 29.7.0 with ts-jest 29.4.6, jsdom environment
- Linting: ESLint 8.57.1 with
@typescript-eslint/parser7.18.0 and@typescript-eslint/eslint-plugin7.18.0 - Formatting: Prettier 3.8.1
- Task runner: npm-run-all2 8.0.4 (
run-sfor sequential script execution) - Dev utilities: chokidar 5.0.0 (file watching in dev mode), ts-node 10.9.2
- Documentation: Docsify (served from
docs/out/), TypeDoc 0.25.13 with typedoc-plugin-markdown 3.17.1 - Dependency management: Renovate (config in
renovate.json) - Security scanning: Snyk (weekly via GitHub Actions)
- No runtime dependencies -- all devDependencies only
simple-tree-component/
├── src/ # Main source code
│ ├── index.ts # Entry point - exports `simpleTree` factory function
│ ├── factory.ts # Creates tree instances based on mode
│ ├── typings.d.ts # Public type declarations (re-exports for consumers)
│ ├── test-utils.ts # Shared test helper functions
│ ├── tsconfig.json # Source-specific TS config (ES2015 modules)
│ ├── data/
│ │ └── data-service.ts # Core data layer - node CRUD, filtering, selection logic
│ ├── event/
│ │ └── event-manager.ts # Pub/sub event system (subscribe, subscribeOnce, publish)
│ ├── types/
│ │ ├── instance.ts # TreeInstance interface (public API), TreeModeNameMap
│ │ ├── options.ts # BaseOptions, TreeConfiguration, defaults
│ │ ├── tree-node.ts # InitTreeNode, TreeNode interfaces and defaults
│ │ ├── subscription.ts # Subscription interface (dispose pattern)
│ │ └── rects.ts # Rect interface, overlay calculation declarations
│ ├── ui/
│ │ ├── base-tree.ts # Core tree rendering, search, collapse, checkboxes, DnD
│ │ ├── common-tree-logic.ts # Abstract base class implementing TreeInstance<K>
│ │ ├── common-dropdown-tree-logic.ts # Dropdown open/close, overlay positioning
│ │ ├── single-select-dropdown.ts # SingleSelectDropdown mode
│ │ ├── multi-select-dropdown.ts # MultiSelectDropdown mode (pillbox UI)
│ │ ├── tree-view.ts # TreeView mode (inline tree, no dropdown)
│ │ ├── key-event-handler.ts # Keyboard navigation (arrows, enter, escape)
│ │ ├── drag-and-drop-handler.ts # Native HTML5 drag-and-drop for node reordering
│ │ ├── overlay-placement.ts # Dropdown positioning calculation
│ │ ├── ui-constants.ts # CSS class names and event name constants
│ │ └── utils.ts # DOM helper functions, HTML escaping
│ ├── validation/
│ │ └── validation.ts # Tree node array validation, uniqueness checks
│ ├── style/
│ │ ├── index.scss # Main stylesheet (all component styles)
│ │ ├── colors.scss # Color variables (overridable with !default)
│ │ ├── defaults.scss # Size/spacing variables (overridable with !default)
│ │ └── mixins.scss # SCSS mixins (chevrons, cross icon, text-overflow)
│ └── __tests__/
│ ├── index.spec.ts # Initialization tests
│ ├── data-service.spec.ts # DataService unit tests
│ ├── event.spec.ts # EventManager tests
│ ├── selection.spec.ts # Selection behavior tests
│ ├── ui-event.spec.ts # UI event integration tests
│ ├── utils.spec.ts # Utility function tests
│ ├── validation.spec.ts # Validation logic tests
│ └── ui/
│ ├── ui-component.spec.ts # UI component integration tests
│ └── overlay-placement.spec.ts # Overlay positioning tests
├── config/
│ ├── rollup.ts # Rollup configuration (UMD output)
│ └── jest.json # Jest configuration
├── dist/ # Build output (committed)
├── demo/ # Aurelia-based demo application
├── docs/ # Docsify documentation site
│ ├── *.md # Documentation pages
│ ├── build-docs.ts # Documentation build script
│ ├── typedoc.json # TypeDoc configuration
│ └── out/ # Built documentation output
├── build.ts # Custom build script (Rollup + Sass + Terser + watch)
├── build-post.ts # Post-build: moves .d.ts files, copies typings
├── .github/workflows/ # CI/CD workflows
├── tsconfig.json # Root TS config (CommonJS, for build scripts)
├── tsconfig.declarations.json # TS config for generating .d.ts files
├── tsconfig.typecheck.json # TS config for type-checking all src/**/*.ts
├── .eslintrc.js # ESLint configuration
├── .prettierrc.js # Prettier configuration
├── .editorconfig # Editor settings
├── renovate.json # Renovate bot configuration
├── index.template.html # Dev server HTML template
└── package.json
CommonTreeLogic<K> (abstract, implements TreeInstance<K>)
├── CommonDropdownTreeLogic<K> (abstract, adds dropdown open/close/positioning)
│ ├── SingleSelectDropdown ("singleSelectDropdown" mode)
│ └── MultiSelectDropdown ("multiSelectDropdown" mode)
└── TreeView ("tree" mode)
Each mode class composes a BaseTree instance which handles the actual DOM rendering, search input, node collapse/expand, checkbox toggling, and drag-and-drop setup.
- Factory pattern:
src/factory.ts--createSimpleTree()selects the correct class based on themodestring. - Pub/Sub event system:
EventManagerprovides internal and external events. Internal events (prefixed with_) like_nodeSelected,_escapePressed,_hoverChangedcoordinate between UI components. Public events (selectionChanged,selectionChanging,nodeIndexChanged) are exposed to consumers viasubscribe()/subscribeOnce(). - Dispose/Subscription pattern: All subscriptions return a
Subscriptionobject with adispose()method for cleanup. - TypeScript conditional types:
TreeModeNameMapmaps mode names to their return types (TreeNode | nullfor single-select,TreeNode[]for multi-select, etc.). - Composition over inheritance:
BaseTreeis composed into each mode class rather than inherited. - Immutable copies:
DataService.getNode()andgetSelected()return copies of nodes (via spread), not internal references.
- Consumer calls
simpleTree(selector, mode, options). - Factory creates the appropriate mode class.
- Mode class creates
DataService(data layer) andEventManager(event bus). BaseTreerenders the DOM and wires up click/keyboard/drag events.- User interactions publish internal events; mode classes handle them and publish public events.
Runs sequentially via run-s:
build:pre-- Removesdist/directory (rimraf)build:build-- Executesbuild.tsvia ts-node:- Rollup bundles
src/index.tsintodist/simple-tree-component.js(UMD format) - Terser minifies to
dist/simple-tree-component.min.js - Sass compiles
src/style/index.scsstodist/simple-tree-component.cssand.min.css - Copies SCSS source files to
dist/scss/for consumers who want to customize variables
- Rollup bundles
build:types-- Generates TypeScript declaration files intodist/types/build:post-- Moves.d.tsfiles intodist/types/, copiessrc/typings.d.tstodist/typings.d.ts
Runs build.ts --dev which:
- Enables Rollup watch mode with sourcemaps
- Watches SCSS files for changes and rebuilds styles
- Auto-formats changed files with Prettier
- Copies
index.template.htmltoindex.htmlon changes
dist/
├── simple-tree-component.js # UMD bundle
├── simple-tree-component.min.js # Minified UMD bundle
├── simple-tree-component.css # Compiled CSS
├── simple-tree-component.min.css # Minified CSS
├── scss/ # Raw SCSS source files (for variable overrides)
├── types/ # TypeScript declaration files
└── typings.d.ts # Main type declaration entry
- Framework: Jest 29.7.0 with ts-jest 29.4.6
- Environment: jsdom (simulates browser DOM)
- Config file:
config/jest.json - Test location:
src/__tests__/andsrc/__tests__/ui/ - Test helpers:
src/test-utils.tsprovidesinitialize(),beforeEachTest(),createInstance(),createTreeNode(),simulate(), and assertion helpers - Total test lines: ~2,781 lines across 9 test files
- Coverage: Generated via
--coverageflag, output tocoverage/directory; reported to Codecov in CI
| File | Tests |
|---|---|
index.spec.ts |
Initialization, default options, mode selection |
data-service.spec.ts |
Node CRUD, filtering, selection, collapse |
event.spec.ts |
EventManager pub/sub, subscribeOnce |
selection.spec.ts |
Selection behavior across modes, checkbox recursion |
ui-event.spec.ts |
UI event integration (keyboard, clicks) |
utils.spec.ts |
HTML escaping, regex escaping |
validation.spec.ts |
Node validation, uniqueness checks |
ui/ui-component.spec.ts |
Full UI component integration tests |
ui/overlay-placement.spec.ts |
Overlay positioning calculations |
npm test # Runs typecheck + unit tests sequentially
npm run test:typecheck # TypeScript type checking only (no emit)
npm run test:unit # Jest unit tests with coverage- Parser:
@typescript-eslint/parser(ecmaVersion 2018, sourceType module) - Extends:
plugin:@typescript-eslint/recommended,prettier - Key rules:
@typescript-eslint/naming-convention: enforces camelCase for variables (with eslint-disable comments used for exceptions likeDEV_MODE)@typescript-eslint/no-explicit-any: disabled (0)@typescript-eslint/explicit-function-return-type: disabled (0)
- Ignores:
demo/,dist/
trailingComma: 'es5'printWidth: 130
indent_style: spaceindent_size: 4(2 for JSON files)end_of_line: lfcharset: utf-8-bomtrim_trailing_whitespace: trueinsert_final_newline: true
- 4-space indentation throughout TypeScript and SCSS
- Double quotes for strings in TypeScript
- No semicolons are omitted -- semicolons are always used
- CSS class names use kebab-case (e.g.,
simple-tree-node-wrapper) - TypeScript class names use PascalCase, methods and variables use camelCase
- Internal event names are prefixed with
_(e.g.,_nodeSelected) eslint-disablecomments are used sparingly and with specific rule names
All workflows use reusable workflows from ckotzbauer/actions-toolkit.
Three parallel jobs:
- build --
npm ci,npm run build,npm test, reports coverage to Codecov - docs --
npm ci,npm run build:docs(validates documentation builds) - lint --
npm ci,npm run lint
- Input: version number
- Runs:
npm ci, build, build:docs, test - Publishes to npm, creates GitHub release with
dist/as artifact - Triggers
deploy-docsworkflow after release
- Builds documentation with
npm run build:docs - Deploys
docs/out/togh-pagesbranch via SSH
- Runs weekly (Monday at 12:00 UTC) and on manual trigger
- Executes
snyk monitor
| Command | Description |
|---|---|
npm run build |
Full production build (clean, bundle, types, post-process) |
npm start |
Dev mode with watch (Rollup watch + SCSS watch + auto-format) |
npm test |
Run typecheck and unit tests |
npm run test:unit |
Jest unit tests with coverage |
npm run test:typecheck |
TypeScript type checking only |
npm run lint |
ESLint on all TypeScript files |
npm run format |
Prettier formatting on all TypeScript files |
npm run build:docs |
Build documentation (TypeDoc API docs + Docsify) |
npm run docs:serve |
Serve documentation locally via Docsify |
- No runtime dependencies: The component must remain zero-dependency. All packages in
package.jsonare devDependencies. - UMD output: The bundle exports a single default export (
simpleTreefunction) in UMD format, usable via script tags or module imports. - Three component modes:
"tree","singleSelectDropdown","multiSelectDropdown"-- the mode string determines the class instantiated and the type ofgetSelected()return value. - SCSS customization: All SCSS variables use
!defaultflags, allowing consumers to override colors, sizes, and spacing by importing the SCSS source fromdist/scss/before the component styles. - Node values must be unique: The
valueproperty on tree nodes serves as a unique identifier. Validation enforces this. - Copies on read: Public API methods that return nodes (
getNode(),getSelected()) return copies, not internal references. - Event naming: Public events are
"selectionChanged","selectionChanging","nodeIndexChanged". Internal events are prefixed with_. - Branch: Main branch is
main. - Contributing: Follows guidelines at
https://github.com/ckotzbauer/.github/blob/main/CONTRIBUTING.md. - Releases: Manual via GitHub Actions
workflow_dispatchwith a version input. Published to npm. - Dependency updates: Managed by Renovate bot (weekly schedule, demo/ directory ignored).