Show HN: InDom – 3.8KB modern JavaScript auto-cleanup DOM library
2 weeks ago
1
Lightweight: Only 3.8KB gzipped – adds minimal overhead.
Intuitive DOM Toolkit: Comprehensive API with single-instance objects per element – eliminating duplication and boosting performance for element selection, manipulation, traversal, event handling and more.
Modern JavaScript: Built with ES2022, empowers clean and maintainable code.
Powerful Cleanup: State (event listeners, data, etc.) automatically removed when elements are destroyed. Leak-proof by design, no need for manual cleanup to avoid memory leaks.
Stack Agnostic: Set events with InDom, remove elements with any library (an older JS DOM library, a large JS framework, etc.) – cleanup still happens automatically. This allows gradual adoption of InDom at any pace.
Fast & Dependency-Free: Optimized for performance with zero external dependencies.
Modern Browser Support: Compatible with all modern browsers.
Auto-Typed Field IO: One-line get / set for any input—checkbox arrays, multi-select, files, radios etc. No need for manual branching.
Smart Value Harvester: Turns any container into a plain object of field values—single call, auto-typed, with dynamic-name grouping and zero config.
Three Distribution Formats: Plain JavaScript, ES modules, and TypeScript.
First-class TypeScript: ES2022-compatible type definitions included in /dist, source code in /src.
The convenience shortcuts ($1, $a, $id, $n, $v) are optional and can be renamed, scoped differently, or omitted entirely according to your preference. In the ES Modules and TypeScript distribution formats, you can simply choose not to import them, or import them under different names.
Queries the DOM using the CSS selector and returns an InDom object that contains the matching DOM element.
Returns null if no matching element is found.
Parameters:
selector {string} - CSS selector string
container {ParentNode | InDom} (optional) - The container element or InDom object to search within. Defaults to document.
Returns: {InDom | null} - InDom object, or null when not found
Examples:
$1('.example>div').setStyle('color','blue');/* If .example>div doesn't match any element, $1('.example>div') will return null. Attempting to call a method on null will result in a TypeError. If you want to avoid this error when the element is not found, use the optional chaining operator (?.) e.g.:*/$1('.example>div')?.setStyle('color','blue');$1('.example>div').onClick(n=>{//n here is the InDom objectn.addClass('clicked').setStyle({'color': 'red','font-size': '120%'});});// Set style to the first 'span', of the first '.example>div'$1('span',$1('.example>div')).setStyle('color','green');//or:constdiv=$1('.example>div');$1('span',div).setStyle('color','green');
Queries the DOM using the CSS selector and returns an InDomArray of InDom objects for each matching DOM element.
Returns an empty InDomArray if no matching elements are found.
Parameters:
selector {string} - CSS selector string
container {ParentNode | InDom} (optional) - The container element or InDom object to search within. Defaults to document.
Returns: {InDomArray} - InDomArray object, empty when none found
Examples:
// Set style on every '.example'$a('.example').setStyle('color','blue');// Set click event on every '.example>span'$a('.example>span').onClick(n=>{n.setStyle('color','green');});// The same, written as a single-line arrow function: $a('.example>span').onClick(n=>n.setStyle('color','green'));constexample1=$1('.example');//Set data 'init': 1 on direct children 'div' of the first '.example'$a('>div',example1).setData('init',1);//InDomArray objects themselves don't have get* methods//Get the left and top of each '.example>div' relative to viewport$a('.example>div').each(n=>{// .getBox() returns the bounding boxconstbox=n.getBox();console.log(`left:${box.left} top:${box.top}`);});
Fetches the element with the specified ID and returns an InDom object that contains it.
Returns null if no element with the given ID is found.
Parameters:
id {string} - The ID of the element to fetch
Returns: {InDom | null} - InDom object, or null when not found
Examples:
// You could get the InDom object by its ID using the general selector method:constexample1=$1("#test");// But it's more efficient, especially in HTML documents with many DOM elements,// to get it directly by ID:constexample2=$id("test");
Creates a new InDom object from a given underlying DOM element or an HTML string representing one DOM element.
Parameters:
source {Document | Element | string} - DOM Element or HTML string of a DOM element
Returns: {InDom} - New InDom object or an existing one (if one already exists for the given source element).
Throws:
TypeError - If the source is not a valid DOM Element, the document or HTML string of one DOM Element
Note:
If source is a string, it’s parsed as HTML. Sanitize untrusted strings before passing them.
Examples:
// Example 1constimg2=$n('<img src="example-star.png" alt="second star image example" width="50" height="50">');$1('.img-example-2').append(img2);// Example 2constcontainer=$id('img-container');constbtn=$1('>.btn',container);/** @type {InDom} */letimg;// Define the click handler for the button (no need for the InDom object // or the event arguments here).// It either loads an image for the first time or toggles the image source // on subsequent clicks.btn.onClick(()=>{// Image has not been loaded/created yetif(!img){// Check if an image load is already in progress to prevent duplicate requests.if(btn.getData('loading')===1){btn.setHtml('the image is loading...');// Exit the handler early as no further action is needed.return;}// Create a native HTML Image object.constimgEl=newImage();// Define the callback for when the image finishes loading successfully.imgEl.onload=()=>{// Wrap the loaded native image element in an InDom objectimg=$n(imgEl);img.setAttr('alt','a star image example');// Append the InDom-wrapped image to the container element.container.append(img);btn.setData('loading',0).setHtml('change img');};// Configure the image source and initial properties.imgEl.src='example-star.png';imgEl.width=50;imgEl.height=50;// Set a flag on the button to indicate that an image load is now in progress.btn.setData('loading',1);// Exit the handler as the load process has started.// Note: A production implementation should also handle img.onerror etc.return;}// Image already exists, toggle its source// Check the current src to determine which image to switch to.if(img.getAttr('src')=="example-cloud.png"){// If it's currently showing the 'cloud' image, switch to the 'star' image.img.setAttr('src','example-star.png').setAttr('alt','a star image example');// Exit after thatreturn;}// It is a 'star', switch to the 'cloud' image.img.setAttr('src','example-cloud.png').setAttr('alt','a cloud image example');return;});// Noticeconsttest1=$n('<div><span>one single parent element</span></div>');// will work , but: try{consttest2=$n('<div><span>div 1</span></div><div><span>div 2</span></div>');// will throw a TypeError because you can create one InDom object only for one element }catch(e){console.log(e);}// in case you need to insert multiple elements set the HTML of the parent element // or append / prepend HTML to it , and then get the InDom object you want: e.g. $1('.example>div').setHtml('<div><span>div 1</span></div><div><span>div 2</span></div>');consttest3=$1('.example>div>div:nth-child(2)');test3.setStyle('color','blue');//InDom objects are created only once for the same DOM elementconsta=$1('.example');constb=$1('.example');if(a===b){console.log('it\'s the same object');}
Registers a function to execute when DOM is ready (or immediately if already ready).
Parameters:
fn {() => void} - Function to execute
Throws:
TypeError - If fn handler is not a function
Examples:
// If the JavaScript file (containing InDom) is loaded and executed before the HTML DOM // content is fully parsed, attempting to select elements immediately might fail because // they don't exist yet. Additionally, adding event listeners to elements that haven't // been parsed yet will also fail. Use the InDom.onReady() function to ensure your code // runs only after the DOM is fully loaded and ready.InDom.onReady(()=>{// Safe to use InDom for querying DOM elements and attach event listeners here$1('.example').addClass('on');});
Returns the current value of the element, normalized for its type.
Single value inputs (input, textarea, etc.): string or null.
select (single): string or null.
select (multiple): array of selected values or empty array.
input[type=checkbox] (same name group): array of checked values.
input[type=radio] (same name group): string of the checked value or null.
input[type=file] (single or multiple): FileList object (zero or more files).
Parameters:
container {Document | Element | InDom} (optional) - Scope for checkbox and radio group lookups. When provided, only searches within this container for related elements. Defaults to document.
Returns: {string | string[] | FileList | null} - string for single values, array for multiple/select, FileList for file inputs, null when no selection or the element lacks a value property
Throws:
Error - If the underlying element has been removed
constcontainer=$1('.input-examples');// Iterate through each direct div child of the container.$a('>div',container).each(div=>{// Find the first child element within the current div.// Based on the HTML structure, this is the actual field input/textarea/select/etc.constfield=$1('>*',div);// Create a button constbtn=$n("<span class='btn'>log value</span>");// Append the button to the current div so it sits next to the field.div.append(btn);// Attach a click event listener to the button.btn.onClick(()=>{// When the button is clicked, log the field's name and its current value.console.log(`name: ${field.getAttr("name")} value:`);// Call the getValue() method on the field and log the result.// The output will vary based on the type of field and its current state.console.log(field.getValue());});/* Expected outputs based on initial HTML state: - input "username" -> string - textarea "message" -> string - select "color" -> string (selected color) - select "size" multiple -> array of strings (selected sizes), empty if none selected - radio "payment" -> string (selected payment), null if none selected - checkbox "features" -> array of strings (selected features), empty if none selected - file "documents" -> FileList object, empty if none selected with .length 0 */});
Returns a plain object with field names as keys and their getValue() results as values.
one call → all document field's values as JS object.
checkbox groups / multiple selects become arrays automatically
duplicate names in different forms / sections → add a container argument
dynamic fields (name_34, name_65) → auto-group under name:{'34':'Alice','65':'Bob'}
Parameters:
...args {string | string[] | InDom} (optional) - Field names (rest or array) or an InDom object as last arg to limit scope
Returns: {Object} - map of field names to their current values
Throws:
TypeError - If a given field name is not a non-empty string
Examples:
// every input/textarea/select field in documentleto=$v();console.log(o);//{"username":"Alice","message":"","color":"blue","size":["s","m"],"payment":null,//"features":["wifi","gps"]...}// every field inside first .input-exampleso=$v($1('.input-examples'));console.log(o);//{"username":"Alice","message":"","size":["s","m"],"payment":null,"features":[]...}// only username + features (whole document)o=$v('username','features');console.log(o);//{"username":"Alice","features":["wifi","gps"]}// only username + features (inside first .input-examples)o=$v('username','features',$1('.input-examples'));console.log(o);//{"username":"Alice","features":[]}// the same as $v(["username","features"],$1('.input-examples'));
// pick normal + grouped fieldso=$v('username','name_','age_');console.log(o);//{"username":"Alice","name":{"34":"Bob","65":"Carol"},"age":{"34":"28","65":"32"}}// harvest all (default: group underscores)o=$v();console.log(o);//{"username":"Alice","message":"",..."name":{"34":"Bob","65":"Carol"},//"age":{"34":"28","65":"32"}}// harvest all WITHOUT groupingo=$v([]);console.log(o);//{"username":"Alice","message":"",..."name_34":"Bob","age_34":"28",//"name_65":"Carol","age_65":"32"}
Sets the element’s value, normalised for its type (see getValue()).
Parameters:
value {string | string[]} - Value(s) to assign
container {Document|Element|InDom} (optional) - Scope for checkbox and radio group lookups. When provided, only searches within this container for related elements. Defaults to document.
Returns: {InDom | InDomArray} - this for chaining
Throws:
TypeError - If the element has no writable value
Error- If the underlying element(s) has been removed
Examples:
// single text input$1('[name="username"]').setValue('Bob');// multiple select$1('[name="size"]').setValue(['m','l']);// check only 'gps' in this container (other containers ignored)constdiv=$1('.input-examples');$1('[name="features"]',div).setValue('gps',div);// a single value can be set with a string or a one-item array// clear all editable fields$a('input, textarea, select').setValue(null);
// log every keypress in username / message fields$a('[name="username"], [name="message"]').on('keydown',(n,e)=>{console.log(`name:${n.getAttr('name')} , key pressed:${e.key} , current value:${n.getValue()}`);if(e.key==='s'){e.preventDefault();// block 's' key}});// add / remove hover class on every .example>div$a('.example>div').onEnter(n=>n.addClass('on'));$a(".example>div").onLeave(n=>n.removeClass('on'));// simple accordion: only one panel open at a timeconstmenu=$1('#menu');constmenuBtn=$1('>.btn',menu);constsearch=$1('#search');constsearchBtn=$1('>.btn',search);menuBtn.onClick(()=>{if(menu.hasClass('on')){menu.removeClass('on');return;}if(search.hasClass('on')){searchBtn.onClick();}// close othermenu.addClass('on');});searchBtn.onClick(()=>{if(search.hasClass('on')){search.removeClass('on');return;}if(menu.hasClass('on')){menuBtn.onClick();}// close othersearch.addClass('on');});// clicking anywhere adds 'clicked' class to the clicked element$n(document).onClick((_,e)=>$n(e.target).addClass('clicked'));// 'on' can also accept many event types for the same handler // here is a simple throttle exampleletcanClick=true;$1(".example>div").on(["click","touchstart"],n=>{if(!canClick){return;}canClick=false;console.log(n);// do somethingsetTimeout(()=>canClick=true,300);});
Registers a callback function that runs after the object's internal state (listeners, data) has been cleaned up, and just before its element is removed from the DOM.
Parameters:
fn {(n: InDom) => void} - The callback function
Returns: {Function | Function[]} - The internal handler(s) – pass to .off('onRemove', …) to unregister
Throws:
TypeError - If fn is not a function
Error - If the underlying element(s) has been removed
Examples:
constexample=$1('.example');// callback fires no matter how the element is removedexample.onRemove(()=>{console.log('removed:',example);alert('press OK → element disappears');});// Through InDom remove method on the objectexample.remove();// Through InDom setHtml on body $1('body').setHtml('empty');// Through native DOM removaldocument.querySelector('.example').remove();// Through native innerHTML on its parent document.querySelector('.example').parentElement.innerHTML='empty';
Removes event listener(s) registered with .on() or its shorthand methods.
Parameters:
type {string} (optional) - Event type. If omitted, all listeners are removed.
fn {Function | Function[]} (optional) - Handler(s) returned by .on(). If omitted, all listeners of type are removed.
Returns: {InDom | InDomArray} - this for chaining
Throws:
TypeError - If type is provided but is not a non-empty string.
RangeError - (InDomArray only) If fn array length does not match collection length.
Error - If the underlying element has been removed
Examples:
constdivs=$a('.example>div');divs.onClick(n=>{console.log(n);/* this function is visible in DevTools: #events / click / Set entry / [[TargetFunction]] */});// a simple logger example divs.onEnter(n=>console.log(`onEnter in:${n.getHtml()}`));constaddOnFns=divs.onEnter(n=>n.addClass('on'));constremoveOnFns=divs.onLeave(n=>n.removeClass('on'));// remove addOnFns and removeOnFns but keep the first onEnter loggerdivs.off('mouseenter',addOnFns).off('mouseleave',removeOnFns);// remove every mouseenter handler (including the logger)divs.off('mouseenter');// remove every handler of every type (including onClick)divs.off();
Cleans the internal state of the InDom object(s) and removes the underlying DOM element(s) from the document.
This method is also triggered automatically, when the element is removed from the DOM by any other means.
Throws:
Error - If the underlying element (or an element in case of InDomArray) has already been removed
Examples:
// remove the first .example>div$1(".example>div").remove();// remove all .example>div$a(".example>div").remove();
Returns the InDom object for the closest ancestor (or direct parent if no selector) that matches the selector.
Returns null if nothing is found.
Parameters:
selector {string} (optional) - CSS selector to test against ancestors.
Returns: {InDom | null} - InDom object, or null when not found
Throws:
Error - If the underlying element has been removed
Examples:
constspan=$1('.example>div>span');console.log(span.getParent().getHtml());// <span>this is a first test</span>console.log(span.getParent('.example').getHtml());// <div> <span>this is a first test</span></div>...
Returns this if its underlying element matches the selector, otherwise the InDom object for its closest ancestor element that matches.
Returns null if nothing is found.
Parameters:
selector {string} - CSS selector to test against this and ancestors.
Returns: {InDom | null} - InDom object, or null when not found
Throws:
Error - If the underlying element has been removed
Examples:
// delegate clicks on all links (present or future)$n(document).onClick((_,e)=>{// _ instead of n because we only need the event object here (for IDEs)constlink=$n(e.target).getSelfOrParent("a");if(link){console.log(`URL:${link.getAttr('href')} clicked`);}});// test link (works even if added later)$1('body').append(`<a href="https://github.com/constcallid/indom" target="_blank"> InDom - modern JavaScript DOM library</a>`);
Prepends one or more HTML strings, DOM elements or InDom objects to the beginning of the underlying element(s).
Parameters:
...children {(string | Node | InDom)[]} - Content to append (variadic; single array is flattened)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Note:
If an argument is a string, it’s parsed as HTML and inserted. Sanitize untrusted strings before passing them.
Examples:
// prepend examples (mirror of append examples)constul=$1('ul.example-1');ul.prepend('<li>first</li>');// stringul.prepend(img);// DOM Elementul.prepend($n(img));// InDom object (same img) ul.prepend($a('>div',donor));// InDomArray$a('>li',ul).prepend('<span>test</span>');// bulk prepend to every <li> of ul
Inserts one or more HTML strings, DOM elements or InDom objects after the underlying element(s).
When multiple items are provided they are inserted in reverse order so the first item appears first in the DOM.
Parameters:
...siblings {(string | Node | InDom)[]} - Content to append (variadic; single array is flattened)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Note:
If an argument is a string, it’s parsed as HTML and inserted. Sanitize untrusted strings before passing them.
//isolated stepsconstul=$1('ul.example-1');constfirstLi=$1(">li",ul);// raw HTML stringfirstLi.after('<div>test</div>');console.log(ul.getHtml());// <li>li 1</li><div>test</div><li>li 2</li>// native DOM elementconstimg=newImage();img.src='example-star.png';img.width=img.height=50;firstLi.after(img);console.log(ul.getHtml());//<li>li 1</li><img ...><li>li 2</li>// InDom objectfirstLi.after($n(img));// same img, in InDom objectconsole.log(ul.getHtml());// identical markup//<li>li 1</li><img ...><li>li 2</li>// InDomArray (moved from .example-2)constdonor=$1('.example-2');firstLi.after($a('>div',donor));// moves both divsconsole.log(ul.getHtml());//<li>li 1</li><div>div 1</div><div>div 2</div><li>li 2</li>console.log(donor.getHtml());// <span>span 1</span> (divs gone)// bulk after to every <li> of ul$a('>li',ul).after('<span>test</span>');console.log(ul.getHtml());//<li>li 1</li><span>test</span><li>li 2</li><span>test</span>
Inserts one or more HTML strings, DOM elements or InDom objects before the underlying element(s).
Parameters:
...siblings {(string | Node | InDom)[]} - Content to append (variadic; single array is flattened)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Note:
If an argument is a string, it’s parsed as HTML and inserted. Sanitize untrusted strings before passing them.
Examples:
// before examples (mirror of after examples)constul=$1('ul.example-1');constfirstLi=$1(">li",ul);firstLi.before('<div>test</div>');// raw HTML stringfirstLi.before(img);// native DOM elementfirstLi.before($n(img));// same img, in InDom objectfirstLi.before($a('>div',donor));// InDomArray (moves both divs)$a('>li',ul).before('<span>test</span>');// bulk before to every <li> of ul
content {string} - Content to insert (coerced to string)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Note:
setHtml inserts raw HTML. Use it with trusted strings; sanitize any user-provided content before calling it.
Examples:
constdiv1=$1('.example>div');//set onClick on the every span child of div1$a('>span',div1).onClick(n=>console.log('clicked',n));//replace innerHTML → old spans gone, listener gonediv1.setHtml('<span>another test</span>');// re-register on the new span(s):$a('>span',div1).onClick(n=>console.log('clicked',n));// or:div1.onClick((_,e)=>{constspan=$n(e.target).getSelfOrParent('.example>div>span');if(span){console.log('clicked',span);}});
Stores a key/value pair in memory or updates the underlying element’s data-* attribute (if it already exists) with the stringified value.
Available only for objects whose underlying element is connected to the DOM, ensuring internal state consistency.
Parameters:
key {any} - Data key
value {any} - Data value (will be coerced to string for data-* attributes)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element has already been removed
Examples:
constdiv=$1('.example>div');// click-counter stored in memorydiv.onClick(()=>{// read counter (default 0 if never stored)constclicks=div.getData('clicked')??0;div.setData('clicked',clicks+1);});// store object and intdiv.setData('user',{id: 34,name: 'Bob'});console.log(div.hasData('user') ? 'has data key: user'
: 'doesn\'t have data key: user');//has data key: userdiv.setData('test',1);console.log([div.getData('user'),div.getData('test')]);// [{id: 34, name: 'Bob'}, 1]// remove only 'user'div.removeData('user');console.log([div.getData('user'),div.getData('test')]);// [undefined, 1] // grab every editable field inside the first .input-examplesconstfields=$a('input, textarea, select',$1('.input-examples'));// snapshot original values as JSON stringsfields.each(n=>n.setData('originalValue',JSON.stringify(n.getValue())));// button to check if anything changed$1('body').append('<div class="checkBtn">Any field modified?</div>');$1('.checkBtn').onClick(()=>{// InDomArray extends Array and inherits all standard array methods.// true if any field's current value differs from its snapshot (stops at first true)constmodified=fields.some(n=>n.getData('originalValue')!==JSON.stringify(n.getValue()));console.log(modified ? 'modified' : 'same');});{// The above is to demonstrate different concepts because with InDom you could just:constsection=$1('.input-examples');$1('body').append('<div class="rwCheckBtn">Any field modified? (rw)</div>');constoriginal=JSON.stringify($v(section));$1('.rwCheckBtn').onClick(()=>console.log(original===JSON.stringify($v(section)) ? 'same' : 'modified'));}
Returns the data-* attribute value (as string) if it exists, otherwise the in-memory value.
Returns null if the key is not found in either place.
Available only for objects whose underlying element is connected to the DOM, ensuring internal state consistency.
Parameters:
key {any} - Key to look up (stringified for data-* attributes)
Returns: {any} - The stored value, or null if not found
Throws:
Error - If the underlying element has been removed
Returns true if the underlying element has a data-* attribute or in its internal memory map, otherwise false.
Available only for objects whose underlying element is connected to the DOM, ensuring internal state consistency.
Parameters:
key {any} - Data key to test (automatically stringified and prefixed with data- for attribute check)
Returns: {boolean} - true when the key exists, false otherwise
Throws:
Error - If the underlying element has been removed
Sets an attribute value to the underlying element.
Parameters:
key {string} - Attribute key.
value {any} - Attribute value (will be coerced to string).
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Examples:
constimg=$n('<img src="example-star.png" width="50" height="50">');// helper: return attr value if the attribute exists or 'no alt' if doesn'tconstgetImgAlt=()=>img.hasAttr('alt') ? img.getAttr('alt') : 'no alt';console.log(`img alt:${getImgAlt()}`);// no alt img.setAttr('alt','example image');console.log(`img alt:${getImgAlt()}`);// example imageimg.removeAttr('alt');console.log(`img alt:${getImgAlt()}`);// no alt
Returns a DOMRect with the underlying element’s viewport-relative bounding box.
Returns: {DOMRect} - Native object with left / x, top / y, width, height, right, bottom.
Throws:
Error - If the underlying element has been removed
Examples:
constdiv=$n('<div></div>');div.setStyle({display: 'inline-block',position: 'fixed',top: '110px',left: '130px',width: '100px',height: '150px',backgroundColor: 'blue'});//console.log(div.getBox());console.log(JSON.stringify(div.getBox()));//DOMRect {"x":0,"y":0,"width":0,"height":0,"top":0,"right":0,"bottom":0,"left":0}//because it is not yet connected to DOM $1('body').append(div);console.log(div.getBox());console.log(JSON.stringify(div.getBox()));//DOMRect {"x":130,"y":110,"width":100,"height":150,"top":110,"right":230,//"bottom":260,"left":130}
Returns a DOMRect that expands the underlying element’s bounding box by its margins.
Returns: {DOMRect} - Native object with left / x, top / y, width, height, right, bottom.
Throws:
Error - If the underlying element has been removed
Examples:
constdiv=$n('<div></div>');div.setStyle({'display': 'inline-block','position': 'fixed','top': '110px','left': '130px','width': '100px','height': '150px','background-color': 'blue','margin': '10px 20px'});$1('body').append(div);console.log(div.getBox());//DOMRect {"top":120,"left":150,"right":250,"bottom":270,"width":100,"height":150}constouterBox=div.getOuterBox();console.log(outerBox);//DOMRect {"x":130,"y":110,"width":140,"height":170,"top":110,"right":270,//"bottom":280,"left":130}constdiv2=$n('<div></div>');div2.setStyle({'display': 'inline-block','position': 'fixed','top': outerBox.top+'px','left': outerBox.left+'px','width': outerBox.width+'px','height': outerBox.height+'px','background-color': 'red'});$1('body').append(div2);// red div2 overlay: exactly covers the margin box of the div blue element
Adds one or more CSS classes to the underlying element(s).
Parameters:
...names {string} - Class name(s) to add (variadic)
Returns: {InDom | InDomArray} - this for chaining
Throws:
Error - If the underlying element(s) has been removed
Examples:
// add 'clicked' class to any .example>div that gets clickedconstdivs=$a('.example>div');divs.onClick(n=>n.addClass('clicked'));// button: counts how many are currently clicked, then resets themconstsumResetClicked=$n('<div>Sum and reset clicked</div>');$1('body').append(sumResetClicked);sumResetClicked.onClick(()=>{letclicked=0;divs.each(n=>{if(n.hasClass('clicked')){// test stateclicked++;n.removeClass('clicked');// reset state}});console.log('clicked:'+clicked);});
InDomArray extends Array, so every native Array method works. All methods that return a new array (concat, filter, flat, flatMap, map, slice, toReversed, toSorted, toSpliced) automatically return another InDomArray.
Examples:
// Suppose that for every #mainMenu>div there is a matching .menu-icon (e.g. positioned fixed)constmenuIcons=$a('.menu-icon');$a('#mainMenu>div').each((n,i)=>{n.onEnter(()=>menuIcons[i].addClass('on'));n.onLeave(()=>menuIcons[i].removeClass('on'));});constcat=$id('categories');// Sort direct child .example divs by number of their direct span children,// then re-append in new ordercat.append($a('>div',cat).sort((a,b)=>$a('>span',a).length-$a('>span',b).length));
Executes a function for each InDom object in the collection.
Parameters:
fn {(n: InDom, index: number, array: InDomArray) => void} - Function to execute
Returns: {InDomArray} - this for chaining
Examples:
$a('.example>div').each(n=>{if(!n.hasData('init')){// one-time initialisationn.setData('init',1);}});// .each() is safe on empty collections: the callback simply never runs
Returns a new InDomArray collection containing only the InDom objects that their elements match a CSS selector or pass a predicate function.
The original collection is left untouched.
Parameters:
selectorOrFn {string | {(n: InDom, index: number, array: InDomArray) => boolean}} - CSS selector to match against elements or predicate function; return true to include the item in the result.
Returns: {InDomArray} - New filtered collection (empty if nothing matches).
Example:
constexampleDivs=$a('.example>div');exampleDivs.onEnter(n=>n.addClass('opened'));$1('body').append('<div id="test-filter">test filter</div>');$id('test-filter').onClick((...args)=>{// keep only .opened itemsconstopenedDivs=exampleDivs.filter('.opened');// keep items that contain at least one <a>constdivsWithLinks=openedDivs.filter(n=>$a('a',n).length>0);// same as:constdivsWithLinks2=newInDomArray();openedDivs.each(n=>{if($a('a',n).length>0){divsWithLinks2.push(n);}});});
InDom works directly in any modern browser — no bundler or build process is required.
Simply include the library script and you’re ready to use it.
Example:
<script src="https://cdn.jsdelivr.net/npm/indom@latest/dist/indom.min.js"></script>'><scriptsrc="./js/indom.min.js"></script><!-- or via CDN --><scriptsrc="https://cdn.jsdelivr.net/npm/indom@latest/dist/indom.min.js"></script>
Your own scripts can then use InDom immediately, or you can wrap logic in InDom.onReady() to ensure the DOM is fully loaded.
You can also use InDom with your bundler of choice. Just make sure the InDom library file is loaded before the file that first uses it.
To enable full autocomplete and inline documentation in your IDE, add a reference comment at the top of your script:
/// <reference path="./dist/indom.js" />
This enables autocomplete and JSDoc hints in most IDEs while typing.
Replace /dist/indom.js with the actual location of your indom.js file (it doesn’t have to be in a production folder)
InDom is fully compatible with ES Modules environments.
You can import it directly in supported browsers or through any bundler that understands ESM syntax.
Example (browser import):
<scripttype="module">import{InDom,InDomArray,$1,$a,$id,$n,$v}from'./dist/indom.esm.min.js';InDom.onReady(()=>{$1('.example').setHtml('Hello from InDom!');});</script>
InDom ships with ES2022-compatible type definitions in dist/indom.d.ts and the original TypeScript source in src/indom.ts.
Option 1 — Editor hints in plain JavaScript (no TS compile)
Use a triple-slash reference to enable IntelliSense/JSDoc in your editor:
/// <reference path="./dist/indom.d.ts" />
This is for editor tooling only. For runtime, include the JS build as shown in Plain JavaScript.
If you are compiling TypeScript, prefer imports (see Option 2), as the type definitions themselves use named exports, not global variables.
Option 2 — TypeScript projects (tsc / bundlers)
Import the named exports from the ESM build (or from the TypeScript source):
// from the package ESM buildimport{InDom,InDomArray,$1,$a,$id,$n,$v}from'indom/dist/indom.esm.js';// or from a local copy of the ESM build// import { InDom, InDomArray, $1, $a, $id, $n, $v } from './dist/indom.esm.js';// or compile directly from the TypeScript source// import { InDom, InDomArray, $1, $a, $id, $n, $v } from './src/indom.ts';
All exports are named — import only what you need, or import the full library as InDom.
Tree-shaking works naturally in modern bundlers.
InDom can be freely extended or modified — its core methods follow consistent patterns and naming, making it safe to build custom helpers or override behavior as needed.
Extend Example:
/** * Extend InDom with a custom helper: scrollTop getter / setter * Handles both Element and Document nodes, enforces integer input, * and uses requestAnimationFrame for smooth scrolling. */InDom.prototype.scrollTop=function(y,smooth){if(!y){// document itself doesn't have scrollTop only its documentElementreturn(this.el()instanceofDocument ? document.documentElement : this.el()).scrollTop;}if(!Number.isInteger(y)){// accept only an integer for y to scrollthrownewTypeError('Expected an integer to scrollTop, got '+y);}requestAnimationFrame(()=>{// if it is document it is better to scroll to window instead of its documentElement(this.el()instanceofDocument ? window : this.el()).scrollTo({top: y,behavior: smooth===true ? 'smooth' : 'instant'});});};// usage example $1(".example>div").onClick(()=>$n(document).scrollTop(100,true));
Modify Example:
// Modify InDom with custom onClick method that throttles clicks and touchstartInDom.prototype.onClick=function(fn,opts){// If no function provided, delegate to standard click eventif(!fn){returnthis.on('click');}letcanClick=true;// Throttle flag to prevent rapid repeated triggers// Attach handlers to both 'click' and 'touchstart' eventsreturnthis.on(['click','touchstart'],(n,e)=>{if(!canClick){return;// Ignore rapid repeats during throttle period}canClick=false;// Disable further triggerssetTimeout(()=>canClick=true,300);// Re-enable after 300msfn(n,e);// Execute user-provided callback with InDom object and event},opts);};// Bulk version for InDomArray - applies onClick to all InDom objects in the arrayInDomArray.prototype.onClick=function(fn,opts){constfnArr=newArray(this.length);for(leti=0;i<this.length;i++){// Apply onClick to each individual InDom object and store handler referencesfnArr[i]=this[i].onClick(fn,opts);}returnfnArr;// Return array of handler references for cleanup};// Example usage:constexampleDivs=$a(".example>div");// Apply custom onClick behavior - logs HTML content and event detailsconstclickHandlers=exampleDivs.onClick((n,e)=>{console.log(['.example>div onClick','html:',n.getHtml(),'event:',e]);});// Example: Removing event handlers (cleanup)// Method 1: Remove ALL click and touchstart listeners from exampleDivsexampleDivs.off('click').off('touchstart');// Method 2: Remove only the specific handlers we createdexampleDivs.off('click',clickHandlers).off('touchstart',clickHandlers);
Enhanced cleanup method — clarity and reusability for the above Modify Example
// Create custom removal methods for easier management// Remove click/touchstart handlers from a single objectInDom.prototype.removeOnClick=function(fn){this.off('click',fn);// Remove click handlerthis.off('touchstart',fn);// Remove touchstart handler};// Remove click/touchstart handlers from multiple InDom objects in an arrayInDomArray.prototype.removeOnClick=function(fnArr){consthasFunctions=Array.isArray(fnArr);// Validate that the handler array matches the number of InDom objectsif(hasFunctions&&fnArr.length!==this.length){thrownewRangeError(`Expected ${this.length} handlers, got ${fnArr.length}`);}// Remove handlers from each InDom object individuallyfor(leti=0;i<this.length;i++){this[i].removeOnClick(hasFunctions ? fnArr[i] : undefined);}};// Usage examples for custom cleanup methods:// Removes all 'click' and 'touchstart' events from exampleDivsexampleDivs.removeOnClick();// Removes only the specific clickHandlers we created earlierexampleDivs.removeOnClick(clickHandlers);