{"id":21310951,"url":"https://github.com/webqit/realdom","last_synced_at":"2025-09-11T16:46:09.842Z","repository":{"id":45712055,"uuid":"514181729","full_name":"webqit/realdom","owner":"webqit","description":"Low-level realtime DOM APIs","archived":false,"fork":false,"pushed_at":"2025-05-20T11:49:49.000Z","size":9039,"stargazers_count":10,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-07T08:23:46.009Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webqit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"ox-harris","patreon":null,"open_collective":"webqit","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2022-07-15T07:49:08.000Z","updated_at":"2025-05-20T11:49:49.000Z","dependencies_parsed_at":"2024-08-26T15:01:36.115Z","dependency_job_id":"d092b524-3d96-43e3-ac14-2003fd025223","html_url":"https://github.com/webqit/realdom","commit_stats":{"total_commits":137,"total_committers":2,"mean_commits":68.5,"dds":"0.45255474452554745","last_synced_commit":"990b079f4075879b018a7b3b7587b56887e1d6d6"},"previous_names":["webqit/dom"],"tags_count":59,"template":false,"template_full_name":null,"purl":"pkg:github/webqit/realdom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webqit%2Frealdom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webqit%2Frealdom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webqit%2Frealdom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webqit%2Frealdom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webqit","download_url":"https://codeload.github.com/webqit/realdom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webqit%2Frealdom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274672477,"owners_count":25328547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-11T02:00:13.660Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-21T17:15:18.526Z","updated_at":"2025-09-11T16:46:09.808Z","avatar_url":"https://github.com/webqit.png","language":"JavaScript","funding_links":["https://github.com/sponsors/ox-harris","https://opencollective.com/webqit"],"categories":[],"sub_categories":[],"readme":"# realdom\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n[![bundle][bundle-src]][bundle-href]\n[![License][license-src]][license-href]\n\nA small, low-level utility for working with the (real) DOM in realtime!\n\n## Documentation\n\n\u003e **Info** This is *(Early stage)* documentation for `v2.x`. (Looking for [`v1.x`](https://github.com/webqit/realdom/tree/v1.0.3)?)\n\n+ [Download Options](#download-options)\n+ [The Ready-State API](#the-ready-state-api)\n  + [Method: `realdom.ready()`](#method-realdomready)\n+ [The Realtime Mutations API](#the-realtime-mutations-api)\n  + [Method: `realdom.realtime( context ).observe()`](#method-realdomrealtime-context-observe)\n    + [Concept: *Scope*](#concept-scope)\n    + [Concept: *Targets*](#concept-targets)\n    + [Concept: *Records*](#concept-records)\n    + [Concept: *Static Sensitivity*](#concept-static-sensitivity)\n    + [Concept: *Event Details*](#concept-event-details)\n    + [Concept: *Abort Signals*](#concept-abort-signals)\n    + [Concept: *Life Cycle Signals*](#concept-life-cycle-signals)\n    + [Concept: *Timing*](#concept-timing)\n  + [Method: `realdom.realtime( context ).query()`](#method-realdomrealtime-context-query)\n    + [Concept: *Scope*](#concept-scope-1)\n    + [Concept: *Targets*](#concept-targets-1)\n    + [Concept: *Records*](#concept-records-1)\n    + [Concept: *Realtime Queries*](#concept-realtime-queries)\n  + [Method: `realdom.realtime( context, 'attr' ).observe()`](#method-realdomrealtime-context-attr-observe)\n    + [Concept: *Scope*](#concept-scope-2)\n    + [Concept: *Targets*](#concept-targets-2)\n    + [Concept: *Records*](#concept-records-2)\n    + [Concept: *Atomic Delivery*](#concept-atomic-delivery)\n    + [Concept: *Event Details*](#concept-event-details-1)\n    + [Concept: *Abort Signals*](#concept-abort-signals-1)\n    + [Concept: *Life Cycle Signals*](#concept-life-cycle-signals-1)\n    + [Concept: *Timing*](#concept-timing-1)\n  + [Method: `realdom.realtime( context, 'attr' ).get()`](#method-realdomrealtime-context-attr-get)\n    + [Concept: *Scope*](#concept-scope-3)\n    + [Concept: *Targets*](#concept-targets-3)\n    + [Concept: *Realtime Attributes*](#concept-realtime-attributes)\n  + [Method: `realdom.realtime( context ).attr()`](#method-realdomrealtime-context-attr)\n  + [Implementation Notes](#implementation-notes)\n+ [The Render Scheduling API](#the-render-scheduling-api)\n  + [Method: `realdom.schedule( 'read', ... )`](#method-realdomschedule-read--)\n  + [Method: `realdom.schedule( 'write', ... )`](#method-realdomschedule-write--)\n  + [Method: `realdom.schedule( 'cycle', ... )`](#method-realdomschedule-cycle--)\n+ [Issues](#issues)\n+ [License](#license)\n\n## Download Options\n\n**_Use as an npm package:_**\n\n```bash\nnpm i @webqit/realdom\n```\n\n```js\n// Import\nimport init from '@webqit/realdom';\n\n// Initialize the lib\ninit.call( window );\n\n// Obtain the APIs\nconst { ready, realtime, schedule } = window.webqit.realdom;\n```\n\n**_Use as a script:_**\n\n```html\n\u003cscript src=\"https://unpkg.com/@webqit/realdom/dist/main.js\"\u003e\u003c/script\u003e\n```\n\n```js\n// Obtain the APIs\nconst { ready, realtime, schedule } = window.webqit.realdom;\n```\n\n## The Ready-State API\n\nKnow when the document is ready! This is a simplistic API for working with the document's [ready state](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState).\n\n### Method: `realdom.ready()`\n\nKnow when the document is ready.\n\n```js\n// Signature 1\nready([ callback = undefined ]);\n```\n\n```js\n// Signature 2\nready([ timing = 'interactive'[, callback = undefined ]]);\n```\n\nThe `ready()` function takes a callback function to be called at a certain document-ready state. This function receives the `window` object.\n\n```js\n// Binding to the document's ready state\nready( window =\u003e console.log( `Document \"ready state\" is now \"interactive\"` ) );\n```\n\n*When no callback function is provided, a promise is returned.*\n\n```js\n// Awaiting the document's ready state\nawait ready();\nconsole.log( `Document \"ready state\" is now \"interactive\"` );\n```\n\n**--\u003e** Use the two-parameter syntax to specify the `timing` - i.e. *[ready state](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState)* - at which to be called. This can be either of two values:\n\n+ `interactive` - *(The default)* The point at which the document has finished loading and the document has been parsed but sub-resources such as scripts, images, stylesheets and frames are still loading.\n\n+ `complete` - The point at which the document and all sub-resources have finished loading.\n\n```js\n// Binding to the document's \"complete\" ready state\nready( 'complete', () =\u003e console.log( 'Document \"ready state\" is now \"complete\"' ) );\n```\n\n```js\n// Awaiting the document's \"complete\" ready state\nawait ready( 'complete' );\nconsole.log( `Document \"ready state\" is now \"complete\"` );\n```\n\n## The Realtime Mutations API\n\nReact to realtime DOM operations! This is a set of succint and consistent methods for accessing the DOM - either on-demand (you calling the DOM... as you would using [`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)) or in realtime (you letting the DOM call you... as you would using the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) API).\n\n### Method: `realdom.realtime( context ).observe()`\n\nObserve the (real) DOM in realtime!\n\n```js\n// Signature 1\nrealtime( context ).observe( callback[, options = {} ]);\n```\n\n```js\n// Signature 2\nrealtime( context ).observe( targets, callback[, options = {} ]);\n```\n\n### Concept: *Scope*\n\nReport all *direct children* additions and removals to/from the given element - the context:\n\n```js\n// Observing all direct children mutations\nrealtime( document.body ).observe( handleChanges );\n```\n\n**--\u003e** Observe entire *subtree* of the given element using the `options.subtree` flag:\n\n```js\n// Observing all subtree mutations\nrealtime( document.body ).observe( handleChanges, { subtree: true } );\n```\n\n\u003e **Info** Given that only direct children are covered without the `options.subtree` flag, you'd normally always need this flag when the `document` object is the *context*.\n\n**--\u003e** Observe into Shadow Roots of the given element, however nested, using the `options.subtree = 'cross-roots'` flag:\n\n```js\n// Observing all subtree mutations\nrealtime( document.body ).observe( handleChanges, { subtree: 'cross-roots' } );\n```\n\n### Concept: *Targets*\n\nPass in a CSS selector to match elements in realtime; e.g. \"p\" for all `\u003cp\u003e` elements:\n\n```js\n// Observing only \"p\" elements mutations\nrealtime( document.body ).observe( 'p', handleChanges, { subtree: true } );\n```\n\n...whether \"p\" elements added via markup:\n\n```js\n// and whether or not it's deeply nested as par { subtree: true }:\ndocument.body.innerHTML = '\u003cdiv\u003e\u003cp\u003e\u003c/p\u003e\u003c/div\u003e';\n```\n\n...or \"p\" elements added programmatically:\n\n```js\n// and whether or not it's deeply nested as par { subtree: true }:\nconst p = document.createElement( 'p' );\nconst div = document.createElement( 'div' );\ndiv.appendChild( p );\ndocument.body.appendChild( div );\n```\n\n**--\u003e** Pass in an Xpath query to express what can't be expressed in a CSS selector; e.g. for when you need to match text and comment nodes in realtime. Xpath expressions must be enclosed in parentheses.\n\n```js\n// Observing all \"comment\" nodes having a certain content\nrealtime( document.body ).observe( '(comment()[contains(., \"hello world\")])', handleChanges, { subtree: true } );\n```\n\n\u003e **Info** Note that Xpath expressions must not be prefixed with the direct children `/` or descendant `//` qualifiers as that is controlled internally. The `options.subtree` parameter is how you specify the resolution context for your queries.\n\n**--\u003e** Observe element instances as targets too. (E.g. a \"p\" instance.)\n\n```js\n// observing an instance plus a selector\nconst pElement = document.createElement( 'p' );\nrealtime( document.body ).observe( [ pElement, orCssSelector ], handleChanges, { subtree: true } );\n```\n\n...both for when they're added:\n\n```js\n// and whether or not it's deeply nested as par { subtree: true }:\nconst div = document.createElement( 'div' );\ndiv.appendChild( pElement );\ndocument.body.appendChild( div );\n```\n\n...and when they're removed:\n\n```js\n// either via an overwrite... (indirect overwrite in this case)...\ndocument.body.innerHTML = '';\n```\n\n```js\n// or via some programmatic means... (indirect removal in this case)...\ndocument.querySelector( 'div' ).remove();\n```\n\n### Concept: *Records*\n\nHandle mutation records - each having an `entrants` and an `exits` array property, representing added and removed nodes respectively:\n\n```js\n// Handling changes\nfunction handleChanges( record ) {\n    for ( const addedNode of record.entrants ) {\n        console.log( 'added:', addedNode );\n    }\n    for ( const removedNode of record.exits ) {\n        console.log( 'removed:', removedNode );\n    }\n}\n```\n\n**--\u003e** Use the `options.generation` parameter to require only either the `entrants` or `exits` list:\n\n```js\n// Requiring only the \"entrants\" list\nrealtime( document.body ).observe( handleChanges, { generation: 'entrants' } );\n```\n\n```js\n// Handling just record.entrants\nfunction handleChanges( record, context ) {\n    for ( const addedNode of record.entrants ) {\n        console.log( 'added:', addedNode );\n    }\n    console.log( record.exits ); // Empty array\n}\n```\n\n**--\u003e** Use the `record.target` property to access the *mutation target* - often, the parent element under which mutation happened:\n\n```js\n// Inspecting record.target\nfunction handleChanges( record ) {\n    console.log( record.target ); // HTMLBodyElement\n}\n```\n\n### Concept: *Static Sensitivity*\n\nWhen targeting elements using *attribute selectors*, use the `options.staticSensitivity` flag to opt in to statically matching elements based on the attributes mentioned in the selector:\n\n```js\n// Adding the options.staticSensitivity flag\nrealtime( document.body ).observe( 'p[draggable=\"true\"]', handleChanges, { staticSensitivity: true } );\n```\n\n*Now, \"p\" elements are matched for `[draggable=\"true\"]` in their static state too:*\n\n```js\n// The following \"p\" element suddenly matches and is reported (record.entrants)\ndocument.querySelector( 'p' ).setAttribute( 'draggable', 'true' );\n```\n\n```js\n// The following \"p\" element suddenly doesn't match and is reported (record.exits)\ndocument.querySelector( 'p' ).setAttribute( 'draggable', 'false' );\n```\n\n### Concept: *Event Details*\n\nUse the `option.eventDetails` flag to require the actual DOM operation that happened under the hood:\n\n```js\n// Requiring that event details be added\nrealtime( document.body ).observe( 'p[draggable=\"true\"]', handleChanges, { eventDetails: true } );\n```\n\n```js\n// Inspecting record.event\nfunction handleChanges( record ) {\n    console.log( record.event );\n}\n```\n\n*You get an array in the format: `[ HTMLBodyElement, 'appendChild' ]` - for mutations that happen programatically:*\n\n```js\n// Running an operation\ndocument.body.appendChild( pElement );\n```\n\n*You get the keyword: `parse` - for elements recorded directly from the HTML parser while the document loads. (This happens for mutation listeners created early in the document tree.):*\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript\u003e\n      realdom.realtime( document ).observe( 'meta[foo]', handleChanges, { subtree: true } );\n    \u003c/script\u003e\n    \u003cmeta name=\"foo\" content=\"bar\"\u003e\n    \u003c!--\n    At this point in the document parsing, the meta element is now reported, and record.event is: \"parse\"\n    --\u003e\n    \u003cscript\u003e\n      const meta2 = document.createElement( 'meta' );\n      meta2.name = 'foo';\n      meta2.content = 'baz';\n      document.head.appendChild( meta2 );\n    \u003c/script\u003e\n    \u003c!--\n    At this point in the document parsing, the meta2 element is now reported, and record.event is: [ HTMLHeadElement, 'appendChild' ]\n    --\u003e\n  \u003c/head\u003e\n\u003c/html\u003e\n```\n\n*You get the keyword: `mutation` - for mutations that happen in other ways; e.g in when the user directly alters the DOM tree from the browser console.*\n\n### Concept: *Abort Signals*\n\nPass in an [Abort Signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort your mutation listener at any time:\n\n```js\n// Providing an AbortSignal\nconst abortController = new AbortController;\nrealtime( document.body ).observe( 'p', handleChanges, { signal: abortController.signal } );\n```\n\n```js\n// Abort at any time\nabortController.abort();\n```\n\n### Concept: *Life Cycle Signals*\n\nWhen dealing with nested event listeners - event handlers that themselves create event listeners, tying child listeners' lifecycle to parent's lifecycle can be cumbersome.\n\n```js\n// Managing nested lifecycles using multiple AbortSignals\nconst parentAbortController = new AbortController;\nlet recursionAbortController;\nrealtime( document.body ).observe( 'p', record =\u003e {\n    // Abort all nested listeners in \"previous\" recursion\n    recursionAbortController?.abort();\n    // Create a new AbortController for listeners in \"this\" recursion\n    recursionAbortController = new AbortController;\n    for( const addedNode of record.entrants ) {\n        addedNode.addEventListener( 'click', handleClick, { signal: recursionAbortController.signal } );\n    }\n}, { signal: parentAbortController.signal } );\n```\n\n```js\n// Abort parent at any time\nparentAbortController.abort();\n// Abort the latest instance of recursionAbortController\nrecursionAbortController?.abort();\n```\n\n**--\u003e** Use the `options.lifecycleSignals` parameter to opt in to receiving auto-generated signals for tying nested listeners:\n\n```js\n// Managing nested lifecycles using automatic lifecycle signals\nconst parentAbortController = new AbortController;\nrealtime( document.body ).observe( 'p', ( record, flags ) =\u003e {\n    for( const addedNode of record.entrants ) {\n        addedNode.addEventListener( 'click', handleClick, { signal: flags.signal } );\n    }\n}, { signal: parentAbortController.signal, lifecycleSignals: true } );\n```\n\n```js\n// Abort parent at any time\nparentAbortController.abort();\n// The latest flags.signal instance is also automatically aborted\n```\n\n### Concept: *Timing*\n\nFor when timing is everything, meet the `options.timing` parameter!\n\n*By default, mutation records are delivered at the \"async\" timing of the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) API. This means that there's a small lag between when mutations happen and when they are delivered.*\n\n```js\n// Observing with \"asynchronous\" timing\nlet deliveredElement;\nrealtime( document.body ).observe( 'p', record =\u003e {\n    deliveredElement = record.entrants[ 0 ];\n} );\n```\n\n```js\n// Confirming the \"async\" delivery\nconst pElement = document.createElement( 'p' );\ndocument.body.appendChild( pElement );\nconsole.log( pElement.isConnected ); // true\nconsole.log( deliveredElement ); // undefined\n```\n\n```js\n// Estimating delivery timing\nsetTimeout( () =\u003e {\n    console.log( deliveredElement ); // HTMLParagraphElement\n}, 0 );\n```\n\n**--\u003e** Use the `options.timing = \"sync\"` parameter to observe mutations *synchronously*:\n\n```js\n// Opting in to \"synchronous\" timing\nlet deliveredElement;\nrealtime( document.body ).observe( 'p', record =\u003e {\n    deliveredElement = record.entrants[ 0 ];\n}, { timing: 'sync' } );\n```\n\n```js\n// Confirming the \"sync\" delivery\nconst pElement = document.createElement( 'p' );\ndocument.body.appendChild( pElement );\nconsole.log( pElement.isConnected ); // true\nconsole.log( deliveredElement ); // HTMLParagraphElement\n```\n\n*There is also a rare case where a tool needs to extend the DOM in more low-level ways, and this time, needs to *intercept* certain mutations before they actually happen. For example, you could only really [rewrite `\u003cscript\u003e` elements](https://github.com/webqit/oohtml#scoped-js) before they're parsed and executed if you could *intercept* them.*\n\n**--\u003e** Use the `options.timing = \"intercept\"` parameter to observe mutations *before* they actually happen:\n\n```js\n// Trying the \"intercept\" timing\nrealtime( document.body ).observe( 'script', handleScripts, { timing: 'intercept' } );\n```\n\n```js\n// Making the mutation\nconst scriptElement = document.createElement( 'script' );\ndocument.body.appendChild( scriptElement );\n```\n\n```js\n// Confirming the \"intercpted\" delivery\nfunction handleScripts( record ) {\n    const deliveredElement = record.entrants[ 0 ];\n    // We're receiving an element that is only just about to be added to the DOM\n    console.log( deliveredElement.isConnected ); // false\n    console.log( deliveredElement.parentNode ); // null\n    console.log( record.event ); // [ HTMLBodyElement, 'appendChild' ]\n    // We can rewrite this script\n    deliveredElement.text = 'alert( \"Tada!\" )';\n}\n```\n\n*And thanks to the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) API, interception also works at parse time for mutation listeners created early enough:*\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript\u003e\n      realdom.realtime( document ).observe( 'script[rewriteme]', handleScripts, { timing: 'intercept' } );\n    \u003c/script\u003e\n    \u003cscript rewriteme\u003e\n        alert( 'Hello world!' );\n    \u003c/script\u003e\n    \u003c!--\n    At this point in the document parsing, the script[rewriteme] element is now reported, and rewritten\n    But note that this time, element was just already in the DOM, only yet to be handled. So...\n    console.log( deliveredElement.isConnected ); // true\n    console.log( deliveredElement.parentNode ); // HTMLHeadElement\n    console.log( record.event ); // \"parse\"\n    --\u003e\n  \u003c/head\u003e\n\u003c/html\u003e\n```\n\n### Method: `realdom.realtime( context ).query()`\n\nWork with the (real) DOM both on-demand (as you would with [`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)), and in *realtime* (as you would with `realtime().observe()`).\n\n```js\n// Signature 1\nrealtime( context ).query([ callback = undefined[, options = {} ]]);\n```\n\n```js\n// Signature 2\nrealtime( context ).query( targets[, callback = undefined[, options = {} ]]);\n```\n\n```js\n// Signature 3\nconst records = realtime( context ).query([ options = {} ]);\n```\n\n### Concept: *Scope*\n\nGet all *direct children* of the given element delivered:\n\n```js\n// Getting all children delivered to a callback\nrealtime( document.body ).query( handleResult );\n```\n\n```js\n// Retreiving all children\nconst records = realtime( document.body ).query();\n// Deligating to handler\nrecords.forEach( record =\u003e handleResult( record ) );\n```\n\n**--\u003e** Use the `realtime().children()` alias:\n\n```js\n// Delivering, using the .children() alias\nrealtime( document.body ).children( handleResult );\n```\n\n```js\n// Retreiving, using the .children() alias\nconst records = realtime( document.body ).children();\n// Deligating to handler\nrecords.forEach( record =\u003e handleResult( record ) );\n```\n\n\u003e **Info** Using the `options.subtree` flag without specifying *targets* produces no result.\n\n### Concept: *Targets*\n\nPass in a CSS selector to match elements: e.g. \"p\" for all `\u003cp\u003e` elements:\n\n```js\n// Matching just \"p\" children\nrealtime( document.body ).query( 'p', handleResult );\n```\n\n```js\n// Using the .children() alias\nrealtime( document.body ).children( 'p', handleResult );\n```\n\n```js\n// Retreiving, using the .children() alias\nconst records = realtime( document.body ).children( 'p' );\n// Deligating to handler\nrecords.forEach( record =\u003e handleResult( record ) );\n```\n\n**--\u003e** Use the `options.subtree` flag to query the entire subtree.\n\n```js\n// Using the options.subtree flag\nrealtime( document.body ).query( 'p', handleResult, { subtree: true } );\n```\n\n**--\u003e** Use the `realtime().subtree()` alias:\n\n```js\n// Using the .subtree() alias\nrealtime( document.body ).subtree( 'p', handleResult );\n```\n\n```js\n// Retreiving, using the .subtree() alias\nconst records = realtime( document.body ).subtree( 'p' );\n// Deligating to handler\nrecords.forEach( record =\u003e handleResult( record ) );\n```\n\n**--\u003e** Pass in an Xpath query to express what can't be expressed in a CSS selector; e.g. for when you need to match text and comment nodes. Xpath expressions must be enclosed in parentheses.\n\n```js\n// Query all \"comment\" nodes having a certain content\nrealtime( document.body ).query( '(comment()[contains(., \"hello world\")])', handleResult, { subtree: true } );\n```\n\n\u003e **Info**: Note that Xpath expressions must not be prefixed with the direct children `/` or descendant `//` qualifiers as that is controlled internally. The `options.subtree` parameter is how you specify the resolution context for your queries.\n\n### Concept: *Records*\n\nHandle query result records in the same way as mutation records:\n\n```js\n// Handling query result\nfunction handleResult( record ) {\n    // record.entrants is the list of matched nodes\n    for ( const matchedNode of record.entrants ) {\n        console.log( 'matched:', matchedNode );\n    }\n    console.log( record.exits ); // Always an empty array\n    console.log( record.event ); // Always the keyword: \"query\"\n}\n```\n\n\u003e **Info** Setting the `options.generation` parameter to `exits` effectively defies the logic, thus no query actually happens.\n\n**--\u003e** Use the `record.target` property to access the *query context* for the record:\n\n```js\n// Inspecting record.target\nfunction handleResult( record ) {\n    console.log( record.target ); // HTMLBodyElement\n}\n```\n\n\u003e **Info** Elements are organized into records by common parent. Thus, an expression like `realtime().subtree( 'p' )` on the below will produce 2 records, with 2 entrants per record:\n\u003e\n\u003e  ```html\n\u003e  \u003chtml\u003e\n\u003e    \u003cbody\u003e\n\u003e      \u003csection\u003e\n\u003e        \u003cp\u003e\u003c/p\u003e\n\u003e        \u003cp\u003e\u003c/p\u003e\n\u003e      \u003c/section\u003e\n\u003e      \u003cp\u003e\u003c/p\u003e\n\u003e      \u003cp\u003e\u003c/p\u003e\n\u003e    \u003c/body\u003e\n\u003e  \u003c/html\u003e\n\u003e  ```\n\n### Concept: *Realtime Queries*\n\nGet both current and future elements delivered to the same handler function:\n\n```js\n// Matching all current \"p[draggable]\" elements\nrealtime( document ).query( 'p[draggable]', handleDraggables, { subtree: true } );\n// Subscribe to future matching elements\nrealtime( document ).observe( 'p[draggable]', handleDraggables, { subtree: true } );\n```\n\n**--\u003e** Use the `options.live` flag to achieve the same:\n\n```js\n// Matching all current \"p[draggable]\" elements and staying subscribed\nrealtime( document ).query( 'p[draggable]', handleDraggables, { subtree: true, live: true } );\n```\n\n**--\u003e** Use equivalent APIs the same way:\n\n```js\n// Using the .subtree() alias\nrealtime( document ).subtree( 'p[draggable]', handleDraggables, { live: true } );\n```\n\n```js\n// Using the .children() alias\nrealtime( document ).children( 'p[draggable]', handleDraggables, { live: true } );\n```\n\n**--\u003e** Use the `options.live` flag in conjunction with other concepts:\n\n```js\n// Using the options.live flag\nconst abortController = new AbortController;\nrealtime( document ).children( 'p[draggable]', handleDraggables, {\n    live: true,\n    signal: abortController.signal,\n    lifecycleSignals: true,\n    staticSensitivity: true,\n    timing: 'sync',\n    eventDetails: true,\n} );\n```\n\n### Method: `realdom.realtime( context, 'attr' ).observe()`\n\nObserve DOM attributes in realtime!\n\n```js\n// Signature 1\nrealtime( context, 'attr' ).observe( callback[, options = {} ]);\n```\n\n```js\n// Signature 2\nrealtime( context, 'attr' ).observe( targets, callback[, options = {} ]);\n```\n\n### Concept: *Scope*\n\nReport all attribute changes on the given element:\n\n```js\n// Observing all attributes\nrealtime( document.body, 'attr' ).observe( handleChanges );\n```\n\n**--\u003e** Observe all attribute changes across the entire *subtree* of the given context using the `options.subtree` flag:\n\n```js\n// Observing entire subtree\nrealtime( document.body, 'attr' ).observe( handleChanges, { subtree: true } );\n```\n\n\n\u003e **Info** Given that only direct children are covered without the `options.subtree` flag, you'd normally always need this flag when the `document` object is the *context*.\n\n**--\u003e** Observe into Shadow Roots of the given element, however nested, using the `options.subtree = 'cross-roots'` flag:\n\n```js\n// Observing entire subtree\nrealtime( document.body, 'attr' ).observe( handleChanges, { subtree: 'cross-roots' } );\n```\n\n### Concept: *Targets*\n\nObserve specific attributes on the given context:\n\n```js\n// Observing the \"draggable\" attribute\nrealtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges );\n```\n\n### Concept: *Records*\n\nHandle mutation records - an array of *attribute-change* records:\n\n```js\n// Handling mutation records\nfunction handleChanges( records ) {\n    for ( const record of records ) {\n        console.log( record.name );\n    }\n}\n```\n\n**--\u003e** Use the `options.oldValue` and `options.newValue` flags to require attribute's old and new values respectively:\n\n```js\n// Requiring attribute's old and new value\nrealtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges, { newValue: true, oldValue: true } );\n```\n\n```js\n// Inspecting attribute's old and new value\nfunction handleChanges( records ) {\n    for ( const record of records ) {\n        console.log( record.name, record.value, record.oldValue );\n    }\n}\n```\n\n**--\u003e** Use the `record.target` property to access the *mutation target* - the element on which mutation happened:\n\n```js\n// Inspecting record.target\nfunction handleChanges( records ) {\n    console.log( records[ 0 ].target );\n}\n```\n\n**--\u003e** Where exactly one attribute is being observed and is passed as a string instead of an array, mutation records are delivered in singular form instead of as an array:\n\n```js\n// Passing the observed attribute as a bare string instead of an array\nrealtime( element, 'attr' ).observe( 'draggable', handleChanges );\n```\n\n```js\n// Receiving records in singular form instead of as an array\nfunction handleChanges( record ) {\n    console.log( record.name );\n}\n```\n\n### Concept: *Atomic Delivery*\n\nGet records for multiple attributes delivered atomically - in whole - whenever *any* of the attributes change:\n\n```js\n// Observing multiple attributes atomically\nrealtime( element, 'attr' ).observe( [ 'attr1', 'attr2', 'attr3' ], handleChanges, { atomic: true } );\n```\n\n```js\n// Receiving all 3 records anytime any one of them changes\nfunction handleChanges( records ) {\n    const [ attr1, attr2, attr3 ] = records;\n}\n```\n\n### Concept: *Event Details*\n\nUse the `option.eventDetails` flag to require the actual DOM operation that happened under the hood:\n\n```js\n// Requiring that event details be added\nrealtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges, { eventDetails: true } );\n```\n\n```js\n// Inspecting record.event\nfunction handleChanges( records ) {\n    console.log( records[ 0 ].event );\n}\n```\n\n*You get an array in the format: `[ HTMLInputElement, 'toggleAttribute' ]` - for mutations that happen programatically:*\n\n```js\n// Running an operation\nelement.toggleAttribute( 'required' );\n```\n\n*You get the keyword: `mutation` - for mutations that happen in other ways; e.g in when the user directly alters an element's attribute from the browser console.*\n\n### Concept: *Abort Signals*\n\nPass in an [Abort Signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort your mutation listener at any time:\n\n```js\n// Providing an AbortSignal\nconst abortController = new AbortController;\nrealtime( element, 'attr' ).observe( [ 'required' ], { signal: abortController.signal } );\n```\n\n```js\n// Abort at any time\nabortController.abort();\n```\n\n### Concept: *Life Cycle Signals*\n\nWhen dealing with nested event listeners (*[see above](#concept-life-cycle-signals)*), use the `options.lifecycleSignals` parameter to opt in to receiving auto-generated signals for tying nested listeners:\n\n```js\n// Managing nested lifecycles using automatic lifecycle signals\nconst parentAbortController = new AbortController;\nrealtime( element, 'attr' ).observe( [ 'draggable' ], ( records, flags ) =\u003e {\n    if ( records[ 0 ].value === 'true' ) {\n        element.addEventListener( 'drag', handleDrag, { signal: flags.signal } );\n    }\n}, { newValue: true, signal: parentAbortController.signal, lifecycleSignals: true } );\n```\n\n```js\n// Abort parent at any time\nparentAbortController.abort();\n// The latest flags.signal instance is also automatically aborted\n```\n\n### Concept: *Timing*\n\nFor when timing is critical (*[see above](#concept-timing)*), use the `options.timing = \"sync\"` parameter to observe mutations *synchronously*:\n\n```js\n// Opting in to \"synchronous\" timing\nrealtime( element, 'attr' ).observe( [ 'draggable' ], records =\u003e {\n    // Handle records\n}, { timing: 'sync' } );\n```\n\n**--\u003e** Use the `options.timing = \"intercept\"` parameter to observe attribute mutations *before* they actually happen:\n\n```js\n// Opting in to \"synchronous\" timing\nrealtime( img, 'attr' ).observe( [ 'src' ], records =\u003e {\n    // Handle records\n}, { timing: 'intercept' } );\n```\n\n### Method: `realdom.realtime( context, 'attr' ).get()`\n\nWork with attributes both on-demand and in *realtime* (as you would with `realtime( element, 'attr' ).observe()`).\n\n```js\n// Signature 1\nrealtime( context, 'attr' ).get([ callback = undefined[, options = {} ]]);\n```\n\n```js\n// Signature 2\nrealtime( context, 'attr' ).get( targets[, callback = undefined[, options = {} ]]);\n```\n\n```js\n// Signature 3\nconst records = realtime( context, 'attr' ).get([ options = {} ]);\n```\n\n### Concept: *Scope*\n\nGet all attributes on the given element:\n\n```js\n// Getting all attributes delivered to a handler\nrealtime( document.body, 'attr' ).get( handleChanges );\n```\n\n```js\n// Retreiving all attributes\nconst records = realtime( document.body, 'attr' ).get();\n// Deligating to handler\nhandleChanges( records );\n```\n\n### Concept: *Targets*\n\nGet specific attributes on the given context:\n\n```js\n// Getting the \"draggable\" attributes delivered to a handler\nrealtime( element, 'attr' ).get( [ 'draggable' ], handleChanges );\n```\n\n```js\n// Retreiving the \"draggable\" attributes\nconst records = realtime( document.body, 'attr' ).get( [ 'draggable' ] );\n// Deligating to handler\nhandleChanges( records );\n```\n\n### Concept: *Realtime Attributes*\n\nGet both current and future attributes delivered to the same handler function:\n\n```js\n// Getting the \"draggable\" attributes delivered to a handler\nrealtime( element, 'attr' ).get( [ 'draggable' ], handleChanges );\n// Observing future \"draggable\" attribute changes\nrealtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges );\n```\n\n**--\u003e** Use the `options.live` flag to achieve the same:\n\n```js\n// Getting current and future state of the \"draggable\" attributes delivered to a handler\nrealtime( element, 'attr' ).get( [ 'draggable' ], handleChanges, { live: true } );\n```\n\n**--\u003e** Use the `options.live` flag in conjunction with other concepts:\n\n```js\n// Using the options.live flag with other flags\nconst abortController = new AbortController;\nrealtime( element, 'attr' ).get( [ 'attr1', 'attr2', 'attr3' ], handleChanges, {\n    newValue: true,\n    oldValue: true,\n    atomic: true,\n    live: true,\n    signal: abortController.signal,\n    lifecycleSignals: true,\n    timing: 'sync',\n    eventDetailsL true,\n} );\n```\n\n### Method: `realdom.realtime( context ).attr()`\n\n*An alias for [`realtime( context, 'attr' ).get()`](#method-realdomrealtime-context-attr-get).*\n\n\n### Implementation Notes\n\n+ The `realtime` API's unique timing capabilities is based on literal interception of DOM APIs. And here is the complete list of them:\n    \n    + `Node`: `insertBefore`, `replaceChild`, `removeChild`, `appendChild`, `textContent`, `nodeValue`.\n    + `Element`: `insertAdjacentElement`, `insertAdjacentHTML`, `setHTML`, `replaceChildren`, `replaceWith`, `remove`. `before`, `after`, `append`, `prepend`, `toggleAttribute`, `removeAttribute`, `setAttribute`.\n    + `HTMLElement`: `outerText`, `innerText`.\n    \n    You may need to consider this caveat on your specific usecase.\n\n## The Render Scheduling API\n\nEliminate layout thrashing by scheduling DOM read/write operations. (Compare [fastdom](https://github.com/wilsonpage/fastdom))\n\n```js\nschedule( 'read', () =\u003e {\n  console.log( 'reading phase of the UI' );\n} );\n\nschedule( 'write', () =\u003e {\n  console.log( 'writing phase of the UI' );\n} );\n\nschedule( 'read', () =\u003e {\n  console.log( 'reading phase of the UI'  );\n} );\n\nschedule( 'write', () =\u003e {\n  console.log( 'writing phase of the UI'  );\n} );\n```\n\n```\nreading phase of the UI\nreading phase of the UI\nwriting phase of the UI\nwriting phase of the UI\n```\n\n**_Concept_**\n\nThe `schedule` API works as a regulatory layer between your app/library and the DOM. It lets you think of the DOM in terms of a \"reading\" phase and a \"writing\" phase, and lets you hook into this cycle when working with the DOM: `schedule( 'read', ... )` for doing \"read\" operations, and `schedule( 'write', ... )` for doing \"write\" operations. Batching DOM operations this way lets us avoid unnecessary document reflows and dramatically speeds up layout performance.\n\n\u003e Each read/write operation is added to a corresponding read/write queue. The queues are emptied (reads, then writes) at the turn of the next frame using `window.requestAnimationFrame`.\n\n### Method: `realdom.schedule( 'read', ... )`\n\n```js\n// Signature\nschedule( 'read', readCallback[, inPromiseMode = false ]);\n```\n\nSchedules a job for the \"read\" phase. Can return a promise that resolves when job eventually executes; you ask for a promise by supplying `true` as a second argument.\n\n```js\nconst promise = schedule( 'read', () =\u003e {\n  const width = element.clientWidth;\n}, true/*give back a promise*/ );\n```\n\n### Method: `realdom.schedule( 'write', ... )`\n\n```js\n// Signature\nschedule( 'write', writeCallback[, inPromiseMode = false ]);\n```\n\nSchedules a job for the \"write\" phase. Can return a promise that resolves when job eventually executes; you ask for a promise by supplying `true` as a second argument.\n\n```js\nconst promise = schedule( 'write', () =\u003e {\n  element.style.width = width + 'px';\n}, true/*give back a promise*/ );\n```\n\n### Method: `realdom.schedule( 'cycle', ... )`\n\n```js\n// Signature\nschedule( 'cycle', readCallback, writeCallback );\n```\n\nPuts your read/write operations in a cycle that keeps in sync with the UI's read/write cycle.\n\n```js\nschedule( 'cycle',\n    // onread\n    () =\u003e {\n        // Do a read operation\n        const width = element.clientWidth;\n        // Now if we return anything other than undefined, the \"onwrite\" block is executed\n        return width; // recieved by the \"onwrite\" callback on its first parameter\n    },\n    // onwrite\n    ( width, carried ) =\u003e {\n        // Do a write operation\n        element.style.width = width + 'px';\n        // Now if we return anything other than undefined, the cycle repeats starting with the \"onread\" block\n        return newCarry; // recieved by the \"onwrite\" block again on its second parameter: \"carried\"\n    }\n);\n```\n\n## Issues\n\nTo report bugs or request features, please submit an [issue](https://github.com/webqit/realdom/issues).\n\n## License\n\nMIT.\n\n[npm-version-src]: https://img.shields.io/npm/v/@webqit/realdom?style=flat\u0026colorA=18181B\u0026colorB=F0DB4F\n[npm-version-href]: https://npmjs.com/package/@webqit/realdom\n[npm-downloads-src]: https://img.shields.io/npm/dm/@webqit/realdom?style=flat\u0026colorA=18181B\u0026colorB=F0DB4F\n[npm-downloads-href]: https://npmjs.com/package/@webqit/realdom\n[bundle-src]: https://img.shields.io/bundlephobia/minzip/@webqit/realdom?style=flat\u0026colorA=18181B\u0026colorB=F0DB4F\n[bundle-href]: https://bundlephobia.com/result?p=@webqit/realdom\n[license-src]: https://img.shields.io/github/license/webqit/realdom.svg?style=flat\u0026colorA=18181B\u0026colorB=F0DB4F\n[license-href]: https://github.com/webqit/realdom/blob/master/LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebqit%2Frealdom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebqit%2Frealdom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebqit%2Frealdom/lists"}