{"id":16125752,"url":"https://github.com/dy/spect","last_synced_at":"2025-07-14T03:07:25.894Z","repository":{"id":35088063,"uuid":"178706885","full_name":"dy/spect","owner":"dy","description":"Observable selectors in DOM","archived":false,"fork":false,"pushed_at":"2025-01-18T20:53:51.000Z","size":5554,"stargazers_count":79,"open_issues_count":5,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-19T21:02:30.948Z","etag":null,"topics":["aspects","live-collection","selector-observer","spect"],"latest_commit_sha":null,"homepage":"https://dy.github.io/spect","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/dy.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","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},"funding":{"github":"dy"}},"created_at":"2019-03-31T15:32:02.000Z","updated_at":"2025-01-18T20:53:53.000Z","dependencies_parsed_at":"2024-10-20T08:00:33.670Z","dependency_job_id":"a929df06-6e2a-4254-97db-a02904e546e3","html_url":"https://github.com/dy/spect","commit_stats":null,"previous_names":["spectjs/spect"],"tags_count":81,"template":false,"template_full_name":null,"purl":"pkg:github/dy/spect","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dy%2Fspect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dy%2Fspect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dy%2Fspect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dy%2Fspect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dy","download_url":"https://codeload.github.com/dy/spect/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dy%2Fspect/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265236762,"owners_count":23732497,"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","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":["aspects","live-collection","selector-observer","spect"],"created_at":"2024-10-09T21:31:30.102Z","updated_at":"2025-07-14T03:07:25.867Z","avatar_url":"https://github.com/dy.png","language":"JavaScript","funding_links":["https://github.com/sponsors/dy"],"categories":[],"sub_categories":[],"readme":"\u003c!--\n\u003cp align=\"center\"\u003e▶ \u003ca href=\"https://codepen.io/dyv/pen/oNXXZEb\" target=\"_blank\"\u003e\u003cstrong\u003eRun\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cbr/\u003e\n--\u003e\n\n# \u003csub\u003e\u003cimg alt=\"spect\" src=\"./logo2.svg\" height=30 /\u003e\u003c/sub\u003e spect   \u003ca href=\"https://github.com/spectjs/spect/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/spectjs/spect/actions/workflows/test.yml/badge.svg\"/\u003e\u003c/a\u003e \u003cimg alt=\"npm bundle size\" src=\"https://img.shields.io/bundlejs/size/spect\"\u003e \u003ca href=\"https://npmjs.org/package/spect\"\u003e\u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/v/spect\"\u003e\u003c/a\u003e\n\n\u003e Observe selectors in DOM.\n\n#### _`spect( container=document, selector, handler? )`_\n\nObserves _`selector`_ in _`container`_, invokes `handler` any time matching elements appear.\u003cbr/\u003e\nHandler can return a teardown function, called for unmatched elements.\u003cbr/\u003e\nReturns live collection of elements.\n\n```js\nimport spect from 'spect';\n\n// assign aspect\nconst foos = spect('.foo', el =\u003e {\n  console.log('connected');\n  return () =\u003e console.log('disconnected');\n});\n\n// modify DOM\nconst foo = document.createElement('div');\nfoo.className = 'foo';\ndocument.body.append(foo);\n// ... \"connected\"\n\nfoo.remove();\n// ... \"disconnected\"\n```\n\n#### _`spect(element[s], handler)`_\n\nListens for connected/disconnected events for the list of elements. (alternative to [fast-on-load](https://www.npmjs.com/package/fast-on-load))\n\n```js\nconst nodes = [...document.querySelectorAll('.foo'), document.createElement('div')];\n\n// assign listener\nspect(nodes, el =\u003e {\n  console.log(\"connected\");\n  return () =\u003e console.log(\"disconnected\");\n});\n\ndocument.body.appendChild(nodes.at(-1))\n// ... \"connected\"\n\nnodes.at(-1).remove()\n// ... \"disconnected\"\n```\n\n### Live Collection\n\nSpect creates live collection of elements matching the selector. Collection extends Array and implements Set / HTMLColection interfaces.\n\n```js\nconst foos = spect(`.foo`);\n\n// live collection\nfoos[idx], foos.at(idx)                       // Array\nfoos.has(el), foos.add(el), foos.delete(el)   // Set\nfoos.item(idx), foos.namedItem(elementId)     // HTMLCollection\nfoos.dispose()                                // destroy selector observer / unsubscribe\n```\n\n### Technique\n\nIt combines selector parts indexing from [selector-observer](https://github.com/josh/selector-observer) for simple queries and animation events from [insertionQuery](https://github.com/naugtur/insertionQuery) for complex selectors.\n\nSimple selector is id/name/class/tag followed by classes or attrs.\n\n* `#a`, `.x.y`, `[name=\"e\"].x`, `*`, `a-b-c:x` - simple selectors.\n* `a b`, `#b .c` - complex selectors.\n\n\u003c!--\n## Examples\n\n\u003cdetails\u003e\u003csummary\u003eHello World\u003c/summary\u003e\n\n```html\n\u003cdiv class=\"user\"\u003e{{ user.name || \"Loading...\" }}\u003c/div\u003e\n\n\u003cscript type=\"module\"\u003e\n  import spect from 'spect'\n  import templize from 'templize'\n\n  // initialize template\n  spect('.user', async el =\u003e templize(el, {\n    user: (await fetch('/user')).json() // value is available when resolved\n  }))\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eTimer\u003c/summary\u003e\n\n```html\n\u003ctime class=\"timer\"\u003e{{ count }}\u003c/time\u003e\n\u003ctime class=\"timer\"\u003e{{ count }}\u003c/time\u003e\n\n\u003cscript type=\"module\"\u003e\n  import spect from 'spect'\n  import templize from 'templize'\n\n  spect('.timer', timer =\u003e {\n    const params = templize(timer, { count: 0 })\n    const id = setInterval(() =\u003e params.count++, 1000)\n    return () =\u003e clearInterval(id)\n  })\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eCounter\u003c/summary\u003e\n\n```html\n\u003coutput id=\"count\"\u003e{{ count }}\u003c/output\u003e\n\u003cbutton id=\"inc\" onclick=\"{{ inc }}\"\u003e+\u003c/button\u003e\n\u003cbutton id=\"dec\" onclick=\"{{ dec }}\"\u003e-\u003c/button\u003e\n\n\u003cscript type=\"module\"\u003e\n  import spect from 'spect'\n  import v from 'value-ref'\n  import templize from 'templize'\n\n  const count = v(0)\n  spect('#count', el =\u003e templize(el, { count }))\n\n  // bind events via HTML template\n  spect('#inc', el =\u003e templize(el, { inc: () =\u003e count.value++ }))\n  spect('#dec', el =\u003e templize(el, { dec: () =\u003e count.value-- }))\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eTodo list\u003c/summary\u003e\n\n```html\n\u003cform class=\"todo-form\"\u003e\n  \u003clabel for=\"add-todo\"\u003e\n    \u003cspan\u003eAdd Todo\u003c/span\u003e\n    \u003cinput name=\"text\" required\u003e\n  \u003c/label\u003e\n  \u003cbutton type=\"submit\"\u003eAdd\u003c/button\u003e\n  \u003cul class=\"todo-list\"\u003e{{ todos }}\u003cul\u003e\n\u003c/form\u003e\n\n\u003cscript type=\"module\"\u003e\n  import spect from 'spect'\n  import v from 'value-ref'\n  import h from 'hyperf'\n  import tpl from 'templize'\n\n  const todos = v([])\n\n  spect('.todo-list', el =\u003e tpl(el, {\n    todos: v.from(todos, item =\u003e h`\u003cli\u003e${ item.text }\u003c/li\u003e`)\n  }))\n\n  spect('.todo-form', form =\u003e form.addEventListener('submit', e =\u003e {\n    e.preventDefault()\n    if (!form.checkValidity()) return\n    todos.value = [...todos.value, { text: form.text.value }]\n    form.reset()\n  }))\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eForm validator\u003c/summary\u003e\n\n```html\n\u003cform id=\"email-form\"\u003e\n  \u003clabel for=\"email\"\u003ePlease enter an email address:\u003c/label\u003e\n  \u003cinput id=\"email\" onchange={{ validate }}/\u003e\n  The address is {{ valid ? \"valid\" : \"invalid\" }}\n\u003c/form\u003e\n\n\u003cscript type=\"module\"\u003e\n  import spect from 'spect'\n  import templize from 'templize'\n\n  const isValidEmail = s =\u003e /.+@.+\\..+/i.test(s)\n\n  spect('#email-form', form =\u003e {\n    const params = templize(form, {\n      valid: false,\n      validate: () =\u003e params.valid = isValidEmail(e.target.value)\n    })\n  })\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003ePrompt\u003c/summary\u003e\n\n```html\n\u003cdialog class=\"dialog\" open={{showPrompt}}\u003e\n  Proceed?\n  \u003cmenu\u003e\n    \u003cbutton onclick={{cancel}}\u003eCancel\u003c/button\u003e\n    \u003cbutton onclick={{confirm}}\u003eConfirm\u003c/button\u003e\n  \u003c/menu\u003e\n\u003c/dialog\u003e\n\n\u003cscript\u003e\nimport v from 'value-ref'\nimport spect from 'spect'\n\nspect('.dialog', el =\u003e {\n  const showPrompt = v(false), proceed = v(false)\n  templize(el, {\n    showPrompt, proceed,\n    cancel() {showPrompt.value = proceed.value = false;},\n    confirm() {showPrompt.value = false; proceed.value = true;}\n  })\n})\n\u003c/script\u003e\n```\n\u003c/details\u003e\n\n[See all examples](examples).\n--\u003e\n\n\u003c!--\n## Best Buddies\n\n* [value-ref](https://github.com/spectjs/value-ref) − value container with observable interface. Indispensible for reactive data.\n* [templize](https://github.com/spectjs/templize) − DOM buddy - hooks up reactive values to template parts.\n* [hyperf](https://github.com/spectjs/hyperf) − builds HTML fragments with reactive fields.\n* [subscribable-things](https://github.com/chrisguttandin/subscribable-things) − collection of observables for different browser APIs.\n--\u003e\n\u003c!-- * [element-props](https://github.com/spectjs/element-props) − unified access to element props with observable support. Comes handy for organizing components. --\u003e\n\u003c!-- * [strui](https://github.com/spectjs/strui) − collection of UI streams, such as router, storage etc. Comes handy for building complex reactive web-apps (spect, rxjs etc). --\u003e\n\n\n## Alternatives\n\n[element-behaviors](https://github.com/lume/element-behaviors),\n[insertionQuery](https://github.com/naugtur/insertionQuery),\n[selector-observer](https://github.com/josh/selector-observer),\n[qso](https://www.npmjs.com/package/qso),\n[qsa-observer](https://www.npmjs.com/package/qsa-observer),\n[element-observer](https://github.com/WebReflection/element-observer),\n[livequery](https://github.com/hazzik/livequery),\n[selector-listener](https://github.com/csuwildcat/SelectorListener),\n[mutation-summary](https://github.com/rafaelw/mutation-summary),\n[fast-on-load](https://ghub.io/fast-on-load),\n[selector-set](https://github.com/josh/selector-set),\n[rkusa/selector-observer](https://github.com/rkusa/selector-observer).\n[css-chain](https://github.com/sashafirsov/css-chain)\n\n\u003cp align=\"center\"\u003e\u003ca href=\"https://krishnized.com/license/\"\u003eॐ\u003c/a\u003e\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdy%2Fspect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdy%2Fspect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdy%2Fspect/lists"}