jessquery
Version:
Modern JavaScript is pretty good, but typing document.querySelector() is a pain. This is a tiny library that makes DOM manipulation easy. jQuery is around 80kb (30kb gzipped), while this is only around 8kb (3.5kb gzipped). Lots of JSDoc comments so it's s
881 lines (840 loc) • 120 kB
TypeScript
declare module "jessquery" {
/**
* A proxy covering a single HTML element that allows you to chain methods sequentially (including asynchronous tasks) and then execute them all at once.
*
* Methods:
* - {@link DomProxy.on} - Add an event listener to the element
* - {@link DomProxy.once} - Add an event listener that will only fire once to the element
* - {@link DomProxy.off} - Remove an event listener from the element
* - {@link DomProxy.delegate} - Delegate an event listener to the element
* - {@link DomProxy.html} - Change the HTML of the element with an **UNSANITIZED** string of new HTML. This is useful if you want to add a script tag or something. If you want to sanitize the HTML, use {@link DomProxy.sanitize} instead.
* - {@link DomProxy.sanitize} - Sanitizes a string of untrusted HTML using the setHTML API, and sets the sanitized HTML to the provided element. Offers protection against XSS attacks.
* - {@link DomProxy.text} - Change the text of the element while retaining the HTML.
* - {@link DomProxy.val} - Sets the value of a DOM element based on its type. For form elements such as inputs, textareas, and selects, the appropriate property (e.g., `value`, `checked`) will be adjusted. For other elements, the `textContent` property will be set.
* - {@link DomProxy.css} - Add a CSS Rule to the element. If the first argument is an object, it will be treated as a map of CSS properties and values. Otherwise, it will be treated as a single CSS property and the second argument will be treated as the value.
* - {@link DomProxy.addStylesheet} - Add a stylesheet to the ENTIRE DOCUMENT (this is useful for things like :hover styles). Got a good idea for how to make this scoped to a single element? Open a PR!
* - {@link DomProxy.addClass} - Add a class to the element
* - {@link DomProxy.removeClass} - Remove a class from the element
* - {@link DomProxy.toggleClass} - Toggle a class on the element
* - {@link DomProxy.set} - Set an attribute on the element. If the value is undefined, it will be set to `""`, which is useful for boolean attributes like disabled or hidden.
* - {@link DomProxy.unset} - Remove an attribute from the element
* - {@link DomProxy.toggle} - Toggle an attribute on the element
* - {@link DomProxy.data} - Set a data attribute on the element.
* - {@link DomProxy.attach} - Attaches children to the element based on the provided options.
* - {@link DomProxy.cloneTo} - Clone of the element to a new parent element in the DOM. By default, it is appended inside the new parent element, but you change change this with the `position` option. The original element remains in its current location. If you want to move the element instead of cloning it, use {@link DomProxy.moveTo}.
* - {@link DomProxy.moveTo} - Move the element to a new parent element in the DOM. By default, it is appended inside the new parent element, but you change change this with the `position` option. The original element is removed from its current location. If you want to clone the element instead of moving it, use {@link DomProxy.cloneTo}.
* - {@link DomProxy.become} - Replace the element with a new element. By default, the new element is cloned from its original location. To permanentaly remove it instead, set the mode to 'move'.
* - {@link DomProxy.purge} - Remove the element from the DOM entirely
* - {@link DomProxy.send} - Sends an HTTP request using the current element as the body of the request unless otherwise specified.
* - {@link DomProxy.do} - Executes an asynchronous function and waits for it to resolve before continuing the chain (can be synchronous too)
* - {@link DomProxy.defer} - Schedules a function for deferred execution on the element. This will push the operation to the very end of the internal event loop.
* - {@link DomProxy.fromJSON} - Fetches a JSON resource from the provided URL, applies a transformation function on it and the proxy's target element.
* - {@link DomProxy.fromHTML} - Fetches an HTML resource from the provided URL and inserts it into the proxy's target element.
* - {@link DomProxy.fromStream} - Fetches a stream resource from the provided URL and inserts it into the proxy's target element. Can also use Server-Sent Events.
* - {@link DomProxy.transition} - Animate the element using the WAAPI. The queue will wait for the animation to complete before continuing.
* - {@link DomProxy.wait} - Sets a timeout for the given number of milliseconds and waits for it to resolve before continuing the chain
* - {@link DomProxy.next} - Switch to the nextElementSibling in the middle of a chain
* - {@link DomProxy.prev} - Switch to the previousElementSibling in the middle of a chain
* - {@link DomProxy.first} - Switch to the firstChild of the element in the middle of a chain
* - {@link DomProxy.last} - Switch to the lastChild of the element in the middle of a chain
* - {@link DomProxy.parent} - Switch to the parentElement in the middle of a chain
* - {@link DomProxy.ancestor} - Switch to the closest ancestor matching a selector in the middle of a chain
* - {@link DomProxy.pick} - Switch to the first descendant matching a selector in the middle of a chain
* - {@link DomProxy.pickAll} - Switch to a collection of all the descendants that match a selector in the middle of a chain
* - {@link DomProxy.kids} - Switch to a collection of all the children of the element in the middle of a chain
* - {@link DomProxy.siblings} - Switch to a collection of all the siblings of the element in the middle of a chain
* - {@link DomProxy.if} - Uses a predicate function to conditionally apply methods to the proxy in the middle of the chain.
* - {@link DomProxy.takeWhile} - Uses a predicate function to determine whether the element should remain in the proxy. If the predicate returns false, the proxy will be emptied and no further methods will be executed.
* - {@link DomProxy.refresh} - Refresh the proxy to the original target element (what you selected with $).
*/
export type DomProxy<T extends HTMLElement = HTMLElement> = T & {
/** Add an event listener to the element.
* @param ev The event name
* @param fn The event listener
* @returns This {@link DomProxy}
* @example
* $('button').on('click', () => console.log('clicked'))
*/
on: (ev: string, fn: EventListenerOrEventListenerObject) => DomProxy<T>
/** Add an event listener that will only fire once to the element
* @param ev The event name
* @param fn The event listener
* @returns This {@link DomProxy}
* @example
* $('button').once('click', () => console.log('clicked'))
* // The event listener will only fire once
*/
once: (ev: string, fn: EventListenerOrEventListenerObject) => DomProxy<T>
/** Remove an event listener from the element
* @param ev The event name
* @param fn The event listener
* @returns This {@link DomProxy}
* @example
* $('button').off('click', clickHandler)
* // The event listener will no longer fire
*/
off: (ev: string, fn: EventListenerOrEventListenerObject) => DomProxy<T>
/** Delegate an event listener to the element
* @param event The event name
* @param subSelector The sub-selector
* @param handler The event handler
* @returns This {@link DomProxy}
* @example
* $('.container').delegate('click', '.buttons', (e) => console.log('Button clicked'))
*/
delegate: (
event: string,
subSelector: string,
handler: EventListenerOrEventListenerObject
) => DomProxy<T>
/** Change the HTML of the element with an **UNSANITIZED** string of new HTML. If you want to sanitize the HTML, use {@link DomProxy.sanitize} instead.
*
* - By default, only the element's children will be replaced (innerHTML). If you want to replace the element itself (outerHTML), set the second argument to true.
*
* @param newHtml The new HTML
* @param outerHTML Whether to replace the element itself or just its children
* @returns This {@link DomProxy}
* @example
* $('button').html('<span>Click me!</span>')
* // <button><span>Click me!</span></button>
*
* @example
* $('button').html('<span>Click me!</span>', true)
* // <span>Click me!</span>
*/
html: (newHtml: string, outerHTML?: boolean) => DomProxy<T>
/**
* Sanitizes a string of untrusted HTML using the setHTML API, and sets the sanitized HTML to the provided element.
* Offers protection against XSS attacks.
*
* @param {string} html - Untrusted HTML string to sanitize and set.
* @param {Sanitizer} [sanitizer] - An instance of Sanitizer to customize the sanitization. Defaults to a new Sanitizer() with default configuration.
* @returns {DomProxy} - The provided element with the sanitized content set.
*
* @example
* const maliciousHTML = `<span>Safe Content</span>
* <script>alert("hacked!")</script>`;
* const sanitizer = new Sanitizer({
* allowElements: ['span']
* });
* $('button').sanitize(maliciousHTML, { sanitizer });
* // The button will only contain the 'Safe Content' span;
* // Any scripts (or other unwanted tags) will be removed.
* // Only span elements will be allowed.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML}
*/
sanitize: (html: string, options: SetHTMLOptions) => DomProxy<T>
/** Change the text of the element while retaining the HTML.
* @param newText The new text
* @returns This {@link DomProxy}
* @example
* $('button').text('Click me!')
*/
text: (newText: string) => DomProxy<T>
/**
* Sets the value of a DOM element based on its type. For form elements such as inputs, textareas, and selects, the appropriate property (e.g., `value`, `checked`) will be adjusted. For other elements, the `textContent` property will be set.
* @param newValue - The value to be set. This can be a string, number, an array for multi-selects, a FileList for file inputs, or a boolean for checkboxes.
* - For `input[type="checkbox"]`: A truthy value sets it to checked, otherwise unchecked.
* - For `input[type="radio"]`: If the `newValue` matches the input's value, it's checked.
* - For `input[type="file"]`: Sets the input's `files` property (expects a FileList or similar).
* - For `select[multiple]`: Expects an array of values to select multiple options.
* @returns This {@link DomProxy}.
* @example
* $('input[type="text"]').val('New Val')
* $('input[type="checkbox"]').val(true)
* $('input[type="radio"]').val('radio1')
* $('input[type="file"]').val(myFileList)
* $('select[multiple]').val(['option1', 'option2'])
*/
val: (
newValue: string | number | (string | number)[] | FileList
) => DomProxy<T>
/** Add a CSS Rule to the element. If the first argument is an object, it will be treated as a map of CSS properties and values. Otherwise, it will be treated as a single CSS property and the second argument will be treated as the value.
* @param propOrObj The CSS property or object containing CSS properties and values
* @param value The CSS value
* @returns This {@link DomProxy}
* @example
* $('button').css('color', 'red')
* OR
* $('button').css({ color: 'red', backgroundColor: 'blue' })
*/
css: (
propOrObj: string | { [key: string]: string | number },
value?: string
) => DomProxy<T>
/** Add a stylesheet to the ENTIRE DOCUMENT (this is useful for things like :hover styles). Got a good idea for how to make this scoped to a single element? Open a PR!
*
* - This should be used only when you need to do something like set a pseudo-class on the fly. Otherwise, just write a real stylesheet.
*
* - Got a good idea for how to make this scoped to a single element? Open a PR! I was thinking something like the `@scope` rule being automatically inserted, but that's still only in Chromium browsers.
*
* {@link https://css.oddbird.net/scope/explainer/}
*
* - Right now, every rule will be given an !important flag, so it will override any existing styles. This is drastic I know, but it's the only way to make this work if you're creating other inline styles.
* @param css The CSS to add
* @returns This {@link DomProxy}
* @example
* $('button').stylesheet('button:hover { color: red; }')
* // Now all buttons on the page will turn red when hovered
*/
addStylesheet: (css: string) => DomProxy<T>
/** Add one or more classes to the element. Just add a comma in between each class name, or use spread syntax from an array.
* @param className The class name
* @returns This {@link DomProxy}
* @example
* $('button').addClass('btn')
*
* @example
* const classes = ['btn', 'btn-primary']
* $('button').addClass(...classes)
*
* @example
* $('button').addClass('btn', 'btn-primary')
*/
addClass: (className: string) => DomProxy<T>
/** Remove one or more classes from the element. Just add a comma in between each class name, or use spread syntax from an array.
* @param className The class name
* @returns This {@link DomProxy}
* @example
* $('button').removeClass('btn')
*
* @example
* const classes = ['btn', 'btn-primary']
* $('button').removeClass(...classes)
*
* @example
* $('button').removeClass('btn', 'btn-primary')
*/
removeClass: (className: string) => DomProxy<T>
/** Toggle a class on the element. If given a second argument, it will be used as a boolean to determine whether to add or remove the class. Otherwise, it will be toggled from whatever it is currently.
* @param className The class name
* @returns This {@link DomProxy}
* @example
* $('button').toggleClass('btn')
*
* @example
* const mediaQuery = window.matchMedia('(max-width: 600px)')
* $('button').toggleClass('lilBtn', mediaQuery.matches)
*/
toggleClass: (className: string) => DomProxy<T>
/** Set one or more attributes on the element. If the first argument is an object, it will be treated as a map of attributes and values. Otherwise, you can pass a string as a key for a single attribute and the second argument will be treated as the value. If the value is undefined, it will be set to `""`, which is useful for boolean attributes like disabled or hidden.
* @param attr The attribute name or object containing attributes and values
* @param value The attribute value (optional)
* @returns This {@link DomProxy}
* @example
* $('button').set('disabled')
* $('button').set('formaction', '/submit')
* $('button').set({ disabled: true, formaction: '/submit' })
*/
set: (
attr: string | { [key: string]: string },
value?: string
) => DomProxy<T>
/** Remove an attribute from the element
* @param attr The attribute name
* @returns This {@link DomProxy}
* @example
* $('button').unset('disabled')
*/
unset: (attr: string) => DomProxy<T>
/** Toggle an attribute on the element. It can take a second argument, which will be used as a boolean to determine whether to add or remove the attribute. Otherwise, it will be toggled from whatever it is currently.
*
* @param attr The attribute name
* @returns This {@link DomProxy}
* @example
* $('button').toggle('disabled')
*
* @example
* let clickedTooManyTimes = false
* $('button').toggle('disabled', clickedTooManyTimes)
*/
toggle: (attr: string, value?: boolean) => DomProxy<T>
/**
* Set a data attribute on the element. If the first argument is an object, it will be treated as a map of data attributes and values. Otherwise, it will be treated as a single data attribute and the second argument will be treated as the value.
* @param key The dataset key
* @param value The corresponding value for the dataset key
* @returns This {@link DomProxy}
* @example
* $('div').data('info', 'extraDetails')
* This implies: element.dataset.info = 'extraDetails'
*/
data: (
key: string | { [key: string]: string },
value?: string
) => DomProxy<T>
/**
* Attaches children to the element based on the provided options.
* The children can be:
* - A string of HTML
* - A CSS selector
* - An HTMLElement
* - A DomProxy
* - An array of any of the above
*
* The position can be:
* - 'append' (default): Adds the children to the end of the element.
* - 'prepend': Adds the children to the beginning of the element.
* - 'before': Adds the children before the element.
* - 'after': Adds the children after the element.
*
* The HTML is sanitized by default, which helps prevent XSS attacks.
* If you want to disable sanitization, set the `sanitize` option to `false`.
*
* @param {...*} children - The children to attach. The last argument can be an options object.
* @param {Object} [options] - The options object.
* @param {('append'|'prepend'|'before'|'after')} [options.position='append'] - Where to attach the children.
* @param {boolean} [options.sanitize=true] - Whether or not to sanitize the HTML.
* @returns This {@link DomProxy}
*
* @example
* $('button').attach('<span>Click me!</span>');
* $('button').attach($('.container'), { position: 'prepend' });
* $('button').attach([$('.container'), '<span>Click me!</span>'], { position: 'before' });
* $('button').attach('<image src="x" onerror="alert(\'hacked!\')">'); // No XSS attack here!
* $('button').attach('<image src="x" onerror="alert(\'hacked!\')">', { sanitize: false }); // XSS attack here!
* @see https://stackoverflow.com/questions/14846506/append-prepend-after-and-before
*/
attach: (...children: ChildInput[]) => DomProxy<T>
/**
* Clone of the element to a new parent element in the DOM. By default, it is appended inside the new parent element, but you change change this with the `position` option. The original element remains in its current location. If you want to move the element instead of cloning it, use `moveTo`.
* @param parentSelector CSS selector for the parent element to which the cloned element will be added.
* @param options Optional configuration for the function behavior.
* @param {boolean} [options.all=false] If set to true, the element will be cloned or moved to all elements matching the parentSelector.
* @param {"before" | "after" | "prepend" | "append"} [options.position="append"] If not selected, the element will be placed inside the parent element after any existing children. If you want it right outside of the parent element, use 'before' or 'after'. If you want it to be the first child, use 'prepend'.
* @returns This {@link DomProxy}
* @example
* $('button').cloneTo('.target') // Clones and appends to .target (default behavior)
* $('button').cloneTo('.target', { position: 'prepend' }) // Clones and prepends to .target as first child
* $('button').cloneTo('.target', { all: true }) // Clones and appends to all .target elements
* $('button').cloneTo('.target', { all: true, position: 'before' }) // Clones and adds element just before all .target elements
* @see https://stackoverflow.com/questions/14846506/append-prepend-after-and-before
*/
cloneTo: (
parentSelector: string,
options?: MoveOrCloneOptions
) => DomProxy<T>
/**
* Move the element to a new parent element in the DOM. By default, it is appended inside the new parent element, but you change change this with the `position` option. The original element is removed from its current location. The `all` option is technically available, but it will simply use the last element in the collection. This is because you can only move an element to one place at a time. If you want to clone the element instead of moving it, use `cloneTo`.
* @param parentSelector CSS selector for the parent element to which the element will be moved.
* @param options Optional configuration for the function behavior.
* @param {"before" | "after" | "prepend" | "append"} [options.position="append"] If not selected, the element will be placed inside the parent element after any existing children. If you want it right outside of the parent element, use 'before' or 'after'. If you want it to be the first child, use 'prepend'.
* @returns This {@link DomProxy}
* @example
* $('button').moveTo('.target') // Moves and appends to .target (default behavior)
* $('button').moveTo('.target', { position: 'prepend' }) // Moves and prepends to .target as first child
* @see https://stackoverflow.com/questions/14846506/append-prepend-after-and-before
*/
moveTo: (
parentSelector: string,
options?: MoveOrCloneOptions
) => DomProxy<T>
/**
* The $.become method is used to replace a single element with a different element from elsewhere in the DOM.
*
* Under the hood, it utilizes the native `replaceWith` method but adds extra layers of functionality. The replacement can be a simple HTMLElement, an array of HTMLElements, or another DomProxy instance.
*
* - **Mode**:
*
* - *clone* (default) - This makes a copy of the replacement element to use for the DomProxy. This clone includes the element, its attributes, and all its child nodes, but does not include event listeners. The original element is left untouched.
*
* - *move* - This moves the replacement element to the original element's position. The original element is removed from the DOM. This is the same as calling `replaceWith` directly.
*
* @param {HTMLElement|Array<HTMLElement>|DomProxy} replacements - Element(s) or DomProxy that will replace the current element.
* @param {Object} [options] - Replacement options.
* @param {"move"|"clone"} [options.mode="clone"] - Decides if the new element replaces the existing element as-is ('move') or as a deep clone ('clone').
* @returns {DomProxy} - A DomProxy instance that wraps the new element(s), enabling chainable methods.
*
* @example
* // Replaces div with newElement, literally moving it to the original div's position.
* $('div').become(newElement, {mode: "move"})
*
* @example
* // Replaces div with a deep clone of newElement, leaving the original newElement untouched.
* $('div').become(newElement, {mode: "clone"})
*
* @example
* // Takes a DomProxyCollection as the replacement. The first element is cloned as the replacement.
* $('#button').become($$('.otherButtons'))
*/
become: (
replacements: HTMLElement | Array<HTMLElement> | DomProxy,
options?: { mode?: "move" | "clone" }
) => DomProxy<T>
/** Remove the element from the DOM entirely. This is a light wrapper around `remove`.
* @returns This {@link DomProxy}
* @example
* $('button').purge()
*/
purge: () => DomProxy<T>
/**
* Sends an HTTP request using the current element as the body of the request unless otherwise specified.
*
* None of the options are required-- not even the URL.
*
* If you do not provide a URL the method will:
* - First, look to see if it's in a form with an action property.
* - Next, it will look to see if the element is a button with a formaction property.
* - Next, it will try to see if the element is part of a form that has an action property.
* - Finally, it will use the current URL.
*
* Unless the `body` option is provided, it will be created automatically based on the element type:
* - If it's a form, the entire form will be serialized normally using the formData API unless a custom serializer is provided.
* - If it's an input, textarea, or select, the value will be used.
* - If it isn't a form or one of the above elements, we will check to see if the element has a form property or one can be found with the `closest` method. If so, we will serialize the form using the formData API unless a custom serializer is provided.
* - If none of the above, the element's textContent will be used.
*
* If the `json` option is set to true, the request will be sent as JSON and the response will be parsed as JSON.
*
* Otherwise, the request will be sent as FormData and the response will be parsed as text.
*
* - The serializer option can be used to provide a custom function to serialize the form. It will be passed the element as an argument and should return the serialized form data.
* - This will probably break if you don't return a FormData object from your serializer, but I haven't tested it.
*
* @param {Element} element - The DOM element to gather data from and determine type of the request.
* @param {object} options - Configuration for the AJAX request.
* @param {string} [options.url] - Endpoint for the request. Defaults to element’s action/formAction.
* @param {string} [options.method="POST"] - HTTP method for the request.
* @param {boolean} [options.json=false] - If true, sends and expects JSON, otherwise sends FormData.
* @param {any} [options.body] - Body of the request. Defaults to form/input field content.
* @param {Event} [options.event] - Event object to prevent default behavior.
* @param {function} [options.serializer] - Custom function to serialize form data. Will receive the ENTIRE element as an argument.
* @param {string} [options.error] - Error message for display/update on failed request.
* @param {string} [options.fallback] - Message/content to display during request processing.
* @param {function} [options.onError] - Callback for request errors.
* @param {function} [options.onSuccess] - Callback for all requests, triggered finally.
*
* @returns {DomProxyCollection} - The matched elements with the updated content.
*
* @example
* // Send a JSON request using input value, providing fallback and completion messages.
* $('#myInput').send(myElement, {
* url: '/api/data',
* json: true,
* fallback: 'Loading...',
* onSuccess: (data) => console.log('Received:', data),
* onError: (error) => console.log('Error occurred:', error),
* });
*
* @example
* // Send form data using a custom serializer.
* $('#myForm').send(myElement, {
* url: '/api/data',
* serializer: (form) => {
* const formData = new FormData(form);
* formData.append('extra', 'data');
* return formData;
* },
* });
*/
send: (
options: {
url?: string
json?: boolean
event?: Event
serializer?: (HTMLElement) => FormData
} & FetchOptions
) => DomProxy<T>
/**
* Executes a function and waits for it to resolve before continuing the chain. Basically, if you need to do something and there isn't a good method for it already, just use this.
* - This can be used to do conditional logic, although the `if` or `takeWhile` methods can make this easier.
* - This function can be asynchronous. Either way, any methods called on the queue after this point will reflect changes made to the element after the function is called.
* - The only exception is if you use the wait method inside of the function. Unfortunately, this will cause the external queue to continue on without waiting for the timeout to finish.
* - To call arbitrary timeouts inside of the do method, you can simply use the old `await new Promise((resolve) => setTimeout(resolve, 1000))` trick.
* @param fn The callback. This can receive the element as an argument.
* @returns This {@link DomProxy}
* @example
* $('button')
* .css('color', 'red')
* .do(async (el) => { // The element is passed as an argument
* const response = await fetch('/api')
* const data = await response.json()
* el.text(data.message) // All the methods are still available
* })
* .css('color', 'blue')
*
* @example
* const fetchDog = async (el) => {
* const response = await fetch("https://dog.ceo/api/breeds/image/random")
* const data = await response.json()
* el.set("src", data.message)
* }
*
* button.on("click", () => {
* display.html(`<img />`, true).do(fetchDog)
* })
*/
do: (fn: (el: DomProxy<T>) => Promise<void> | void) => DomProxy<T>
/**
* Schedules a function for deferred execution on the element. This will push the operation to the very end of the internal event loop.
*
* This captures the element's state at the moment it is called which is usually **at the very beginning of the queue**. Generally, you will have changed things since then, so it should not be mixed with:
* - context switching methods like `next`, `prev`, `first`, `last`, `parent`, `ancestor`, `pick`, `pickAll`, `kids`, or `siblings`.
* - conditional methods like `if` or `takeWhile`
*
* Usually, everything will happen in sequence anyways. Given the predictability of each queue, `defer` has limited use cases and should be used sparingly. The whole point of JessQuery is to make things predictable, so you should just put the function at the end of the chain if you can.
*
* The only problem is if you set up an event listener using the same variable that has lots of queued behavior-- especially calls to the `wait` method. Just wrap the `wait` call and everything after it in `defer` to ensure that event handlers don't get stuck behind these in the queue. Or, do the smart thing and just make a new variable.
*
* Honestly, I'm not sure if this even makes much sense. I just spent a bunch of time building a crazy queue system, and I feel like I need to expose it. If you have any ideas for how to make this more useful, please open an issue or PR.
*
* @param {function(DomProxy): void} fn - The function to be deferred for later execution. It will be passed the DomProxy instance as an argument.
* @returns {DomProxy} - The DomProxy instance, allowing for method chaining.
*
* @example
* button
* .on('click', () => {
* button.text('Clicked!'); // This will be delayed for the first second
* })
* .wait(1000)
* .text('Click me!')
*
* button
* .on('click', () => {
* button.text('Clicked!'); // This will happen immediately
* })
* .defer((el) => el.wait(1000).text('Click me!'))
*
* // Take note that this is only an issue if you're using the same variable for both the event handler and the queued behavior.
* button
* .on('click', () => {
* $('button').text('Clicked!'); // No problem here. This gets its own queue.
* })
* .wait(1000)
*
* @example
* // Please limit the use of this method anywhere other than the end of a chain. That's confusing.
* // Only use it for race conditions or other edge cases.
* display
* .defer((el) => el.css("color", "blue"))
* .text("HALF A SECOND OF GLORY") // Text is black
* .wait(500).text("Hello, world!") // Text is red
* .css("color", "red"))
* .wait(500).text("Goodbye, world!") // Text is blue
* // This is missing the point of JessQuery. Just put each method in sequence.
*/
defer: (fn: (element: DomProxy<T>) => Promise<void> | void) => DomProxy<T>
/**
* Fetches a JSON resource from the provided URL and applies a transformation function which uses the fetched JSON and the proxy's target element as arguments.
*
* **While you get access to the element inside, this will be an entirely new queue! So, nothing queued after this method will wait for it.**
*
* - This might seem like a huge issue, but all you have to do is make sure that you do everything inside of the function. You'll have full access to the element, but the nesting is admittedly not ideal.
* @param {string} url - The URL to fetch the JSON from.
* @param {function} transformFunc - The function that applies transformations on the fetched JSON and the proxy's target element.
* @param {FetchOptions} [options={}] - Options for the fetch operation.
* @param {function} [options.error] - A message to display if the fetch fails. Will not be displayed if the `onError` callback is provided.
* @param {function} [options.onError] - A callback to execute if the fetch fails.
* @param {function} [options.onSuccess] - A callback to execute when the fetch is complete.
* @param {function} [options.onWait] - A callback to execute while the fetch is in progress.
* @param {function} [options.waitTime] - The amount of time to wait before executing the onWait callback. Defaults to 250ms.
* @returns This {@link DomProxy}
* @example
* $('#item').fromJSON('/api/data', (element, json) => {
* element.text(json.value);
* });
* @example
* $('#employee')
* .fromJSON('/api/data',
* (element, json) => {
* const { name, title, salary } = json;
* element
* .html(`
* <h1>${name}</h1>
* <h2>${title}</h2>
* <p>${salary}</p>`);
* })
* .css('color', 'red'); // DOES NOT WAIT FOR FETCH TO COMPLETE
* @example
* $('#news-item')
* .fromJSON('/api/news-item', (element, json) => {
* const { title, summary } = json;
*
* element
* .html(`<h1>${title}</h1>
* <p>${summary}</p>`);
* .css('color', 'red'); // THIS IS FINE
* },
*/
fromJSON: (
url: string,
transformFunc: TransformFunction<DomProxy<T>>,
options?: FetchOptions
) => DomProxy<T>
/**
* Fetches an HTML resource from the provided URL and inserts it into the proxy's target element.
* @param {string} url - The URL to fetch the HTML from.
* @param {FetchOptions} [options={}] - Options for the fetch operation.
* @param {function} [options.error] - A message to display if the fetch fails. Will not be displayed if the `onError` callback is provided.
* @param {function} [options.onError] - A callback to execute if the fetch fails.
* @param {function} [options.onSuccess] - A callback to execute when the fetch is complete.
* @param {function} [options.onWait] - A callback to execute while the fetch is in progress.
* @param {function} [options.waitTime] - The amount of time to wait before executing the onWait callback. Defaults to 250ms.
* @param {boolean} [options.runScripts=false] - Determines if scripts in the fetched HTML should be executed. Defaults to false.
* @param {boolean} [options.sanitize=true] - Determines if the fetched HTML should be sanitized before insertion. Defaults to true.
* @param {function} [options.sanitizer] - A custom sanitizer to use for sanitization. {@link https://developer.mozilla.org/en-US/docs/Web/API/Sanitizer/Sanitizer}
* @returns This {@link DomProxy}
*
* @returns This {@link DomProxy}
* @example
* $('#template').fromHTML('/template.html');
* @example
* $('#update').fromHTML('/update.html', { fallback: 'Loading update...', error: 'Failed to load update!' });
* @example
* $('#content').fromHTML('/malicious-content.html', { sanitize: false });
*/
fromHTML: (url: string, options?: FetchOptions) => DomProxy<T>
/**
* Dynamically fetches data from the provided URL and updates a single DOM element using a stream or Server-Sent Event (SSE).
*
* - This includes all the same options as the `fromHTML` and `fromJSON` methods for normal streams, but with a few caveats for SSEs.
* - the `onSuccess` callback will be invoked after each message is received, and it will receive the last event as an argument.
* - SSE's have two additional options, `add` and `onTop`, which determine how the new data is added to the existing content.
*
* @param {string} url - The source URL to retrieve data from.
* @param {object} [options={}] - Configurations for the stream operation.
* @param {boolean} [options.sse=false] - If set to true, listens for messages using SSE. Otherwise, it will make an HTTP request using the ReadableStream interface included in the Fetch API.
* @param {boolean} [options.add=false] - For SSE, if set to true, appends new data to existing content. Otherwise, it replaces the content.
* @param {boolean} [options.toTop=false] - For SSE, if set to true, prepends new data to the top of existing content. Otherwise, it appends to the bottom.
* @param {string} [options.error] - Message to display if the fetch fails. Will not be displayed if the `onError` callback is provided.
* @param {string} [options.onError] - Callback to execute if the fetch fails.
* @param {function} [options.onSuccess] - Callback invoked upon stream completion or after each SSE message is received. For SSEs, receives the last event as an argument.
* @param {string} [options.onWait] - Callback to execute while the fetch is in progress.
* @param {string} [options.waitTime] - Amount of time to wait before executing the onWait callback. Defaults to 250ms.
* @param {string} [options.runScripts=false] - Determines if scripts in the fetched HTML should be executed. Defaults to false.
* @param {boolean} [options.sanitize=true] - If set to true, sanitizes incoming HTML content before injecting it into the DOM to prevent potential XSS attacks.
* @param {function} [options.sanitizer] - A custom sanitizer to use for sanitization. {@link https://developer.mozilla.org/en-US/docs/Web/API/Sanitizer/Sanitizer}
* @returns {DomProxy} - Returns the instance of DomProxy for chaining.
*
* @example
* // Streaming generic data into a single element.
* $('#content').fromStream('/api/data');
*
* // Streaming with SSE and appending data to existing content.
* $('#liveFeed').fromStream('/api/live', {
* sse: true,
* add: true,
* onSuccess: (data) => console.log('New data received:', data)
* });
*/
fromStream: (
url: string,
options?: {
sse?: boolean
add?: boolean
toTop?: boolean
onError?: (error: string) => void
onSuccess?: (data: any) => void
onWait?: () => void
waitTime?: number
runScripts?: boolean
sanitize?: boolean
sanitizer?: Sanitizer
}
) => DomProxy<T>
/** Animate the element using the WAAPI. The queue will wait for the animation to complete before continuing.
*
* - Returns the proxy so that you can continue chaining methods. If you need to return the animation object, just use the `animate` method directly.
* - Remember, this method is blocking, so watch out for any event handlers using the same variable.
*
* **Keyframes**:
*
* The keyframes can be specified in a few different ways:
*
* - *Array* - An array of objects (keyframes) consisting of properties and values to iterate over. This is the canonical format returned by the `getKeyframes()` method. Each keyframe is an object containing the CSS properties and values to animate. The first keyframe is the "from" keyframe, and the last keyframe is the "to" keyframe. The number of keyframes is arbitrary.
*
* Example:
*
* ```js
* $('button').animate([
* { opacity: 0, color: '#fff' },
* { opacity: 1, color: '#000' }
* ], 2000)
* ```
* - *Object* - An object containing the CSS properties and values to animate. The keys are the CSS properties, and the values are the CSS values. The object is treated as a single keyframe. The key is the property to animate, and the value is an array of values to iterate over. The number of elements in each array does not need to be equal. The provided values will be spaced out independently.
*
* Example:
*
* ```js
* $('button').animate({
* opacity: [0, 1], // [ from, to ]
* color: ['#fff', '#000'] // [ from, to ]
* }, 2000)
* ```
* **Options**:
*
* Either an integer representing the animation's duration (in milliseconds), or an Object containing one or more timing properties described in the `KeyframeEffect()` options parameter and/or the following options:
*
* - *Number* - The duration of the animation in milliseconds.
* - *Object* - An object containing the animation options. The options are the same as the options for the `KeyframeEffect` constructor.
*
* - *id* - A string with which to reference the animation.
* - *rangeStart* - Specifies the start of an animation's attachment range along its timeline, i.e. where along the timeline an animation will start. The JavaScript equivalent of the CSS `animation-range-start` property. `rangeStart` can take the same value types as `rangeEnd`.
* - *rangeEnd* - Specifies the end of an animation's attachment range along its timeline, i.e. where along the timeline an animation will end. The JavaScript equivalent of the CSS `animation-range-end` property.
* - *timeline* - The `AnimationTimeline` to associate with the animation. Defaults to `Document.timeline`. The JavaScript equivalent of the CSS `animation-timeline` property.
*
* @param keyframes The keyframes to animate
* @param options The animation options
* @returns This {@link DomProxy}
* @example
* $('button').transition([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 })
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
*/
transition: (
keyframes: Keyframe[] | PropertyIndexedKeyframes,
options: KeyframeAnimationOptions
) => DomProxy<T>
/** Sets a timeout for the given number of milliseconds and waits for it to resolve before continuing the chain
* @param ms The number of milliseconds to wait
* @returns This {@link DomProxy}
* @example
* $('button').css('color', 'red').wait(1000).css('color', 'blue')
*/
wait: (ms: number) => DomProxy<T>
/** Switch to the nextElementSibling in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxy} from the next element
* @example
* $('button')
* .css('color', 'red')
* .next()
* .css('color', 'blue')
* // the next button will turn blue
* // the original button will remain red
*/
next: () => DomProxy<T>
/** Switch to the previousElementSibling in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxy} from the previous element
* @example
* $('button')
* .css('color', 'red')
* .prev()
* .css('color', 'blue')
* // the previous button will turn blue
* // the original button will remain red
*/
prev: () => DomProxy<T>
/** Switch to the firstChild of the element in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxy} from the firstChild element
* @example
* $('container')
* .css('color', 'red')
* .first()
* .css('color', 'blue')
*/
first: () => DomProxy<T>
/** Switch to the lastChild of the element in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxy} from the lastChild element
* @example
* $('container')
* .css('color', 'red')
* .lastChild()
* .css('color', 'blue')
*/
last: () => DomProxy<T>
/** Switch to the parentElement in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxy} from the parent element
* @example
* $('button')
* .css('color', 'red')
* .parent()
* .css('color', 'blue')
* // the parent of the button will turn blue
* // the button itself will remain red
*/
parent: () => DomProxy<T>
/** Switch to the closest ancestor matching a selector in the middle of a chain. Uses the native `closest` method.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @param ancestorSelector The ancestor selector
* @returns A new {@link DomProxy} from the ancestor element
* @example
* $('.buttons').ancestor('.container')
*/
ancestor: (ancestorSelector: string) => DomProxy<T>
/** Switch to the first descendant matching a selector in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @param subSelector The sub-selector
* @returns A new {@link DomProxy} created from the first element matching the sub-selector
* @example
* $('.container').pick('.buttons')
* // Switches to the first element with the class 'buttons' inside the container
*/
pick: (subSelector: string) => DomProxy<T>
/** Switch to a collection of all of the descendants of the element that match a selector in the middle of a chain.
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @param subSelector The sub-selector
* @returns A new {@link DomProxyCollection} created from the descendants matching the sub-selector
* @example
* $('.container').pick('.buttons')
*/
pickAll: (subSelector: string) => DomProxyCollection<T>
/** Switch to the children of the element in the middle of a chain
*
* This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxyCollection} created from the children of the element
* @example
* $('.container')
* .css('color', 'red')
* .kids()
* .css('color', 'blue')
* // All the children of the container will turn blue
* // The container itself will remain red
*/
kids: () => DomProxyCollection<T>
/** Switch to the siblings of the element in the middle of a chain
*
*This will throw an error if the proxy was created as "fixed" (with a second argument of true).
* @returns A new {@link DomProxyCollection} created from the siblings of the element
* @example
* $('button')
* .css('color', 'red')
* .siblings()
* .css('color', 'blue')
* // All the siblings of the button will turn blue
* // The button itself will remain red
*/
siblings: () => DomProxyCollection<T>
/**
* Executes conditional logic on the element based on its current state.
*
* This method operates based on a predicate function that receives the element and returns a boolean. If the predicate returns `true`, the `then` function will be executed. Otherwise, the `or` function will be executed.
*
* It receives an object with three properties.
*
* @param options Object containing the following properties:
* - `is`: The predicate function that receives the element and returns a boolean.
* - `then`: Function to be executed if the predicate returns `true`. It receives the element as an argument.
* - `or`: Function to be executed if the predicate returns `false`. It receives the element as an argument.
* @returns This {@link DomProxy}
*
* @example
* // Simple example
* $('button')
* .if({
* is: (el) => el.text() === 'Click me!',
* then: (el) => el.css('color', 'green'),
* or: (el) => el.css('color', 'red')
* })
*
* @example
* // Complex example
* $('div')
* .if({
* is: (el) => el.hasClass('active'),
* then: (el) => {
* el.addClass('highlight')
* .pick('span').css('font-weight', 'bold');
* },
* or: (el) => {
* el.removeClass('highlight')