Skip to content

MagikIO/mote

Repository files navigation

Mote

So mote it be

A lightweight, TypeScript-first DOM manipulation library that provides a jQuery-like API for modern web applications. The name "mote" refers to small, self-contained, reusable pieces of code that can be composed to build larger applications.

Version License

Features

  • TypeScript-First: Fully typed with generic support and strict mode options
  • Fluent API: Chainable methods for expressive, readable code
  • Lightweight: Zero dependencies, small footprint
  • Modern: Built for ES6+ environments
  • Type-Safe Events: Custom event typing with TagEventMap
  • Multiple Export Formats: ESM, CommonJS, and TypeScript declarations
  • jQuery-like: Familiar API for developers who know jQuery

Installation

npm install @magik_io/mote
pnpm add @magik_io/mote
yarn add @magik_io/mote

Quick Start

import { Mote, El, All } from '@magik_io/mote';

// Create and append a new element
new Mote('div#container')
  .addClass('wrapper')
  .text('Hello World')
  .on('click', (e) => console.log(e))
  .appendTo('#app');

// Manipulate an existing element
new El('#myElement')
  .addClass('active')
  .html('<p>Updated content</p>')
  .on('click', () => console.log('Clicked!'));

// Work with multiple elements
new All('.items')
  .addClass('highlighted')
  .on('click', (e) => console.log('Item clicked:', e));

Table of Contents

Core Classes

Mote - Element Creation

The Mote class extends El and is used for creating new DOM elements and appending them to the document.

import { Mote, mote } from '@magik_io/mote';

// Create element with id using tagName#id syntax
const container = new Mote('div#myContainer')
  .addClass('container')
  .appendTo('#app');

// Using factory function
mote('button#submitBtn', '#form', (id) => {
  // Nested creation with access to parent ID
  mote('span', id).text('Submit');
});

El - Single Element Manipulation

The El class is for manipulating existing DOM elements.

import { El, el } from '@magik_io/mote';

// Select and manipulate an element
const input = new El('#username')
  .addClass('form-control')
  .set({ placeholder: 'Enter username' })
  .on('input', (e) => console.log(e.target.value));

// Using factory function
el<'input'>('#email').val('user@example.com');

All - Multiple Element Manipulation

The All class allows you to manipulate multiple elements at once.

import { All, all } from '@magik_io/mote';

// Select and manipulate multiple elements
new All<'button'>('.btn')
  .addClass('btn-primary')
  .on('click', (e) => console.log('Button clicked'));

// Iterate over elements
all('.items').each((element, index) => {
  console.log(`Item ${index}:`, element);
});

API Reference

Mote API

Constructor

new Mote<ElementName>(tagName: ElementName)

Creates a new element. Supports tagName#id syntax.

Examples:

new Mote('div')                    // Creates a div
new Mote('div#container')          // Creates a div with id="container"
new Mote('button#submitBtn')       // Creates a button with id="submitBtn"

Append Methods

  • appendTo(selector) - Append element to specified target

    new Mote('div').appendTo('#app');
    new Mote('span').appendTo(document.body);
  • appendToBody() - Append element to document.body

    new Mote('div#modal').appendToBody();
  • appendToHead() - Append element to document.head

    new Mote('script').appendToHead();
  • appendToRoot() - Append element to document.documentElement

    new Mote('div').appendToRoot();
  • appendWithin(callback) - Execute callback with element's ID

    new Mote('div#parent').appendWithin((id) => {
      new Mote('span').text('Child').appendTo(id);
    });

Factory Function

mote<ElementName>(tagName, appendTo, callback?)

Example:

mote('div#container', '#app', (id) => {
  mote('h1', id).text('Title');
  mote('p', id).text('Content');
});

El API

Constructor

new El<ElementName, StrictTypes>(selector)

Accepts:

  • CSS selector string
  • HTML element
  • Function returning element or selector

Examples:

new El('#myElement')
new El(document.getElementById('myElement'))
new El(() => document.querySelector('.active'))

Selection & Navigation

  • self() - Get the underlying HTML element

    const element = new El('#myDiv').self();
  • parent() - Get parent element

    new El('#child').parent().addClass('parent-class');
  • firstChild<E>() - Get first child element

    const first = new El('#parent').firstChild<HTMLDivElement>();
  • children() - Get all child nodes

    const nodes = new El('#parent').children();
  • find(selector) - Find elements within current element

    const buttons = new El('#form').find('button');

Class Management

  • addClass(className) - Add class(es)

    el.addClass('active');
    el.addClass(['active', 'highlight']);
    el.addClass(() => 'dynamic-class');
  • removeClass(className) - Remove class(es)

    el.removeClass('active');
    el.removeClass(['active', 'highlight']);
  • toggleClass(className) - Toggle class(es)

    el.toggleClass('active');
    el.toggleClass(['active', 'visible']);
  • hasClass(className) - Check if element has class

    if (el.hasClass('active')) { /* ... */ }

Content Manipulation

  • html(content) - Set inner HTML

    el.html('<p>Hello World</p>');
  • text(content) - Set text content

    el.text('Hello World');
    el.text(123);
    el.text(true);
  • textContent(content?) - Get or set text content

    const text = el.textContent();
    el.textContent('New text');
  • textChild(text) - Append text node as child

    el.textChild('Additional text');
  • empty() - Empty inner HTML

    el.empty();
  • clear() - Clear inner HTML (alias for empty)

    el.clear();

Attributes

  • set(attributes) - Set attributes

    el.set({
      id: 'myId',
      'data-value': '123',
      disabled: true
    });
  • unset(attributes) - Remove attributes

    el.unset(['disabled', 'readonly']);
    el.unset('disabled');
  • id(value?) - Get or set id

    const id = el.id();
    el.id('newId');
  • data(suffix) - Get data attribute

    const value = el.data('user-id'); // Gets data-user-id
  • dataset() - Get dataset object

    const dataset = el.dataset();
    console.log(dataset.userId);

Form Elements

  • val(value?) - Get or set value

    const value = el.val();
    el.val('new value');
    el.val(123);
  • check(boolean) - Check or uncheck input

    el.check(true);
    el.check(false);
  • checked() - Get checked state

    if (el.checked()) { /* ... */ }
  • type(type) - Set input type

    el.type('password');
  • name(name) - Set name attribute

    el.name('username');
  • input(type) - Set name to id and type

    el.input('email'); // Sets name=id and type=email
  • htmlFor(elementId) - Set label's for attribute

    new El('label').htmlFor('myInput');

Images

  • src(url) - Set image source

    new El<'img'>('#myImage').src('/images/photo.jpg');
  • alt(text) - Set image alt text

    new El<'img'>('#myImage').alt('Photo description');

DOM Operations

  • child(child, position?) - Append or prepend child

    el.child('<span>Text</span>');
    el.child(document.createElement('div'), 'prepend');
    el.child('<p>First</p>', 'prepend');
  • wrap(className) - Wrap element in div

    el.wrap('wrapper');
  • remove() - Remove element from DOM

    el.remove();
  • replaceWith(html) - Replace with HTML string

    el.replaceWith('<div>New content</div>');
  • replaceWithElement(tagName, id?) - Replace with new element

    const newEl = el.replaceWithElement('div', 'newId');

Event Handling

  • on(event, listener, options?) - Add event listener

    el.on('click', (e) => console.log(e));
    el.on<{ userId: string }>('custom', (e) => {
      console.log(e.detail.userId);
    });
  • once(event, listener) - Add one-time event listener

    el.once('click', (e) => console.log('Clicked once'));
  • off(event, listener, options?) - Remove event listener

    el.off('click', myHandler);
  • trigger(event, options?) - Trigger custom event

    el.trigger('change');
    el.trigger('custom', { detail: { data: 'value' } });
  • now(eventName, detail) - Dispatch custom event

    el.now('dataUpdated', { userId: '123' });
  • click() - Dispatch click event

    el.click();
  • triggerChange() - Trigger change event (select elements)

    new El<'select'>('#mySelect').triggerChange();
  • dispatchEvent(eventName) - Dispatch event

    el.dispatchEvent('input');

CSS Manipulation

  • css(property) - Get computed CSS property value

    const color = el.css('color');
  • css(property, value) - Set single CSS property

    el.css('color', 'red');
    el.css('font-size', 16);
  • css(properties) - Set multiple CSS properties

    el.css({
      color: 'red',
      'font-size': '16px',
      'background-color': '#f0f0f0'
    });

Visibility & Display

  • show() - Show element

    el.show();
  • hide() - Hide element

    el.hide();
  • toggle() - Toggle visibility

    el.toggle();
  • isVisible() - Check if element is visible

    if (el.isVisible()) { /* ... */ }

Dimensions & Position

  • width() - Get width

    const w = el.width(); // Returns number
  • width(value) - Set width

    el.width(100);      // Sets to 100px
    el.width('50%');    // Sets to 50%
  • height() / height(value) - Get or set height

    const h = el.height();
    el.height(200);
    el.height('auto');
  • offset() - Get offset position relative to document

    const { top, left } = el.offset();
  • position() - Get position relative to offset parent

    const { top, left } = el.position();

Animations

  • animate(keyframes, options) - Animate element using Web Animations API

    el.animate([
      { opacity: 0, transform: 'translateY(-20px)' },
      { opacity: 1, transform: 'translateY(0)' }
    ], {
      duration: 300,
      easing: 'ease-out'
    });
  • fadeIn(duration?) - Fade in element (returns Promise)

    await el.fadeIn(300);
  • fadeOut(duration?) - Fade out element (returns Promise)

    await el.fadeOut(300);
  • fadeTo(opacity, duration?) - Fade to specific opacity (returns Promise)

    await el.fadeTo(0.5, 300);
  • slideDown(duration?) - Slide down element (returns Promise)

    await el.slideDown(300);
  • slideUp(duration?) - Slide up element (returns Promise)

    await el.slideUp(300);

Utilities

  • if(expression) - Conditional chaining

    el.if(condition)?.addClass('active');
  • nestFrom(callback) - Execute callback with element id

    el.nestFrom((id) => {
      new Mote('span').text('Child').appendTo(id);
    });

Factory Function

el<ElementName, StrictTypes>(selector)

Example:

const myEl = el<'div'>('#container');

All API

Constructor

new All<ElementType>(selector)

Example:

new All<'button'>('.btn');

Iteration

  • each(callback) - Iterate over elements

    new All('.items').each((element, index) => {
      console.log(`Item ${index}:`, element.textContent);
    });
  • log(treeView?) - Debug log elements

    new All('.items').log();
    new All('.items').log(true); // Tree view
  • self() - Get NodeList of elements

    const elements = new All('.items').self();

Class Management

  • addClass(className) - Add class to all elements

    new All('.items').addClass('active');
    new All('.items').addClass(['active', 'highlight']);
  • removeClass(className) - Remove class from all elements

    new All('.items').removeClass('active');
  • toggleClass(className) - Toggle class on all elements

    new All('.items').toggleClass('visible');

Content & Attributes

  • html(content) - Set inner HTML for all elements

    new All('.items').html('<p>Same content</p>');
  • text(content) - Set text content for all elements

    new All('.items').text('Same text');
  • textChild(text) - Append text node to all elements

    new All('.items').textChild(' - updated');
  • empty() / clear() - Empty all elements

    new All('.items').empty();
  • set(attributes) - Set attributes on all elements

    new All('.items').set({ 'data-active': 'true' });
  • unset(attributes) - Remove attributes from all elements

    new All('.items').unset(['disabled', 'readonly']);
  • attr(attribute, value) - Set attribute on all elements

    new All('.items').attr('data-value', '123');
  • data(name, value) - Set data attribute on all elements

    new All('.items').data('userId', '123');

Form Elements

  • val(value?) - Set value on all elements

    new All<'input'>('.inputs').val('same value');
  • type(type) - Set type on all elements

    new All<'input'>('.inputs').type('text');
  • name(name) - Set name on all elements

    new All<'input'>('.inputs').name('fieldName');
  • input(type) - Set name to id and type on all elements

    new All<'input'>('.inputs').input('email');
  • htmlFor(elementId) - Set htmlFor on all label elements

    new All<'label'>('.labels').htmlFor('targetInput');

DOM Operations

  • child(element, position?) - Append/prepend child to all elements

    const span = document.createElement('span');
    new All('.items').child(span);
    new All('.items').child(span, 'prepend');
  • wrap(className) - Wrap all elements in div

    new All('.items').wrap('item-wrapper');
  • remove() - Remove all elements from DOM

    new All('.items').remove();
  • replaceWith(html) - Replace all elements with HTML

    new All('.items').replaceWith('<div>Replacement</div>');
  • src(url) - Set src on all elements

    new All<'img'>('.images').src('/images/placeholder.jpg');

Events

  • on(event, listener, options?) - Add event listener to all elements

    new All('.btns').on('click', (e) => console.log('Clicked'));
  • once(event, listener, options?) - Add one-time listener to all elements

    new All('.btns').once('click', (e) => console.log('First click'));
  • off(event, listener) - Remove event listener from all elements

    new All('.btns').off('click', myHandler);
  • now(eventName, detail) - Dispatch custom event on all elements

    new All('.items').now('refresh', { timestamp: Date.now() });
  • click() - Dispatch click event on all elements

    new All('.btns').click();

CSS Manipulation

  • css(property, value) - Set single CSS property on all elements

    new All('.items').css('color', 'red');
  • css(properties) - Set multiple CSS properties on all elements

    new All('.items').css({
      color: 'red',
      'font-size': '16px'
    });

Visibility & Dimensions

  • show() - Show all elements

    new All('.items').show();
  • hide() - Hide all elements

    new All('.items').hide();
  • toggle() - Toggle visibility of all elements

    new All('.items').toggle();
  • width(value) - Set width on all elements

    new All('.items').width(100);
    new All('.items').width('50%');
  • height(value) - Set height on all elements

    new All('.items').height(200);

Animations

  • fadeIn(duration?) - Fade in all elements (returns Promise)

    await new All('.items').fadeIn(300);
  • fadeOut(duration?) - Fade out all elements (returns Promise)

    await new All('.items').fadeOut(300);

Factory Function

all<ElementType>(selector)

Example:

const buttons = all<'button'>('.btn');

Examples

Creating a Form

import { mote } from '@magik_io/mote';

mote('form#loginForm', '#app', (formId) => {
  // Username field
  mote('div.form-group', formId, (groupId) => {
    mote('label', groupId).text('Username').htmlFor('username');
    mote('input#username', groupId)
      .addClass('form-control')
      .set({ type: 'text', placeholder: 'Enter username' });
  });

  // Password field
  mote('div.form-group', formId, (groupId) => {
    mote('label', groupId).text('Password').htmlFor('password');
    mote('input#password', groupId)
      .addClass('form-control')
      .set({ type: 'password', placeholder: 'Enter password' });
  });

  // Submit button
  mote('button#submitBtn', formId)
    .addClass('btn btn-primary')
    .text('Login')
    .on('click', async (e) => {
      e.preventDefault();
      const username = el<'input'>('#username').val();
      const password = el<'input'>('#password').val();
      console.log({ username, password });
    });
});

Dynamic List

import { Mote, All } from '@magik_io/mote';

const items = ['Apple', 'Banana', 'Cherry', 'Date'];

const list = new Mote('ul#fruitList')
  .addClass('list-unstyled')
  .appendTo('#app');

items.forEach((fruit, index) => {
  new Mote('li')
    .addClass('list-item')
    .text(fruit)
    .set({ 'data-index': index.toString() })
    .on('click', (e) => {
      new All('.list-item').removeClass('active');
      e.currentTarget.classList.add('active');
    })
    .appendTo('#fruitList');
});

Modal Dialog

import { Mote } from '@magik_io/mote';

function createModal(title: string, content: string) {
  const modal = new Mote('div#modal')
    .addClass('modal')
    .appendToBody();

  mote('div.modal-content', '#modal', (contentId) => {
    mote('div.modal-header', contentId, (headerId) => {
      mote('h2', headerId).text(title);
      mote('button.close', headerId)
        .text('×')
        .on('click', () => modal.remove());
    });

    mote('div.modal-body', contentId)
      .html(content);

    mote('div.modal-footer', contentId, (footerId) => {
      mote('button', footerId)
        .addClass('btn-primary')
        .text('Close')
        .on('click', () => modal.remove());
    });
  });

  return modal;
}

// Usage
createModal('Welcome', '<p>Welcome to our application!</p>');

Event Delegation

import { El, Mote } from '@magik_io/mote';

const container = new Mote('div#container')
  .appendToBody();

// Add event listener to container
new El('#container').on('click', (e) => {
  const target = e.target as HTMLElement;
  if (target.classList.contains('item')) {
    console.log('Item clicked:', target.textContent);
  }
});

// Dynamically add items
for (let i = 0; i < 10; i++) {
  new Mote('div')
    .addClass('item')
    .text(`Item ${i}`)
    .appendTo('#container');
}

Animations and Transitions

import { El, Mote } from '@magik_io/mote';

// Fade in elements on page load
const notification = new Mote('div#notification')
  .addClass('alert')
  .text('Welcome back!')
  .hide()
  .appendToBody();

// Fade in after a delay
setTimeout(async () => {
  await notification.fadeIn(300);

  // Auto-hide after 3 seconds
  setTimeout(async () => {
    await notification.fadeOut(300);
    notification.remove();
  }, 3000);
}, 500);

// Slide toggle for accordion
const toggleButton = new El('#accordion-toggle');
const content = new El('#accordion-content');

toggleButton.on('click', async () => {
  if (content.isVisible()) {
    await content.slideUp(300);
  } else {
    await content.slideDown(300);
  }
});

// Custom animations with Web Animations API
new El('#animatedBox').animate([
  { transform: 'translateX(0px)', opacity: 1 },
  { transform: 'translateX(100px)', opacity: 0.5 },
  { transform: 'translateX(0px)', opacity: 1 }
], {
  duration: 2000,
  iterations: Infinity,
  easing: 'ease-in-out'
});

// CSS manipulation with animations
new El('#styledElement')
  .css({
    'background-color': '#3498db',
    'transition': 'all 0.3s ease',
    'transform': 'scale(1)'
  })
  .on('mouseenter', (e) => {
    new El(e.currentTarget as HTMLElement).css({
      'background-color': '#2980b9',
      'transform': 'scale(1.05)'
    });
  })
  .on('mouseleave', (e) => {
    new El(e.currentTarget as HTMLElement).css({
      'background-color': '#3498db',
      'transform': 'scale(1)'
    });
  });

TypeScript Generic Types

import { El, Mote } from '@magik_io/mote';

// Specify element type for better type safety
const input = new El<'input'>('#username');
const value = input.val(); // TypeScript knows this returns string

// Strict mode for type-safe event handlers
const button = new El<'button', true>('#submitBtn');
button.on('click', (e) => {
  // e is typed as MouseEvent specifically for button elements
  console.log(e.currentTarget.type);
});

// Create typed elements
const image = new Mote<'img'>('img#logo')
  .src('/logo.png')
  .alt('Company Logo')
  .appendTo('#header');

// Custom event data
interface UserData {
  userId: string;
  username: string;
}

new El('#userProfile').on<UserData>('userUpdated', (e) => {
  console.log(e.detail.userId, e.detail.username);
});

TypeScript Support

Mote is built with TypeScript and provides comprehensive type definitions.

Generic Parameters

El<ElementName, StrictTypes>
Mote<ElementName, StrictMode>
All<ElementType>

Type Definitions

The library exports several useful types:

import type {
  htmlTags,           // Union of all HTML tag names
  htmlElements,       // Union of all HTML element types
  selectorString,     // CSS selector string
  idString,           // ID selector (#id)
  classString,        // Class selector (.class)
  GenericEvent,       // Generic event type with custom data
  TagEventMap,        // Event map for specific element types
} from '@magik_io/mote/types';

Custom Elements

Extend Mote to support custom HTML elements:

// In your types file
declare module '@magik_io/mote' {
  interface CustomHTMLElements {
    'my-component': HTMLElement;
    'custom-button': HTMLButtonElement;
  }
}

// Usage
new Mote<'my-component'>('my-component').appendTo('#app');

Strict Type Mode

Enable strict typing for more precise type checking:

// Without strict mode (default)
const el = new El<'button'>('#myBtn');
el.on('click', (e) => {
  // e is GenericEvent
});

// With strict mode
const strictEl = new El<'button', true>('#myBtn');
strictEl.on('click', (e) => {
  // e is MouseEvent from TagEventMap
  console.log(e.button); // Typed correctly
});

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

Development

# Install dependencies
pnpm install

# Start development server
pnpm dev

# Run linter
pnpm lint

# Run tests
pnpm test

# Build library
pnpm build

License

MIT © Antonio B.

Links


So mote it be

About

So mote it be

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors