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.
- 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
npm install @magik_io/motepnpm add @magik_io/moteyarn add @magik_io/moteimport { 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));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');
});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');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);
});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"-
appendTo(selector)- Append element to specified targetnew Mote('div').appendTo('#app'); new Mote('span').appendTo(document.body);
-
appendToBody()- Append element to document.bodynew Mote('div#modal').appendToBody();
-
appendToHead()- Append element to document.headnew Mote('script').appendToHead();
-
appendToRoot()- Append element to document.documentElementnew Mote('div').appendToRoot();
-
appendWithin(callback)- Execute callback with element's IDnew Mote('div#parent').appendWithin((id) => { new Mote('span').text('Child').appendTo(id); });
mote<ElementName>(tagName, appendTo, callback?)Example:
mote('div#container', '#app', (id) => {
mote('h1', id).text('Title');
mote('p', id).text('Content');
});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'))-
self()- Get the underlying HTML elementconst element = new El('#myDiv').self();
-
parent()- Get parent elementnew El('#child').parent().addClass('parent-class');
-
firstChild<E>()- Get first child elementconst first = new El('#parent').firstChild<HTMLDivElement>();
-
children()- Get all child nodesconst nodes = new El('#parent').children();
-
find(selector)- Find elements within current elementconst buttons = new El('#form').find('button');
-
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 classif (el.hasClass('active')) { /* ... */ }
-
html(content)- Set inner HTMLel.html('<p>Hello World</p>');
-
text(content)- Set text contentel.text('Hello World'); el.text(123); el.text(true);
-
textContent(content?)- Get or set text contentconst text = el.textContent(); el.textContent('New text');
-
textChild(text)- Append text node as childel.textChild('Additional text');
-
empty()- Empty inner HTMLel.empty();
-
clear()- Clear inner HTML (alias for empty)el.clear();
-
set(attributes)- Set attributesel.set({ id: 'myId', 'data-value': '123', disabled: true });
-
unset(attributes)- Remove attributesel.unset(['disabled', 'readonly']); el.unset('disabled');
-
id(value?)- Get or set idconst id = el.id(); el.id('newId');
-
data(suffix)- Get data attributeconst value = el.data('user-id'); // Gets data-user-id
-
dataset()- Get dataset objectconst dataset = el.dataset(); console.log(dataset.userId);
-
val(value?)- Get or set valueconst value = el.val(); el.val('new value'); el.val(123);
-
check(boolean)- Check or uncheck inputel.check(true); el.check(false);
-
checked()- Get checked stateif (el.checked()) { /* ... */ }
-
type(type)- Set input typeel.type('password');
-
name(name)- Set name attributeel.name('username');
-
input(type)- Set name to id and typeel.input('email'); // Sets name=id and type=email
-
htmlFor(elementId)- Set label's for attributenew El('label').htmlFor('myInput');
-
src(url)- Set image sourcenew El<'img'>('#myImage').src('/images/photo.jpg');
-
alt(text)- Set image alt textnew El<'img'>('#myImage').alt('Photo description');
-
child(child, position?)- Append or prepend childel.child('<span>Text</span>'); el.child(document.createElement('div'), 'prepend'); el.child('<p>First</p>', 'prepend');
-
wrap(className)- Wrap element in divel.wrap('wrapper');
-
remove()- Remove element from DOMel.remove();
-
replaceWith(html)- Replace with HTML stringel.replaceWith('<div>New content</div>');
-
replaceWithElement(tagName, id?)- Replace with new elementconst newEl = el.replaceWithElement('div', 'newId');
-
on(event, listener, options?)- Add event listenerel.on('click', (e) => console.log(e)); el.on<{ userId: string }>('custom', (e) => { console.log(e.detail.userId); });
-
once(event, listener)- Add one-time event listenerel.once('click', (e) => console.log('Clicked once'));
-
off(event, listener, options?)- Remove event listenerel.off('click', myHandler);
-
trigger(event, options?)- Trigger custom eventel.trigger('change'); el.trigger('custom', { detail: { data: 'value' } });
-
now(eventName, detail)- Dispatch custom eventel.now('dataUpdated', { userId: '123' });
-
click()- Dispatch click eventel.click();
-
triggerChange()- Trigger change event (select elements)new El<'select'>('#mySelect').triggerChange();
-
dispatchEvent(eventName)- Dispatch eventel.dispatchEvent('input');
-
css(property)- Get computed CSS property valueconst color = el.css('color');
-
css(property, value)- Set single CSS propertyel.css('color', 'red'); el.css('font-size', 16);
-
css(properties)- Set multiple CSS propertiesel.css({ color: 'red', 'font-size': '16px', 'background-color': '#f0f0f0' });
-
show()- Show elementel.show();
-
hide()- Hide elementel.hide();
-
toggle()- Toggle visibilityel.toggle();
-
isVisible()- Check if element is visibleif (el.isVisible()) { /* ... */ }
-
width()- Get widthconst w = el.width(); // Returns number
-
width(value)- Set widthel.width(100); // Sets to 100px el.width('50%'); // Sets to 50%
-
height()/height(value)- Get or set heightconst h = el.height(); el.height(200); el.height('auto');
-
offset()- Get offset position relative to documentconst { top, left } = el.offset();
-
position()- Get position relative to offset parentconst { top, left } = el.position();
-
animate(keyframes, options)- Animate element using Web Animations APIel.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);
-
if(expression)- Conditional chainingel.if(condition)?.addClass('active');
-
nestFrom(callback)- Execute callback with element idel.nestFrom((id) => { new Mote('span').text('Child').appendTo(id); });
el<ElementName, StrictTypes>(selector)Example:
const myEl = el<'div'>('#container');new All<ElementType>(selector)Example:
new All<'button'>('.btn');-
each(callback)- Iterate over elementsnew All('.items').each((element, index) => { console.log(`Item ${index}:`, element.textContent); });
-
log(treeView?)- Debug log elementsnew All('.items').log(); new All('.items').log(true); // Tree view
-
self()- Get NodeList of elementsconst elements = new All('.items').self();
-
addClass(className)- Add class to all elementsnew All('.items').addClass('active'); new All('.items').addClass(['active', 'highlight']);
-
removeClass(className)- Remove class from all elementsnew All('.items').removeClass('active');
-
toggleClass(className)- Toggle class on all elementsnew All('.items').toggleClass('visible');
-
html(content)- Set inner HTML for all elementsnew All('.items').html('<p>Same content</p>');
-
text(content)- Set text content for all elementsnew All('.items').text('Same text');
-
textChild(text)- Append text node to all elementsnew All('.items').textChild(' - updated');
-
empty()/clear()- Empty all elementsnew All('.items').empty();
-
set(attributes)- Set attributes on all elementsnew All('.items').set({ 'data-active': 'true' });
-
unset(attributes)- Remove attributes from all elementsnew All('.items').unset(['disabled', 'readonly']);
-
attr(attribute, value)- Set attribute on all elementsnew All('.items').attr('data-value', '123');
-
data(name, value)- Set data attribute on all elementsnew All('.items').data('userId', '123');
-
val(value?)- Set value on all elementsnew All<'input'>('.inputs').val('same value');
-
type(type)- Set type on all elementsnew All<'input'>('.inputs').type('text');
-
name(name)- Set name on all elementsnew All<'input'>('.inputs').name('fieldName');
-
input(type)- Set name to id and type on all elementsnew All<'input'>('.inputs').input('email');
-
htmlFor(elementId)- Set htmlFor on all label elementsnew All<'label'>('.labels').htmlFor('targetInput');
-
child(element, position?)- Append/prepend child to all elementsconst span = document.createElement('span'); new All('.items').child(span); new All('.items').child(span, 'prepend');
-
wrap(className)- Wrap all elements in divnew All('.items').wrap('item-wrapper');
-
remove()- Remove all elements from DOMnew All('.items').remove();
-
replaceWith(html)- Replace all elements with HTMLnew All('.items').replaceWith('<div>Replacement</div>');
-
src(url)- Set src on all elementsnew All<'img'>('.images').src('/images/placeholder.jpg');
-
on(event, listener, options?)- Add event listener to all elementsnew All('.btns').on('click', (e) => console.log('Clicked'));
-
once(event, listener, options?)- Add one-time listener to all elementsnew All('.btns').once('click', (e) => console.log('First click'));
-
off(event, listener)- Remove event listener from all elementsnew All('.btns').off('click', myHandler);
-
now(eventName, detail)- Dispatch custom event on all elementsnew All('.items').now('refresh', { timestamp: Date.now() });
-
click()- Dispatch click event on all elementsnew All('.btns').click();
-
css(property, value)- Set single CSS property on all elementsnew All('.items').css('color', 'red');
-
css(properties)- Set multiple CSS properties on all elementsnew All('.items').css({ color: 'red', 'font-size': '16px' });
-
show()- Show all elementsnew All('.items').show();
-
hide()- Hide all elementsnew All('.items').hide();
-
toggle()- Toggle visibility of all elementsnew All('.items').toggle();
-
width(value)- Set width on all elementsnew All('.items').width(100); new All('.items').width('50%');
-
height(value)- Set height on all elementsnew All('.items').height(200);
-
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);
all<ElementType>(selector)Example:
const buttons = all<'button'>('.btn');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 });
});
});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');
});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>');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');
}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)'
});
});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);
});Mote is built with TypeScript and provides comprehensive type definitions.
El<ElementName, StrictTypes>
Mote<ElementName, StrictMode>
All<ElementType>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';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');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
});Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Run linter
pnpm lint
# Run tests
pnpm test
# Build library
pnpm buildMIT © Antonio B.
So mote it be ✨