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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ayatkevich/query

v0.2.0

Published

A lightweight, chainable, **lazy** DOM manipulation library inspired by jQuery, written in TypeScript. It provides a simple and expressive API for selecting and manipulating DOM elements, with the added benefit of **lazy evaluation**.

Downloads

20

Readme

@ayatkevich/query

A lightweight, chainable, lazy DOM manipulation library inspired by jQuery, written in TypeScript. It provides a simple and expressive API for selecting and manipulating DOM elements, with the added benefit of lazy evaluation.

Table of Contents

Installation

npm install @ayatkevich/query

Usage

Selecting Elements

Import the $ class and create a new instance by passing a CSS selector:

import { $ } from '@ayatkevich/query';

new $('div');

Specifying a Root Element

You can optionally specify a root element for your query. By default, the root is document, but you can pass any Element as the second argument to scope your selector:

const rootElement = document.getElementById('root');
new $('span', rootElement);

This will select all <span> elements within rootElement, ignoring any <span> elements outside of it.

Example:

import { $ } from '@ayatkevich/query';

// HTML structure:
// <div id="root">
//   <span class="inside">Inside Root</span>
// </div>
// <span class="outside">Outside Root</span>

const rootElement = document.getElementById('root');
const elements = new $('span', rootElement).unwrap();

console.log(elements.length); // Output: 1
console.log(elements[0].textContent); // Output: "Inside Root"

Chaining Methods

The library supports method chaining for a fluent API:

new $('div').addClass('active').setAttribute('role', 'button');

Lazy Evaluation

@ayatkevich/query is designed with laziness in mind. Mutations are not immediately applied to the selected elements. Instead, they are deferred and only executed when necessary. This approach can lead to performance improvements by reducing unnecessary DOM manipulations.

Example of Laziness

Consider the following test cases:

import { describe, expect, it } from '@jest/globals';
import { parseHTML } from 'linkedom';
import { $ } from '@ayatkevich/query';

describe('query', () => {
  it('should allow chaining and demonstrate laziness', () => {
    globalThis.document = parseHTML(/* HTML */ `
      <div>Hello</div>
      <div>World</div>
    `).document;

    const chain = new $('div').addClass('foo').addClass('bar');

    // At this point, mutations have not been applied yet due to laziness.

    const [div] = chain; // Accessing the first element applies mutations to it.
    expect(div.classList.contains('foo')).toBe(true);
    expect(div.classList.contains('bar')).toBe(true);

    // The second div has not had mutations applied yet.
    const divs = Array.from(new $('div'));
    expect(divs[1].classList.contains('foo')).toBe(false);
    expect(divs[1].classList.contains('bar')).toBe(false);
  });

  it('should apply mutations to all elements when unwrap() is called', () => {
    globalThis.document = parseHTML(/* HTML */ `
      <div>Hello</div>
      <div>World</div>
    `).document;

    new $('div').addClass('foo').unwrap();

    expect(
      Array.from(document.querySelectorAll('div')).map((element) =>
        Array.from(element.classList)
      )
    ).toEqual([['foo'], ['foo']]);
  });
});

In the first test case, mutations are applied lazily. The classes 'foo' and 'bar' are only added to the first <div> when it's accessed. The second <div> remains unchanged until it's accessed or until unwrap() is called.

In the second test case, unwrap() forces all pending mutations to be applied to every selected element. This is useful when you want to ensure that all mutations take effect immediately.

Async Unwrapping with await

@ayatkevich/query supports async unwrapping using the await syntax. This allows you to apply all pending mutations to all selected elements asynchronously.

Example of Async Unwrapping

import { describe, expect, it } from '@jest/globals';
import { parseHTML } from 'linkedom';
import { $ } from '@ayatkevich/query';

describe('query', () => {
  it('should allow async unwrapping with await syntax', async () => {
    globalThis.document = parseHTML(/* HTML */ `
      <div>Hello</div>
      <div>World</div>
    `).document;

    const query = new $('div').addClass('async-test');

    // At this point, mutations have not been applied due to laziness
    const divs = Array.from(document.querySelectorAll('div'));
    expect(divs[0].classList.contains('async-test')).toBe(false);
    expect(divs[1].classList.contains('async-test')).toBe(false);

    // Use await to unwrap the query
    await query;

    // After awaiting, mutations should be applied to all elements
    const updatedDivs = Array.from(document.querySelectorAll('div'));
    expect(updatedDivs[0].classList.contains('async-test')).toBe(true);
    expect(updatedDivs[1].classList.contains('async-test')).toBe(true);
  });
});

By awaiting the query, you force all pending mutations to be applied to all selected elements. This can be especially useful in asynchronous contexts or when you want to ensure that all mutations are applied before proceeding.

Class Manipulation

  • addClass(className: string): Lazily adds a class to the selected elements.

    new $('div').addClass('highlight');
  • removeClass(className: string): Lazily removes a class from the selected elements.

    new $('div').removeClass('highlight');
  • toggleClass(className: string): Lazily toggles a class on the selected elements.

    new $('div').toggleClass('active');
  • replaceClass(oldClassName: string, newClassName: string): Lazily replaces an existing class with a new one.

    new $('div').replaceClass('old-class', 'new-class');

Attribute Manipulation

  • setAttribute(name: string, value: string): Lazily sets an attribute on the selected elements.

    new $('input').setAttribute('placeholder', 'Enter your name');
  • removeAttribute(name: string): Lazily removes an attribute from the selected elements.

    new $('input').removeAttribute('disabled');
  • toggleAttribute(name: string): Lazily toggles an attribute on the selected elements.

    new $('input').toggleAttribute('readonly');

Data Attributes

  • setData(data: Record<string, string>): Lazily sets data attributes on the selected elements.

    new $('div').setData({ userId: '123', role: 'admin' });
  • removeData(key: string): Lazily removes a data attribute from the selected elements.

    new $('div').removeData('userId');

DOM Tree Manipulation

  • append(node: Node): Lazily appends a node to the selected elements.

    import { html } from '@ayatkevich/query';
    
    new $('div').append(html`<span>Hello World</span>`);
  • prepend(node: Node): Lazily prepends a node to the selected elements.

    new $('div').prepend(html`<span>Start</span>`);
  • remove(): Lazily removes the selected elements from the DOM.

    new $('.obsolete').remove();
  • before(node: Element): Lazily inserts a node before the selected elements.

    new $('div').before(html`<hr />`);
  • after(node: Element): Lazily inserts a node after the selected elements.

    new $('div').after(html`<hr />`);

Event Handling

  • on(event: string, handler: (event: Event) => void): Lazily attaches an event handler to the selected elements.

    new $('button').on('click', (event) => {
      console.log('Button clicked!');
    });
  • once(event: string, handler: (event: Event) => void): Lazily attaches a one-time event handler to the selected elements.

    new $('button').once('click', (event) => {
      console.log('Button clicked once!');
    });

Testing

The library includes a comprehensive test suite using Jest. To run the tests:

npm test

Example Test Case Using Root Element:

import { describe, expect, it } from '@jest/globals';
import { parseHTML } from 'linkedom';
import { $ } from '@ayatkevich/query';

describe('query', () => {
  it('should allow querying within a specific root element', () => {
    globalThis.document = parseHTML(/* HTML */ `
      <div id="root">
        <span class="inside">Inside Root</span>
      </div>
      <span class="outside">Outside Root</span>
    `).document;

    const rootElement = document.getElementById('root')!;
    const query = new $('span', rootElement);

    const elements = query.unwrap();

    expect(elements.length).toBe(1);
    expect(elements[0].textContent).toBe('Inside Root');
  });
});

This test case demonstrates how to use the root argument to scope your queries to a specific element.

License

This project is licensed under the MIT License.