{"id":17134761,"url":"https://github.com/kapouer/window-page","last_synced_at":"2025-07-24T18:12:03.388Z","repository":{"id":66100692,"uuid":"52269766","full_name":"kapouer/window-page","owner":"kapouer","description":"Promise-based Page state event handler","archived":false,"fork":false,"pushed_at":"2024-10-31T10:24:52.000Z","size":826,"stargazers_count":4,"open_issues_count":10,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T00:33:59.565Z","etag":null,"topics":["prerender","rendering","webpage"],"latest_commit_sha":null,"homepage":"","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/kapouer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"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}},"created_at":"2016-02-22T11:47:32.000Z","updated_at":"2024-10-31T10:24:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"a99a6f2b-381b-45d3-8238-c70b2591f73c","html_url":"https://github.com/kapouer/window-page","commit_stats":{"total_commits":844,"total_committers":2,"mean_commits":422.0,"dds":"0.0023696682464454666","last_synced_commit":"39360f36a108aa7b794adf9a5834d0a9311f5c2e"},"previous_names":[],"tags_count":160,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapouer%2Fwindow-page","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapouer%2Fwindow-page/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapouer%2Fwindow-page/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kapouer%2Fwindow-page/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kapouer","download_url":"https://codeload.github.com/kapouer/window-page/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248688575,"owners_count":21145765,"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":["prerender","rendering","webpage"],"created_at":"2024-10-14T19:45:42.333Z","updated_at":"2025-07-24T18:12:03.368Z","avatar_url":"https://github.com/kapouer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# window.Page\n\nAsync chains for web page lifecycle.\n\nWorks well with:\n\n- async navigation\n- custom elements\n- visibility API\n- history API\n\n`window.Page` is the current State instance.\n\n## Install\n\n```js\nimport 'window-page';\n```\n\nAnd use a bundler to with static or dynamic polyfills.\n\n## Chains\n\n- route: pathname changed and document is not prerendered; can set `state.doc`.\n- ready: document is ready\n- build: pathname changed and document is not prerendered\n- patch: pathname changed and document is not prerendered, or query changed\n- setup: visible, on first view or pathname changed\n- paint: visible, on first view or pathname or query changed\n- fragment: visible, location hash changed; `state.hash` is set.\n- close: visible, referrer closed when pathname changed, before new state is setup\n- catch: error was thrown, `state.error` is set.\n\nA run is triggered by navigation (document.location changed in any way, or\npage history methods called, see below).\n\nSeveral chains are only run when document is visible - i.e. not \"hidden\".\nThis is used to prerender on server, and also prerender on client.\n\nRoute listeners can set `state.doc`: an optional document which styles and scripts are imported after `route` chain.\nThe `ready` chain always has `state.doc = document`.\n\nIf the `state.error` object is removed from state during the catch chain,\nthe navigation will continue as if the error did not happen.\n\n## Usage\n\n```js\n\nPage.route(async function(state) {\n const res = await fetch(page.pathname + '.json');\n const data = await res.json();\n // keep data during navigation\n state.data = data;\n state.doc = state.parseDoc(data.template);\n});\n\nPage.connect({\n  build(state) {\n    // build page\n  }\n  async patch(state) {\n    if (state.query.id == null) return;\n    const data = await (fetch('/getdata?id=' + state.query.id).json())\n    // do something\n  }\n  handleClick(e) {\n    const link = e.target.closest('a[href]');\n    if (!link) return;\n    e.preventDefault();\n    state.push(link.href);\n  }\n});\n\n```\n\n## API\n\n### chains\n\nFor each chain, one can add or remove a listener function that receives the\ncurrent state as argument.\n\n- `state[chainLabel](fn)`\n  runs optional fn right now if the chain is reached, or wait the chain to be run.\n  returns a promise that resolves to corresponding state.\n- `state['un'+chainLabel](fn)`\n  removes fn from a chain, mostly needed for custom elements.\n\nThe fn parameter can be a function or an object with a `\u003cchain\u003e`, or\na `chain\u003cChain\u003e` method - which is a handy way to keep the value of `this`.\n\nFunctions listening for a given stage are run serially.\n\nIf a stage chain is already resolved, new listeners are just added immediately\nto the promise chain.\n\nTo append a function at the end of the current chain, use:\n\n- state.finish(fn)\n  fn is optional, defaults to a promise (returned) that is resolved at the end.\n\nAvoid making a chain wait its own end, or it will deadlock.\n\nTo run a custom chain:\n\n- state.copy().runChain(stage)\n\nNote that custom chains do not propagate properties added to state to other chains.\n\n### listeners and navigation\n\nChains are implemented through native DOM emitters and listeners, and the\nemitter is either a script node in `document.head`, or a state-bound, out of tree, DOM node.\n\nThe script node can be `document.currentScript` when defined, unless the listener is a DOM node that is not itself a script node in document head.\n\nOtherwise it is `state.emitter`. That emitter is shared between two successive states having the same pathname, and distinct otherwise.\n\nThese behaviors ensure that during navigation, a common script keeps its listeners registered, and other listeners will only be triggered during the life of the state that allowed them to be registered.\n\n### state\n\nThe state is a subclass of Loc, which extends URL class with:\n\n- query object\n- sameDomain, samePathname, sameQuery, sameHash, samePath methods\n- toString() returns a path when in the same domain\n- copy() to copy a state and most of its private/public properties\n  useful for working with another stage of a state.\n\n**Important**: use state.push/replace to mutate url properties.\nThe state history methods accept partial objects.\n\n- state.data\n  data is saved in navigator history - must be JSON-serializable.\n\n- state.referrer\n  the previous state, or null;\n  Is not related to `document.referrer`.\n\nPage.State: the state's constructor.\n\n### Document import\n\nWhen a new document is loaded after route chain, stylesheets are loaded in parallel, and scripts are loaded serially, with parallel preloading.\n\nThose methods are called:\n\n- await state.mergeHead(head, prev)\n- await state.mergeBody(body, prev)\n\nThe default `mergeHead` method do a basic diff to keep existing scripts and links\nnodes.\nThe default `mergeBody` method just replaces `document.body` with the new body.\n\nTo manage page transitions, these methods can be overriden by `route` listeners.\n\n### Integration with Custom Elements, event handlers\n\nAn object having build, patch, setup, paint, fragment, close methods can be\nplugged into Page using:\n\n- state.connect(node)\n- state.disconnect(node)\n\nFurthermore, if the object owns methods named `handle${Type}`, they will be\nused as `type` event listeners on that object, receiving arguments `(e, state)`.\n\nTo use \"capture\" listeners, just name the methods `capture\u003cType\u003e` (new in 7.1.0).\n\nTo use the same mecanism to manage event listener on another event emitter,\npass that event emitter as second argument to `state.connect(listener, emitter)`.\n\nTo simply handle or capture events on window,\nuse `handleAll${Type}` or `captureAll${Type}`.\n\nThese event listeners are automatically added during setup, and removed during\nclose (or disconnect).\n\n```js\nconnectedCallback() {\n  Page.connect(this);\n}\ndisconnectedCallback() {\n  Page.disconnect(this);\n}\npatch(state) {}\nsetup(state) {}\nclose() {}\ncaptureSubmit(e, state) {}\nhandleClick(e, state) {}\nhandleAllClick(e, state) {}\n```\n\n### Using the event listener on other objects (window, document...)\n\n- state.connect(listener, emitter)\n\nThis method accepts a second argument to configure event listeners, and\nbenefit from automatic removal of event listeners on `close`.\n\nExample:\n\n```js\nsetup(state) {\n  state.connect(this, window);\n}\nhandleScroll(e, state) {\n  // got click\n}\n```\n\n### History\n\nThese methods will run chains on new state and return immediately the new state:\n\n- state.push(location or url, opts)\n- state.replace(location or url, opts)\n\nOptions:\n\n- vary (boolean, or \"build\", \"patch\", \"fragment\", default false)\n  Overrides how pathname, query, hash are compared to previous state.\n  `true` re-routes the page; and varying on a chain runs the next chains too.\n  Example: reload after a form login.\n\n- data\n  Assign this data to next state.data.\n\n- state.reload(opts)\n  a shortcut for `state.replace(state, opts)`,\n  with the correct value for `vary` set depending on state chains being\n  used or not.\n  `opts.vary` can be set, in which case it is passed as is to `replace`.\n  Example: does not call `setup` then `close` unless BUILD chain is not empty.\n\nA convenient method only that only replaces current window.history entry:\n\n- state.save()\n  useful to save current state.data.\n\n### Loc methods\n\nState inherits from Loc:\n\n- parse(str)\n  parses a url into pathname, query object, hash; and protocol, hostname, port\n  if not the same domain as the document.\n  returns a Loc instance.\n\n- format(loc)\n  format a location to a string with only what was defined,\n  converts obj.path to pathname, query then stringify query obj if any.\n\n- sameDomain(b)\n  compare domains (protocol + hostname + port) of two url or objects.\n\n- samePathname(b)\n  compare domains and pathname of two url or objects.\n\n- sameQuery(b)\n  compare query strings of two url or objects.\n\n- samePath(b)\n  compare domain, pathname, querystring (without hash) of two url or objects.\n\n## Debug logs\n\nJust enable `debug` level in the console.\n\n## License\n\nMIT, see LICENSE file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkapouer%2Fwindow-page","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkapouer%2Fwindow-page","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkapouer%2Fwindow-page/lists"}