npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

simpl4u

v1.0.3

Published

A modular framework for building Electron and web applications using native Web Components, Bootstrap 5 and a reactive state management system.

Readme

Simpl4u

Simpl4u is a modular framework for building Electron and web applications using native Web Components, Bootstrap 5, and a reactive state management system. It provides a library of reusable components, services, and base classes to streamline the creation of dynamic, interactive user interfaces — with no build step required.

WIP — Work in Progress
POC — Proof of Concept


Features

  • Custom Web Components — 18 reusable components: tables, forms, buttons, inputs, navbars, modals, color/date pickers, file uploads, progress bars, toggles, combo-boxes, code editor (Ace), todo/kanban boards, and more.
  • Reactive State Management — Proxy-based global state store (SimplModel) with context namespacing, deep reactivity, and subscriber notification.
  • Localization — Built-in multi-language support with 5 locales (English, Catalan, Spanish, German, Japanese) and string interpolation.
  • Theme Management — Dynamic light/dark theme switching with system preference detection.
  • CRUD Operations — Full create, read, update, delete flows via SimplCrud and SimplTable.
  • Hash-based Routing — Lightweight single-page application routing via RouterService.
  • Modal Dialogs — Bootstrap-powered message, confirm, and prompt dialogs via ModalService.
  • Toast Notifications — Success, error, warning, and info notifications via ToastService (powered by Notyf).
  • Storage Abstraction — Three-tier persistence (IndexedDB, sessionStorage, Electron IPC) via StorageService/StorageAdapter.
  • File Operations — Download files in the browser, plus full file system access in Electron (read, write, copy, delete, directory selection).
  • Bootstrap 5 — All components render Bootstrap-compatible markup.
  • No Build Step — Pure ES modules loaded directly in modern browsers or Electron renderers.
  • Component Scaffolders4u Bash CLI generates and auto-registers new components.

Architecture

HTMLElement
  └── Element                    # Base class: rendering, events, validation, view state
        ├── StaticElement        # State-aware, manual re-render
        │     └── FormElement    # Form input base (required, disabled, reactive, etc.)
        │           ├── simpl-ace-editor
        │           ├── simpl-button
        │           ├── simpl-checkboxes
        │           ├── simpl-color
        │           ├── simpl-combobox
        │           ├── simpl-combobox-list
        │           ├── simpl-date
        │           ├── simpl-file
        │           ├── simpl-input
        │           ├── simpl-progress
        │           ├── simpl-select
        │           ├── simpl-switch
        │           └── simpl-textarea
        │
        └── ReactiveElement      # Auto re-renders on state changes
              ├── simpl-navbar
              ├── simpl-table
              └── simpl-todo

StaticElement (direct)
  ├── simpl-crud
  └── simpl-spinner

Core (/core)

| Class | Description | |---|---| | Element | Root base extending HTMLElement. Provides templating (template(), render()), attribute-based event binding ((click)="method"), model integration, lifecycle-managed message-bus messaging (on() / emit()), Bootstrap form validation, view state persistence, and CSS scoping. | | StaticElement | Extends Element. Subscribes to SimplModel changes and calls onUpdateState(property) — does not auto-re-render. | | FormElement | Extends StaticElement. Base for form input components with required, disabled, hidden, reactive, type, placeholder attributes. | | ReactiveElement | Extends Element. Subscribes to SimplModel and automatically re-renders the template on state changes. Uses morphdom-based in-place DOM patching to avoid unnecessary DOM replacement and flicker. |


Project Structure

simpl4u/
├── adapters/              # Storage adapters (e.g., StorageAdapter)
├── assets/
│   └── i18n/             # Locale files (en, ca, es, de, ja)
├── components/            # Web Component definitions (18 components)
├── core/                  # Base classes (Element, StaticElement, ReactiveElement, FormElement)
├── lib/                   # Vendored dependencies (Bootstrap 5, Bootstrap Icons, morphdom, Notyf, to-excel)
├── models/                # State management (SimplModel)
├── services/              # Application services (router, i18n, theme, modal, toast, etc.)
├── index.js               # Public entrypoint
├── s4u                    # Component scaffolder CLI (Bash)
├── AGENTS.md              # AI agent instructions
├── eslint.config.mjs      # ESLint flat config
└── package.json

Getting Started

Install

npm install

Most dependencies (Bootstrap, Bootstrap Icons, morphdom, Notyf, and to-excel) are vendored in lib/. The only npm runtime dependency is ace-builds, which powers simpl-ace-editor and is loaded on demand from node_modules/ace-builds when the component is first used. ESLint is the only dev dependency.

Lint

npm run lint          # Check code quality
npm run lint:fix      # Auto-fix lint issues

Usage

Import the library in your HTML or JavaScript entry point:

<script type="module" src="node_modules/simpl4u/index.js"></script>

All components are automatically registered as custom elements. For Electron apps, use the same import in your renderer process.

Saving disk space across multiple projects

Installing simpl4u with npm install copies the whole library into each project's node_modules, so if you have several apps that use it, the library is duplicated on disk N times.

To avoid this, install it with pnpm instead. pnpm keeps a single copy of each package version in a global store and hard-links it into every project, so all your apps share one physical copy while keeping fully isolated, working node_modules:

npm install -g pnpm
pnpm install simpl4u

No changes to your code or to simpl4u are required — pnpm installs from the same npm registry. (Note: installing the library globally with npm install -g does not help here, since import 'simpl4u/...' only resolves from a project's local node_modules. The global install is only useful for the s4u CLI.)


CLI

Simpl4u ships with the s4u CLI to scaffold a new component for your application (i.e. a project that consumes simpl4u). It creates components/{name}.js from the chosen base class and auto-registers it in components/index.js.

When you install simpl4u, npm links the CLI into node_modules/.bin, so you can run it with npx:

npx s4u component static MyComponent     # extends StaticElement
npx s4u component reactive MyComponent    # extends ReactiveElement

Inside an npm scripts entry the binary is already on the PATH, so you can call it directly:

{
  "scripts": {
    "generate": "s4u component reactive MyComponent"
  }
}

You can also install it globally to use s4u from anywhere:

npm install -g simpl4u
s4u component reactive MyComponent

The name is converted to kebab-case for the file name and custom-element tag (e.g. MyComponentmy-component.js, tag <my-component>). Generated components import their base class from the published package (e.g. import { ReactiveElement } from 'simpl4u/core/reactive-element.js';).

The simpl- prefix is reserved for simpl4u's own built-in components. Components you scaffold for your app are named however you like — no simpl- prefix is added.


Components

simpl-table

A dynamic, sortable, filterable data table with CRUD actions and XLS export.

<simpl-table name="users" actions="crude" context="myapp"></simpl-table>

| Attribute | Type | Description | |---|---|---| | name | string | Model key for the table data | | context | string | State context namespace | | actions | string | Action flags: c(create), r(read/detail), u(update), d(delete), e(export) |

Events are emitted via subscribe():

  • create, update, delete, detail, export

simpl-crud

Full CRUD component wrapping simpl-table with modal forms for data entry.

<simpl-crud name="products" actions="crude"></simpl-crud>

| Method | Description | |---|---| | setHeaders([...]) | Define column headers | | setForm([...]) | Define form fields with type, validation, and options |

Form field definition supports: name, required, disabled, class, unique, index, type, items (for selects).

simpl-input

Text input with label, validation, and reactive binding.

<simpl-input name="email" type="email" required placeholder="Enter email"></simpl-input>

simpl-textarea

Multi-line text input.

<simpl-textarea name="description" rows="5" required></simpl-textarea>

simpl-ace-editor

Code editor form field powered by Ace. Binds to the model like any other form field and follows the active theme (light/dark). Ace is loaded on demand from node_modules/ace-builds the first time the component is used.

<simpl-ace-editor name="script" mode="ace/mode/javascript" label="Script"></simpl-ace-editor>

| Attribute | Type | Description | |---|---|---| | name | string | Model key for the editor content | | label | string | Optional field label (i18n key) | | mode | string | Ace language mode (e.g. ace/mode/javascript). Default: ace/mode/text | | height | string | Editor height (CSS). Default: 20rem | | min-height | string | Minimum height (CSS). Default: 12rem | | font-size | string | Editor font size. Default: 0.75rem | | tab-size | number | Spaces per tab. Default: 2 | | wrap | boolean | Enable line wrapping ("true") | | show-print-margin | boolean | Show the print margin ("true") | | theme-dark | string | Ace theme used in dark mode. Default: ace/theme/tomorrow_night | | theme-light | string | Ace theme used in light mode. Default: ace/theme/textmate |

simpl-select

Dropdown select.

<simpl-select name="country" items='[{"id":"es","text":"Spain"},{"id":"de","text":"Germany"}]'></simpl-select>

simpl-switch

Bootstrap toggle switch (boolean).

<simpl-switch name="active"></simpl-switch>

simpl-checkboxes

Button-group checkboxes for multi-select.

<simpl-checkboxes name="roles" values="admin,user,guest"></simpl-checkboxes>

simpl-combobox

Combobox with autocomplete filtering.

<simpl-combobox name="city" items='[{"id":"1","text":"Barcelona"},{"id":"2","text":"Madrid"}]'></simpl-combobox>

simpl-date

HTML5 date picker.

<simpl-date name="birthdate" required></simpl-date>

simpl-color

HTML5 color picker.

<simpl-color name="bgColor"></simpl-color>

simpl-file

File input with optional multiple selection.

<simpl-file name="documents" multiple></simpl-file>

simpl-button

Bootstrap-styled button.

<simpl-button type="primary" title="Save"></simpl-button>

simpl-progress

Bootstrap progress bar.

<simpl-progress name="uploadProgress"></simpl-progress>

simpl-spinner

Full-page loading overlay. Controlled via SpinnerService.

<simpl-spinner></simpl-spinner>

simpl-navbar

Responsive navigation bar with language and theme toggles. Items and the colour variant are managed through NavbarService (not attributes); component properties below trigger a re-render on change.

<simpl-navbar name="My App" icon="bi-rocket" expand="md"></simpl-navbar>
import { NavbarService } from './services/navbar-service.js';

NavbarService.items = [
  { id: 'home', name: 'Home', icon: 'bi-house' },
  { id: 'about', name: 'About', emmit: true },
];
NavbarService.onClick((id) => console.log('clicked', id)); // fires for emmit items

| Property | Type | Description | |---|---|---| | name | string | App title text | | icon | string | Bootstrap Icons class (e.g. bi-house) or an image src URL | | iconOnly | boolean | Hide the title text, show only the icon | | expand | string | Breakpoint at which the navbar expands (sm|md|lg|xl|xxl). Default: md | | variant | string | Bootstrap colour variant (non-persistent default; see NavbarService.variant) | | hideLang | boolean | Hide the language dropdown | | hideTheme | boolean | Hide the theme switcher | | languages | array | Override the available language list |

simpl-todo

Kanban/Trello-style board with drag-and-drop cards.

<simpl-todo></simpl-todo>

simpl-combobox-list

Filterable list (internal component used by simpl-combobox).


Services

| Service | Description | |---|---| | RouterService | Hash-based SPA routing. view property, setView(hash), subscribe(callback). | | LanguageService | i18n with 5 built-in locales. lang getter/setter, i18n(key, params) for translation with {{param}} interpolation, set(languages) to merge custom translations. | | ThemeService | Light/dark theme management. theme getter/setter, switchTheme(), persists choice, detects system preference. | | ModalService | Bootstrap modal dialogs. message(text, title), confirm(text, title) → Promise<boolean>, prompt(text, title, value) → Promise<string>, open(body, title, hideCancel). | | ToastService | Notyf-powered notifications. success(msg), error(msg), warning(msg), info(msg). Configurable via duration (ms), dismissible, and position getters/setters. | | SpinnerService | Spinner overlay control. show(), hide() with debounce to prevent flickering. | | StorageService | High-level storage API wrapping StorageAdapter. saveApp(key), loadApp(key), saveUser(key), loadUser(key), saveSystem(key), loadSystem(key), saveAppModel(), loadAppModel(). | | DatabaseService | IndexedDB record CRUD over on-demand tables. database getter/setter (per-app DB name), insert(table, record), update(table, record), get(table, id), getAll(table), where(table, predicate), remove(table, id), clear(table), count(table). | | CryptoService | Web Crypto helpers for sensitive data: hash(text) (SHA-256), hashPassword(password), verifyPassword(password, hashed), encrypt(data, password), decrypt(payload, password). | | FileService | Browser/Electron file operations. download(filename, data) for browser; in Electron: readFile, writeFileSync, mkdir, selectDirectory, ls, cp, rm, rmdir via IPC. | | TextService | String utilities. unaccent(value) (remove diacritics), sanitize(value) (escape HTML), localDate(dateString) (format ISO date). | | ConfigService | Global persistence flags. saveApp and saveUser (default true) are read by core/element.js to gate automatic view-state save/restore. | | MessageService | Lightweight publish/subscribe message bus for decoupled (e.g. sibling) components. subscribe(topic, handler) → unsubscribe fn, unsubscribe(topic, handler), emit(topic, payload). Inside components, prefer the Element helpers on() / emit() over calling this service directly. | | NavbarService | Owns the simpl-navbar items and colour variant. Items: items get/set, addItem(item, index), removeItem(id), updateItem(id, patch), setVisible(id, visible), getItem(id), clear(), subscribe(cb). Click bus for emmit items: onClick(cb). Variant: variant get/set (persisted), setDefaultVariant(value) (non-persistent), subscribeVariant(cb), variants list. |


Services usage

import { RouterService } from './services/router-service.js';
import { ModalService } from './services/modal-service.js';
import { ToastService } from './services/toast-service.js';
import { SpinnerService } from './services/spinner-service.js';
import { CryptoService } from './services/crypto-service.js';
import { FileService } from './services/file-service.js';

// Navigation
RouterService.subscribe((view) => console.log('Navigated to:', view));
RouterService.setView('settings');

// Modal dialogs
const confirmed = await ModalService.confirm('Delete this item?', 'Confirm');
const name = await ModalService.prompt('Enter your name:', 'Prompt', 'default');

// Notifications
ToastService.success('Saved successfully');
ToastService.error('Something went wrong');

// Configuration
ToastService.duration = 3000;        // 3 seconds
ToastService.dismissible = false;    // Must wait out duration
ToastService.position = { x: 'left', y: 'bottom' };

// Loading spinner
SpinnerService.show();
await doHeavyWork();
SpinnerService.hide();

// File download (browser)
FileService.download('export.xlsx', blob);

// Crypto (hash + encrypt)
const fingerprint = await CryptoService.hash('value');
const encrypted = await CryptoService.encrypt({ token: 'abc' }, 'password');
const decrypted = await CryptoService.decrypt(encrypted, 'password');

// File operations (Electron only)
const content = await FileService.readFile('/path/to/file.txt');

State Management

SimplModel is a singleton reactive state container. Data is organized by context (namespaced keys).

import { SimplModel } from './models/simpl-model.js';

// Set a value
SimplModel.set('John Doe', 'name', 'userform');

// Get a value
const name = SimplModel.get('name', 'userform');

// Subscribe to changes
const unsubscribe = SimplModel.subscribe((model, property) => {
  console.log(`State changed: ${property}`);
});

State changes are batched via a 20ms debounce to optimize performance.


Localization

Built-in locales (located in assets/i18n/):

| Locale | Language | |---|---| | en | English | | ca | Catalan | | es | Spanish | | de | German | | ja | Japanese |

import { LanguageService } from './services/language-service.js';

// Get a translated string
LanguageService.i18n('confirm'); // "Are you sure?"

// With parameters
LanguageService.i18n('error-unique', { field: 'Email' });

// Change language
LanguageService.lang = 'ca';

// Add custom translations
LanguageService.set({ myKey: 'My Translation' });

Themes

ThemeService manages light/dark themes using Bootstrap 5's data-bs-theme attribute:

import { ThemeService } from './services/theme-service.js';

ThemeService.theme = 'dark';   // Switch to dark mode
ThemeService.switchTheme();    // Toggle

The service automatically respects the user's prefers-color-scheme system setting on first load if no preference has been saved.


Storage

Three-tier persistence via StorageService (backed by StorageAdapter in adapters/storage-adapter.js):

| Tier | Backend | Scope | |---|---|---| | App | IndexedDB | Persistent across sessions | | User | sessionStorage | Per browser tab | | System | Electron IPC (window.api.*) | File-system persistence in Electron |

While StorageService persists the app model as a single blob, DatabaseService offers structured record CRUD over IndexedDB tables (object stores). Tables are created on demand and records use an auto-incrementing id. Because simpl4u is shared across apps, set the database name once so each app gets its own store:

import { DatabaseService } from './services/database-service.js';

DatabaseService.database = 'my-app';                 // per-app DB name (set once)

const id   = await DatabaseService.insert('users', { name: 'Ada' }); // auto id
const user = await DatabaseService.get('users', id);
await DatabaseService.update('users', { ...user, name: 'Ada L.' });
const all   = await DatabaseService.getAll('users');
const admins = await DatabaseService.where('users', (u) => u.role === 'admin');
await DatabaseService.remove('users', id);
await DatabaseService.count('users');
await DatabaseService.clear('users');

Event Binding

Components use an attribute-based event binding syntax in their templates:

<button (click)="handleSave">Save</button>

This automatically calls this.handleSave(event) on the component instance.


Inter-component messaging

Components that don't share a parent/child relationship (e.g. siblings) can communicate through a publish/subscribe message bus instead of holding references to each other. The Element base class exposes two ergonomic helpers that wrap MessageService, so components never touch the service directly — mirroring how model / data / getField / setField wrap SimplModel.

  • this.on(topic, handler) — subscribes to a topic for the lifetime of the element. The handler is bound to the component and the subscription is automatically removed when the element leaves the DOM, so you never have to unsubscribe manually. Returns an unsubscribe function if you want to opt out early.
  • this.emit(topic, payload) — publishes a message to every subscriber of topic, regardless of where they sit in the DOM tree.

Topic names follow a domain:action convention (e.g. project:refresh).

// Publisher component
renewTags() {
  this.emit('project:refresh');
}

// Subscriber component (a sibling)
connectedCallback() {
  super.connectedCallback();
  this.on('project:refresh', () => this.reloadItems());
}

Subscribe in connectedCallback (once per DOM connection), not in onReady, which runs on every render and would register a new subscription each time. Use the bus for transient signals; for shared application state prefer the reactive SimplModel.


Roadmap

  • [ ] Unit tests
  • [ ] Build/bundler integration examples
  • [ ] TypeScript definitions
  • [ ] More built-in components (charts, trees, etc.)
  • [ ] Accessibility improvements
  • [ ] Documentation site

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License — see the LICENSE file for details.