@wordpress/interactivity-router 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Interactivity Router
2
+
3
+ > **Note**
4
+ > This package is a extension of the API shared at [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). As part of an [Open Source project](https://developer.wordpress.org/block-editor/getting-started/faq/#the-gutenberg-project) we encourage participation in helping shape this API and the [discussions in GitHub](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) is the best place to engage.
5
+
6
+ This package defines an Interactivity API store with the `core/router` namespace, exposing state and actions like `navigate` and `prefetch` to handle client-side navigations.
7
+
8
+ ## Usage
9
+
10
+ The package is intended to be imported dynamically in the `view.js` files of interactive blocks.
11
+
12
+ ```js
13
+ import { store } from '@wordpress/interactivity';
14
+
15
+ store( 'myblock', {
16
+ actions: {
17
+ *navigate( e ) {
18
+ e.preventDefault();
19
+ const { actions } = yield import(
20
+ '@wordpress/interactivity-router'
21
+ );
22
+ yield actions.navigate( e.target.href );
23
+ },
24
+ },
25
+ } );
26
+ ```
27
+
28
+ ## Frequently Asked Questions
29
+
30
+ At this point, some of the questions you have about the Interactivity API may be:
31
+
32
+ ### What is this?
33
+
34
+ This is the base of a new standard to create interactive blocks. Read [the proposal](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/) to learn more about this.
35
+
36
+ ### Can I use it?
37
+
38
+ You can test it, but it's still very experimental.
39
+
40
+ ### How do I get started?
41
+
42
+ The best place to start with the Interactivity API is this [**Getting started guide**](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/1-getting-started.md). There you'll will find a very quick start guide and the current requirements of the Interactivity API.
43
+
44
+ ### Where can I ask questions?
45
+
46
+ The [“Interactivity API” category](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) in Gutenberg repo discussions is the best place to ask questions about the Interactivity API.
47
+
48
+ ### Where can I share my feedback about the API?
49
+
50
+ The [“Interactivity API” category](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) in Gutenberg repo discussions is also the best place to share your feedback about the Interactivity API.
51
+
52
+ ## Installation
53
+
54
+ Install the module:
55
+
56
+ ```bash
57
+ npm install @wordpress/interactivity --save
58
+ ```
59
+
60
+ _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._
61
+
62
+ ## Docs & Examples
63
+
64
+ **[Interactivity API Documentation](https://github.com/WordPress/gutenberg/tree/trunk/packages/interactivity/docs)** is the best place to learn about this proposal. Although it's still in progress, some key pages are already available:
65
+
66
+ - **[Getting Started Guide](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/1-getting-started.md)**: Follow this Getting Started guide to learn how to scaffold a new project and create your first interactive blocks.
67
+ - **[API Reference](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/2-api-reference.md)**: Check this page for technical detailed explanations and examples of the directives and the store.
68
+
69
+ Here you have some more resources to learn/read more about the Interactivity API:
70
+
71
+ - **[Interactivity API Discussions](https://github.com/WordPress/gutenberg/discussions/52882)**
72
+ - [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/)
73
+ - Developer Hours sessions ([Americas](https://www.youtube.com/watch?v=RXNoyP2ZiS8&t=664s) & [APAC/EMEA](https://www.youtube.com/watch?v=6ghbrhyAcvA))
74
+ - [wpmovies.dev](http://wpmovies.dev/) demo and its [wp-movies-demo](https://github.com/WordPress/wp-movies-demo) repo
75
+
76
+ <br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
package/build/index.js ADDED
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.state = exports.actions = void 0;
7
+ var _interactivity = require("@wordpress/interactivity");
8
+ /**
9
+ * WordPress dependencies
10
+ */
11
+
12
+ // The cache of visited and prefetched pages.
13
+ const pages = new Map();
14
+
15
+ // Helper to remove domain and hash from the URL. We are only interesting in
16
+ // caching the path and the query.
17
+ const cleanUrl = url => {
18
+ const u = new URL(url, window.location);
19
+ return u.pathname + u.search;
20
+ };
21
+
22
+ // Fetch a new page and convert it to a static virtual DOM.
23
+ const fetchPage = async (url, {
24
+ html
25
+ }) => {
26
+ try {
27
+ if (!html) {
28
+ const res = await window.fetch(url);
29
+ if (res.status !== 200) return false;
30
+ html = await res.text();
31
+ }
32
+ const dom = new window.DOMParser().parseFromString(html, 'text/html');
33
+ return regionsToVdom(dom);
34
+ } catch (e) {
35
+ return false;
36
+ }
37
+ };
38
+
39
+ // Return an object with VDOM trees of those HTML regions marked with a
40
+ // `router-region` directive.
41
+ const regionsToVdom = dom => {
42
+ const regions = {};
43
+ const attrName = `data-${_interactivity.directivePrefix}-router-region`;
44
+ dom.querySelectorAll(`[${attrName}]`).forEach(region => {
45
+ const id = region.getAttribute(attrName);
46
+ regions[id] = (0, _interactivity.toVdom)(region);
47
+ });
48
+ const title = dom.querySelector('title')?.innerText;
49
+ return {
50
+ regions,
51
+ title
52
+ };
53
+ };
54
+
55
+ // Render all interactive regions contained in the given page.
56
+ const renderRegions = page => {
57
+ const attrName = `data-${_interactivity.directivePrefix}-router-region`;
58
+ document.querySelectorAll(`[${attrName}]`).forEach(region => {
59
+ const id = region.getAttribute(attrName);
60
+ const fragment = (0, _interactivity.getRegionRootFragment)(region);
61
+ (0, _interactivity.render)(page.regions[id], fragment);
62
+ });
63
+ if (page.title) {
64
+ document.title = page.title;
65
+ }
66
+ };
67
+
68
+ // Variable to store the current navigation.
69
+ let navigatingTo = '';
70
+
71
+ // Listen to the back and forward buttons and restore the page if it's in the
72
+ // cache.
73
+ window.addEventListener('popstate', async () => {
74
+ const url = cleanUrl(window.location); // Remove hash.
75
+ const page = pages.has(url) && (await pages.get(url));
76
+ if (page) {
77
+ renderRegions(page);
78
+ } else {
79
+ window.location.reload();
80
+ }
81
+ });
82
+
83
+ // Cache the current regions.
84
+ pages.set(cleanUrl(window.location), Promise.resolve(regionsToVdom(document)));
85
+ const {
86
+ state,
87
+ actions
88
+ } = (0, _interactivity.store)('core/router', {
89
+ actions: {
90
+ /**
91
+ * Navigates to the specified page.
92
+ *
93
+ * This function normalizes the passed href, fetchs the page HTML if
94
+ * needed, and updates any interactive regions whose contents have
95
+ * changed. It also creates a new entry in the browser session history.
96
+ *
97
+ * @param {string} href The page href.
98
+ * @param {Object} [options] Options object.
99
+ * @param {boolean} [options.force] If true, it forces re-fetching the
100
+ * URL.
101
+ * @param {string} [options.html] HTML string to be used instead of
102
+ * fetching the requested URL.
103
+ * @param {boolean} [options.replace] If true, it replaces the current
104
+ * entry in the browser session
105
+ * history.
106
+ * @param {number} [options.timeout] Time until the navigation is
107
+ * aborted, in milliseconds. Default
108
+ * is 10000.
109
+ *
110
+ * @return {Promise} Promise that resolves once the navigation is
111
+ * completed or aborted.
112
+ */
113
+ *navigate(href, options = {}) {
114
+ const url = cleanUrl(href);
115
+ navigatingTo = href;
116
+ actions.prefetch(url, options);
117
+
118
+ // Create a promise that resolves when the specified timeout ends.
119
+ // The timeout value is 10 seconds by default.
120
+ const timeoutPromise = new Promise(resolve => {
121
+ var _options$timeout;
122
+ return setTimeout(resolve, (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : 10000);
123
+ });
124
+ const page = yield Promise.race([pages.get(url), timeoutPromise]);
125
+
126
+ // Once the page is fetched, the destination URL could have changed
127
+ // (e.g., by clicking another link in the meantime). If so, bail
128
+ // out, and let the newer execution to update the HTML.
129
+ if (navigatingTo !== href) return;
130
+ if (page) {
131
+ renderRegions(page);
132
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
133
+ } else {
134
+ window.location.assign(href);
135
+ yield new Promise(() => {});
136
+ }
137
+ },
138
+ /**
139
+ * Prefetchs the page with the passed URL.
140
+ *
141
+ * The function normalizes the URL and stores internally the fetch
142
+ * promise, to avoid triggering a second fetch for an ongoing request.
143
+ *
144
+ * @param {string} url The page URL.
145
+ * @param {Object} [options] Options object.
146
+ * @param {boolean} [options.force] Force fetching the URL again.
147
+ * @param {string} [options.html] HTML string to be used instead of
148
+ * fetching the requested URL.
149
+ */
150
+ prefetch(url, options = {}) {
151
+ url = cleanUrl(url);
152
+ if (options.force || !pages.has(url)) {
153
+ pages.set(url, fetchPage(url, options));
154
+ }
155
+ }
156
+ }
157
+ });
158
+ exports.actions = actions;
159
+ exports.state = state;
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_interactivity","require","pages","Map","cleanUrl","url","u","URL","window","location","pathname","search","fetchPage","html","res","fetch","status","text","dom","DOMParser","parseFromString","regionsToVdom","e","regions","attrName","directivePrefix","querySelectorAll","forEach","region","id","getAttribute","toVdom","title","querySelector","innerText","renderRegions","page","document","fragment","getRegionRootFragment","render","navigatingTo","addEventListener","has","get","reload","set","Promise","resolve","state","actions","store","navigate","href","options","prefetch","timeoutPromise","_options$timeout","setTimeout","timeout","race","history","replace","assign","force","exports"],"sources":["@wordpress/interactivity-router/src/index.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\trender,\n\tdirectivePrefix,\n\ttoVdom,\n\tgetRegionRootFragment,\n\tstore,\n} from '@wordpress/interactivity';\n\n// The cache of visited and prefetched pages.\nconst pages = new Map();\n\n// Helper to remove domain and hash from the URL. We are only interesting in\n// caching the path and the query.\nconst cleanUrl = ( url ) => {\n\tconst u = new URL( url, window.location );\n\treturn u.pathname + u.search;\n};\n\n// Fetch a new page and convert it to a static virtual DOM.\nconst fetchPage = async ( url, { html } ) => {\n\ttry {\n\t\tif ( ! html ) {\n\t\t\tconst res = await window.fetch( url );\n\t\t\tif ( res.status !== 200 ) return false;\n\t\t\thtml = await res.text();\n\t\t}\n\t\tconst dom = new window.DOMParser().parseFromString( html, 'text/html' );\n\t\treturn regionsToVdom( dom );\n\t} catch ( e ) {\n\t\treturn false;\n\t}\n};\n\n// Return an object with VDOM trees of those HTML regions marked with a\n// `router-region` directive.\nconst regionsToVdom = ( dom ) => {\n\tconst regions = {};\n\tconst attrName = `data-${ directivePrefix }-router-region`;\n\tdom.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {\n\t\tconst id = region.getAttribute( attrName );\n\t\tregions[ id ] = toVdom( region );\n\t} );\n\tconst title = dom.querySelector( 'title' )?.innerText;\n\treturn { regions, title };\n};\n\n// Render all interactive regions contained in the given page.\nconst renderRegions = ( page ) => {\n\tconst attrName = `data-${ directivePrefix }-router-region`;\n\tdocument.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {\n\t\tconst id = region.getAttribute( attrName );\n\t\tconst fragment = getRegionRootFragment( region );\n\t\trender( page.regions[ id ], fragment );\n\t} );\n\tif ( page.title ) {\n\t\tdocument.title = page.title;\n\t}\n};\n\n// Variable to store the current navigation.\nlet navigatingTo = '';\n\n// Listen to the back and forward buttons and restore the page if it's in the\n// cache.\nwindow.addEventListener( 'popstate', async () => {\n\tconst url = cleanUrl( window.location ); // Remove hash.\n\tconst page = pages.has( url ) && ( await pages.get( url ) );\n\tif ( page ) {\n\t\trenderRegions( page );\n\t} else {\n\t\twindow.location.reload();\n\t}\n} );\n\n// Cache the current regions.\npages.set(\n\tcleanUrl( window.location ),\n\tPromise.resolve( regionsToVdom( document ) )\n);\n\nexport const { state, actions } = store( 'core/router', {\n\tactions: {\n\t\t/**\n\t\t * Navigates to the specified page.\n\t\t *\n\t\t * This function normalizes the passed href, fetchs the page HTML if\n\t\t * needed, and updates any interactive regions whose contents have\n\t\t * changed. It also creates a new entry in the browser session history.\n\t\t *\n\t\t * @param {string} href The page href.\n\t\t * @param {Object} [options] Options object.\n\t\t * @param {boolean} [options.force] If true, it forces re-fetching the\n\t\t * URL.\n\t\t * @param {string} [options.html] HTML string to be used instead of\n\t\t * fetching the requested URL.\n\t\t * @param {boolean} [options.replace] If true, it replaces the current\n\t\t * entry in the browser session\n\t\t * history.\n\t\t * @param {number} [options.timeout] Time until the navigation is\n\t\t * aborted, in milliseconds. Default\n\t\t * is 10000.\n\t\t *\n\t\t * @return {Promise} Promise that resolves once the navigation is\n\t\t * completed or aborted.\n\t\t */\n\t\t*navigate( href, options = {} ) {\n\t\t\tconst url = cleanUrl( href );\n\t\t\tnavigatingTo = href;\n\t\t\tactions.prefetch( url, options );\n\n\t\t\t// Create a promise that resolves when the specified timeout ends.\n\t\t\t// The timeout value is 10 seconds by default.\n\t\t\tconst timeoutPromise = new Promise( ( resolve ) =>\n\t\t\t\tsetTimeout( resolve, options.timeout ?? 10000 )\n\t\t\t);\n\n\t\t\tconst page = yield Promise.race( [\n\t\t\t\tpages.get( url ),\n\t\t\t\ttimeoutPromise,\n\t\t\t] );\n\n\t\t\t// Once the page is fetched, the destination URL could have changed\n\t\t\t// (e.g., by clicking another link in the meantime). If so, bail\n\t\t\t// out, and let the newer execution to update the HTML.\n\t\t\tif ( navigatingTo !== href ) return;\n\n\t\t\tif ( page ) {\n\t\t\t\trenderRegions( page );\n\t\t\t\twindow.history[\n\t\t\t\t\toptions.replace ? 'replaceState' : 'pushState'\n\t\t\t\t]( {}, '', href );\n\t\t\t} else {\n\t\t\t\twindow.location.assign( href );\n\t\t\t\tyield new Promise( () => {} );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Prefetchs the page with the passed URL.\n\t\t *\n\t\t * The function normalizes the URL and stores internally the fetch\n\t\t * promise, to avoid triggering a second fetch for an ongoing request.\n\t\t *\n\t\t * @param {string} url The page URL.\n\t\t * @param {Object} [options] Options object.\n\t\t * @param {boolean} [options.force] Force fetching the URL again.\n\t\t * @param {string} [options.html] HTML string to be used instead of\n\t\t * fetching the requested URL.\n\t\t */\n\t\tprefetch( url, options = {} ) {\n\t\t\turl = cleanUrl( url );\n\t\t\tif ( options.force || ! pages.has( url ) ) {\n\t\t\t\tpages.set( url, fetchPage( url, options ) );\n\t\t\t}\n\t\t},\n\t},\n} );\n"],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { render, directivePrefix, toVdom, getRegionRootFragment, store } from '@wordpress/interactivity';
5
+
6
+ // The cache of visited and prefetched pages.
7
+ const pages = new Map();
8
+
9
+ // Helper to remove domain and hash from the URL. We are only interesting in
10
+ // caching the path and the query.
11
+ const cleanUrl = url => {
12
+ const u = new URL(url, window.location);
13
+ return u.pathname + u.search;
14
+ };
15
+
16
+ // Fetch a new page and convert it to a static virtual DOM.
17
+ const fetchPage = async (url, {
18
+ html
19
+ }) => {
20
+ try {
21
+ if (!html) {
22
+ const res = await window.fetch(url);
23
+ if (res.status !== 200) return false;
24
+ html = await res.text();
25
+ }
26
+ const dom = new window.DOMParser().parseFromString(html, 'text/html');
27
+ return regionsToVdom(dom);
28
+ } catch (e) {
29
+ return false;
30
+ }
31
+ };
32
+
33
+ // Return an object with VDOM trees of those HTML regions marked with a
34
+ // `router-region` directive.
35
+ const regionsToVdom = dom => {
36
+ const regions = {};
37
+ const attrName = `data-${directivePrefix}-router-region`;
38
+ dom.querySelectorAll(`[${attrName}]`).forEach(region => {
39
+ const id = region.getAttribute(attrName);
40
+ regions[id] = toVdom(region);
41
+ });
42
+ const title = dom.querySelector('title')?.innerText;
43
+ return {
44
+ regions,
45
+ title
46
+ };
47
+ };
48
+
49
+ // Render all interactive regions contained in the given page.
50
+ const renderRegions = page => {
51
+ const attrName = `data-${directivePrefix}-router-region`;
52
+ document.querySelectorAll(`[${attrName}]`).forEach(region => {
53
+ const id = region.getAttribute(attrName);
54
+ const fragment = getRegionRootFragment(region);
55
+ render(page.regions[id], fragment);
56
+ });
57
+ if (page.title) {
58
+ document.title = page.title;
59
+ }
60
+ };
61
+
62
+ // Variable to store the current navigation.
63
+ let navigatingTo = '';
64
+
65
+ // Listen to the back and forward buttons and restore the page if it's in the
66
+ // cache.
67
+ window.addEventListener('popstate', async () => {
68
+ const url = cleanUrl(window.location); // Remove hash.
69
+ const page = pages.has(url) && (await pages.get(url));
70
+ if (page) {
71
+ renderRegions(page);
72
+ } else {
73
+ window.location.reload();
74
+ }
75
+ });
76
+
77
+ // Cache the current regions.
78
+ pages.set(cleanUrl(window.location), Promise.resolve(regionsToVdom(document)));
79
+ export const {
80
+ state,
81
+ actions
82
+ } = store('core/router', {
83
+ actions: {
84
+ /**
85
+ * Navigates to the specified page.
86
+ *
87
+ * This function normalizes the passed href, fetchs the page HTML if
88
+ * needed, and updates any interactive regions whose contents have
89
+ * changed. It also creates a new entry in the browser session history.
90
+ *
91
+ * @param {string} href The page href.
92
+ * @param {Object} [options] Options object.
93
+ * @param {boolean} [options.force] If true, it forces re-fetching the
94
+ * URL.
95
+ * @param {string} [options.html] HTML string to be used instead of
96
+ * fetching the requested URL.
97
+ * @param {boolean} [options.replace] If true, it replaces the current
98
+ * entry in the browser session
99
+ * history.
100
+ * @param {number} [options.timeout] Time until the navigation is
101
+ * aborted, in milliseconds. Default
102
+ * is 10000.
103
+ *
104
+ * @return {Promise} Promise that resolves once the navigation is
105
+ * completed or aborted.
106
+ */
107
+ *navigate(href, options = {}) {
108
+ const url = cleanUrl(href);
109
+ navigatingTo = href;
110
+ actions.prefetch(url, options);
111
+
112
+ // Create a promise that resolves when the specified timeout ends.
113
+ // The timeout value is 10 seconds by default.
114
+ const timeoutPromise = new Promise(resolve => {
115
+ var _options$timeout;
116
+ return setTimeout(resolve, (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : 10000);
117
+ });
118
+ const page = yield Promise.race([pages.get(url), timeoutPromise]);
119
+
120
+ // Once the page is fetched, the destination URL could have changed
121
+ // (e.g., by clicking another link in the meantime). If so, bail
122
+ // out, and let the newer execution to update the HTML.
123
+ if (navigatingTo !== href) return;
124
+ if (page) {
125
+ renderRegions(page);
126
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
127
+ } else {
128
+ window.location.assign(href);
129
+ yield new Promise(() => {});
130
+ }
131
+ },
132
+ /**
133
+ * Prefetchs the page with the passed URL.
134
+ *
135
+ * The function normalizes the URL and stores internally the fetch
136
+ * promise, to avoid triggering a second fetch for an ongoing request.
137
+ *
138
+ * @param {string} url The page URL.
139
+ * @param {Object} [options] Options object.
140
+ * @param {boolean} [options.force] Force fetching the URL again.
141
+ * @param {string} [options.html] HTML string to be used instead of
142
+ * fetching the requested URL.
143
+ */
144
+ prefetch(url, options = {}) {
145
+ url = cleanUrl(url);
146
+ if (options.force || !pages.has(url)) {
147
+ pages.set(url, fetchPage(url, options));
148
+ }
149
+ }
150
+ }
151
+ });
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["render","directivePrefix","toVdom","getRegionRootFragment","store","pages","Map","cleanUrl","url","u","URL","window","location","pathname","search","fetchPage","html","res","fetch","status","text","dom","DOMParser","parseFromString","regionsToVdom","e","regions","attrName","querySelectorAll","forEach","region","id","getAttribute","title","querySelector","innerText","renderRegions","page","document","fragment","navigatingTo","addEventListener","has","get","reload","set","Promise","resolve","state","actions","navigate","href","options","prefetch","timeoutPromise","_options$timeout","setTimeout","timeout","race","history","replace","assign","force"],"sources":["@wordpress/interactivity-router/src/index.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\trender,\n\tdirectivePrefix,\n\ttoVdom,\n\tgetRegionRootFragment,\n\tstore,\n} from '@wordpress/interactivity';\n\n// The cache of visited and prefetched pages.\nconst pages = new Map();\n\n// Helper to remove domain and hash from the URL. We are only interesting in\n// caching the path and the query.\nconst cleanUrl = ( url ) => {\n\tconst u = new URL( url, window.location );\n\treturn u.pathname + u.search;\n};\n\n// Fetch a new page and convert it to a static virtual DOM.\nconst fetchPage = async ( url, { html } ) => {\n\ttry {\n\t\tif ( ! html ) {\n\t\t\tconst res = await window.fetch( url );\n\t\t\tif ( res.status !== 200 ) return false;\n\t\t\thtml = await res.text();\n\t\t}\n\t\tconst dom = new window.DOMParser().parseFromString( html, 'text/html' );\n\t\treturn regionsToVdom( dom );\n\t} catch ( e ) {\n\t\treturn false;\n\t}\n};\n\n// Return an object with VDOM trees of those HTML regions marked with a\n// `router-region` directive.\nconst regionsToVdom = ( dom ) => {\n\tconst regions = {};\n\tconst attrName = `data-${ directivePrefix }-router-region`;\n\tdom.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {\n\t\tconst id = region.getAttribute( attrName );\n\t\tregions[ id ] = toVdom( region );\n\t} );\n\tconst title = dom.querySelector( 'title' )?.innerText;\n\treturn { regions, title };\n};\n\n// Render all interactive regions contained in the given page.\nconst renderRegions = ( page ) => {\n\tconst attrName = `data-${ directivePrefix }-router-region`;\n\tdocument.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {\n\t\tconst id = region.getAttribute( attrName );\n\t\tconst fragment = getRegionRootFragment( region );\n\t\trender( page.regions[ id ], fragment );\n\t} );\n\tif ( page.title ) {\n\t\tdocument.title = page.title;\n\t}\n};\n\n// Variable to store the current navigation.\nlet navigatingTo = '';\n\n// Listen to the back and forward buttons and restore the page if it's in the\n// cache.\nwindow.addEventListener( 'popstate', async () => {\n\tconst url = cleanUrl( window.location ); // Remove hash.\n\tconst page = pages.has( url ) && ( await pages.get( url ) );\n\tif ( page ) {\n\t\trenderRegions( page );\n\t} else {\n\t\twindow.location.reload();\n\t}\n} );\n\n// Cache the current regions.\npages.set(\n\tcleanUrl( window.location ),\n\tPromise.resolve( regionsToVdom( document ) )\n);\n\nexport const { state, actions } = store( 'core/router', {\n\tactions: {\n\t\t/**\n\t\t * Navigates to the specified page.\n\t\t *\n\t\t * This function normalizes the passed href, fetchs the page HTML if\n\t\t * needed, and updates any interactive regions whose contents have\n\t\t * changed. It also creates a new entry in the browser session history.\n\t\t *\n\t\t * @param {string} href The page href.\n\t\t * @param {Object} [options] Options object.\n\t\t * @param {boolean} [options.force] If true, it forces re-fetching the\n\t\t * URL.\n\t\t * @param {string} [options.html] HTML string to be used instead of\n\t\t * fetching the requested URL.\n\t\t * @param {boolean} [options.replace] If true, it replaces the current\n\t\t * entry in the browser session\n\t\t * history.\n\t\t * @param {number} [options.timeout] Time until the navigation is\n\t\t * aborted, in milliseconds. Default\n\t\t * is 10000.\n\t\t *\n\t\t * @return {Promise} Promise that resolves once the navigation is\n\t\t * completed or aborted.\n\t\t */\n\t\t*navigate( href, options = {} ) {\n\t\t\tconst url = cleanUrl( href );\n\t\t\tnavigatingTo = href;\n\t\t\tactions.prefetch( url, options );\n\n\t\t\t// Create a promise that resolves when the specified timeout ends.\n\t\t\t// The timeout value is 10 seconds by default.\n\t\t\tconst timeoutPromise = new Promise( ( resolve ) =>\n\t\t\t\tsetTimeout( resolve, options.timeout ?? 10000 )\n\t\t\t);\n\n\t\t\tconst page = yield Promise.race( [\n\t\t\t\tpages.get( url ),\n\t\t\t\ttimeoutPromise,\n\t\t\t] );\n\n\t\t\t// Once the page is fetched, the destination URL could have changed\n\t\t\t// (e.g., by clicking another link in the meantime). If so, bail\n\t\t\t// out, and let the newer execution to update the HTML.\n\t\t\tif ( navigatingTo !== href ) return;\n\n\t\t\tif ( page ) {\n\t\t\t\trenderRegions( page );\n\t\t\t\twindow.history[\n\t\t\t\t\toptions.replace ? 'replaceState' : 'pushState'\n\t\t\t\t]( {}, '', href );\n\t\t\t} else {\n\t\t\t\twindow.location.assign( href );\n\t\t\t\tyield new Promise( () => {} );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Prefetchs the page with the passed URL.\n\t\t *\n\t\t * The function normalizes the URL and stores internally the fetch\n\t\t * promise, to avoid triggering a second fetch for an ongoing request.\n\t\t *\n\t\t * @param {string} url The page URL.\n\t\t * @param {Object} [options] Options object.\n\t\t * @param {boolean} [options.force] Force fetching the URL again.\n\t\t * @param {string} [options.html] HTML string to be used instead of\n\t\t * fetching the requested URL.\n\t\t */\n\t\tprefetch( url, options = {} ) {\n\t\t\turl = cleanUrl( url );\n\t\t\tif ( options.force || ! pages.has( url ) ) {\n\t\t\t\tpages.set( url, fetchPage( url, options ) );\n\t\t\t}\n\t\t},\n\t},\n} );\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ export const state: any;
2
+ export namespace actions {
3
+ /**
4
+ * Navigates to the specified page.
5
+ *
6
+ * This function normalizes the passed href, fetchs the page HTML if
7
+ * needed, and updates any interactive regions whose contents have
8
+ * changed. It also creates a new entry in the browser session history.
9
+ *
10
+ * @param {string} href The page href.
11
+ * @param {Object} [options] Options object.
12
+ * @param {boolean} [options.force] If true, it forces re-fetching the
13
+ * URL.
14
+ * @param {string} [options.html] HTML string to be used instead of
15
+ * fetching the requested URL.
16
+ * @param {boolean} [options.replace] If true, it replaces the current
17
+ * entry in the browser session
18
+ * history.
19
+ * @param {number} [options.timeout] Time until the navigation is
20
+ * aborted, in milliseconds. Default
21
+ * is 10000.
22
+ *
23
+ * @return {Promise} Promise that resolves once the navigation is
24
+ * completed or aborted.
25
+ */
26
+ function navigate(href: string, options?: {
27
+ force?: boolean;
28
+ html?: string;
29
+ replace?: boolean;
30
+ timeout?: number;
31
+ }): Promise<any>;
32
+ /**
33
+ * Prefetchs the page with the passed URL.
34
+ *
35
+ * The function normalizes the URL and stores internally the fetch
36
+ * promise, to avoid triggering a second fetch for an ongoing request.
37
+ *
38
+ * @param {string} url The page URL.
39
+ * @param {Object} [options] Options object.
40
+ * @param {boolean} [options.force] Force fetching the URL again.
41
+ * @param {string} [options.html] HTML string to be used instead of
42
+ * fetching the requested URL.
43
+ */
44
+ function prefetch(url: string, options?: {
45
+ force?: boolean;
46
+ html?: string;
47
+ }): void;
48
+ }
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;IAqFE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH;;;;;qBA8BC;IAED;;;;;;;;;;;OAWG;IACH;;;aAKC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@wordpress/interactivity-router",
3
+ "version": "1.0.0",
4
+ "description": "Package that exposes state and actions from the `core/router` store, part of the Interactivity API.",
5
+ "author": "The WordPress Contributors",
6
+ "license": "GPL-2.0-or-later",
7
+ "keywords": [
8
+ "wordpress",
9
+ "gutenberg",
10
+ "interactivity"
11
+ ],
12
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/interactivity-router/README.md",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/WordPress/gutenberg.git",
16
+ "directory": "packages/interactivity-router"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/WordPress/gutenberg/labels/%5BFeature%5D%20Interactivity%20API"
20
+ },
21
+ "engines": {
22
+ "node": ">=12"
23
+ },
24
+ "main": "build/index.js",
25
+ "module": "build-module/index.js",
26
+ "react-native": "src/index",
27
+ "types": "build-types",
28
+ "dependencies": {
29
+ "@wordpress/interactivity": "^4.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "gitHead": "45de2cb4212fed7f2763e95f10300d1ff9d0ec08"
35
+ }