diff --git a/CHANGELOG.md b/CHANGELOG.md index b42346fe..81fba40a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Breaking changes are marked with ⚠️. - ⚠️ Rename `namedRoutes` → `routes`, `defaultParameters` → `defaults`, `baseUrl` → `url`, and `basePort` → `port` ([#338](https://github.com/tighten/ziggy/pull/338)) - ⚠️ Make the `filter()` method on the `Ziggy` class return an instance of that class instead of a collection of routes ([#341](https://github.com/tighten/ziggy/pull/341)) - ⚠️ Make the `nameKeyedRoutes()`, `resolveBindings()`, `applyFilters()`, and `group()` methods on the `Ziggy` class, and the `generate()` method on the `CommandRouteGenerator` class, private ([#341](https://github.com/tighten/ziggy/pull/341)) +- ⚠️ Export from `index.js` instead of `route.js` ([#344](https://github.com/tighten/ziggy/pull/344)) **Deprecated** diff --git a/README.md b/README.md index 0db27793..5f1d5bc7 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ const path = require('path'); mix.webpackConfig({ resolve: { alias: { - ziggy: path.resolve('vendor/tighten/ziggy/src/js/route.js'), + ziggy: path.resolve('vendor/tighten/ziggy/dist'), }, }, }); diff --git a/dist/index.es.js b/dist/index.es.js new file mode 100644 index 00000000..1c7665be --- /dev/null +++ b/dist/index.es.js @@ -0,0 +1,2 @@ +import{stringify as t,parse as r}from"qs";class e{constructor(t,r,e){this.name=t,this.definition=r,this.bindings=r.bindings??{},this.config={absolute:!0,...e}}get template(){return`${this.config.absolute?this.definition.domain?`${this.config.url.match(/^\w+:\/\//)[0]}${this.definition.domain}${this.config.port?":"+this.config.port:""}`:this.config.url:""}/${this.definition.uri}`}get parameterSegments(){var t;return(null===(t=this.template.match(/{[^}?]+\??}/g))||void 0===t?void 0:t.map(t=>({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))??[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const r=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp(`^${r}$`).test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(r,e)=>{if([null,void 0].includes(t[e])&&this.parameterSegments.find(({name:t})=>t===e).required)throw new Error(`Ziggy error: '${e}' parameter is required for route '${this.name}'.`);return encodeURIComponent(t[e]??"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class i extends String{constructor(t,r,i=!0,s){if(super(),this.t=s??Ziggy??(null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy),t){if(!this.t.routes[t])throw new Error(`Ziggy error: route '${t}' is not in the route list.`);this.i=new e(t,this.t.routes[t],{...this.t,absolute:i}),this.s=this.h(r)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:r})=>r===t)).filter(t=>"_query"!==t).reduce((t,r)=>({...t,[r]:this.s[r]}),{});return this.i.compile(this.s)+t({...r,...this.s._query},{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,r){const i=window.location.host+window.location.pathname,[s,n]=Object.entries(this.t.routes).find(([r,s])=>new e(t,s,this.t).matchesUrl(i));if(!t)return s;const h=new RegExp(`^${t.replace(".","\\.").replace("*",".*")}$`).test(s);return r?(r=this.h(r,new e(s,n,this.t)),Object.entries(this.o(n)).filter(([t])=>r.hasOwnProperty(t)).every(([t,e])=>r[t]==e)):h}get params(){return this.o(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}h(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const e=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,i)=>({...t,[e[i].name]:r}),{}):1!==e.length||t[e[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[e[0].name]:t}),{...this.u(r),...this.l(t,r.bindings)}}u(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},e)=>({...t,[r]:this.t.defaults[r]}),{})}l(t,r={}){return Object.entries(t).reduce((t,[e,i])=>{if(!i||"object"!=typeof i||Array.isArray(i)||"_query"===e)return{...t,[e]:i};if(!i.hasOwnProperty(r[e])){if(!i.hasOwnProperty("id"))throw new Error(`Ziggy error: object passed as '${e}' parameter is missing route model binding key '${r[e]}'.`);r[e]="id"}return{...t,[e]:i[r[e]]}},{})}o(t){var e;let i=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const s=(t,r="",e)=>{const[i,s]=[t,r].map(t=>t.split(e));return s.reduce((t,r,e)=>/^{[^}?]+\??}$/.test(r)&&i[e]?{...t,[r.replace(/^{|\??}$/g,"")]:i[e]}:t,{})};return{...s(window.location.host,t.domain,"."),...s(i,t.uri,"/"),...r(null===(e=window.location.search)||void 0===e?void 0:e.replace(/^\?/,""))}}valueOf(){return this.toString()}check(t){return this.has(t)}}export default function(t,r,e,s){const n=new i(t,r,e,s);return t?n.toString():n} +//# sourceMappingURL=index.es.js.map diff --git a/dist/index.es.js.map b/dist/index.es.js.map new file mode 100644 index 00000000..b78a1ef6 --- /dev/null +++ b/dist/index.es.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.es.js","sources":["../src/js/Route.js","../src/js/Router.js","../src/js/index.js"],"sourcesContent":["/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nexport default class Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n","import { parse, stringify } from 'qs';\nimport Route from './Route';\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nexport default class Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n","import Router from './Router';\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/dist/route.js b/dist/index.js similarity index 51% rename from dist/route.js rename to dist/index.js index af9d9863..e0c90e8a 100644 --- a/dist/route.js +++ b/dist/index.js @@ -1,2 +1,2 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("qs")):"function"==typeof define&&define.amd?define(["qs"],e):(t=t||self).route=e(t.qs)}(this,function(t){function e(){return(e=Object.assign||function(t){for(var e=1;e({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))?t:[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const e=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp("^"+e+"$").test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(e,r)=>{var i;if([null,void 0].includes(t[r])&&this.parameterSegments.find(({name:t})=>t===r).required)throw new Error("Ziggy error: '"+r+"' parameter is required for route '"+this.name+"'.");return encodeURIComponent(null!=(i=t[r])?i:"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class i extends String{constructor(t,i,n=!0,s){var o;if(super(),this.t=null!=(o=null!=s?s:Ziggy)?o:null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy,t){if(!this.t.routes[t])throw new Error("Ziggy error: route '"+t+"' is not in the route list.");this.i=new r(t,this.t.routes[t],e({},this.t,{absolute:n})),this.s=this.o(i)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:e})=>e===t)).filter(t=>"_query"!==t).reduce((t,r)=>e({},t,{[r]:this.s[r]}),{});return this.i.compile(this.s)+t.stringify(e({},r,this.s.u),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,e){const i=window.location.host+window.location.pathname,[n,s]=Object.entries(this.t.routes).find(([e,n])=>new r(t,n,this.t).matchesUrl(i));if(!t)return n;const o=new RegExp("^"+t.replace(".","\\.").replace("*",".*")+"$").test(n);return e?(e=this.o(e,new r(n,s,this.t)),Object.entries(this.h(s)).filter(([t])=>e.hasOwnProperty(t)).every(([t,r])=>e[t]==r)):o}get params(){return this.h(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}o(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const i=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,n)=>e({},t,{[i[n].name]:r}),{}):1!==i.length||t[i[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[i[0].name]:t}),e({},this.l(r),this.g(t,r.bindings))}l(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},i)=>e({},t,{[r]:this.t.defaults[r]}),{})}g(t,r={}){return Object.entries(t).reduce((t,[i,n])=>{if(!n||"object"!=typeof n||Array.isArray(n)||"_query"===i)return e({},t,{[i]:n});if(!n.hasOwnProperty(r[i])){if(!n.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+i+"' parameter is missing route model binding key '"+r[i]+"'.");r[i]="id"}return e({},t,{[i]:n[r[i]]})},{})}h(r){var i;let n=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const s=(t,r="",i)=>{const[n,s]=[t,r].map(t=>t.split(i));return s.reduce((t,r,i)=>/^{[^}?]+\??}$/.test(r)&&n[i]?e({},t,{[r.replace(/^{|\??}$/g,"")]:n[i]}):t,{})};return e({},s(window.location.host,r.domain,"."),s(n,r.uri,"/"),t.parse(null===(i=window.location.search)||void 0===i?void 0:i.replace(/^\?/,"")))}valueOf(){return this.toString()}check(t){return this.has(t)}}return function(t,e,r,n){const s=new i(t,e,r,n);return t?s.toString():s}}); -//# sourceMappingURL=route.js.map +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("qs")):"function"==typeof define&&define.amd?define(["qs"],e):(t=t||self).route=e(t.qs)}(this,function(t){function e(){return(e=Object.assign||function(t){for(var e=1;e({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))?t:[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const e=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp("^"+e+"$").test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(e,r)=>{var i;if([null,void 0].includes(t[r])&&this.parameterSegments.find(({name:t})=>t===r).required)throw new Error("Ziggy error: '"+r+"' parameter is required for route '"+this.name+"'.");return encodeURIComponent(null!=(i=t[r])?i:"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class i extends String{constructor(t,i,n=!0,s){var o;if(super(),this.t=null!=(o=null!=s?s:Ziggy)?o:null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy,t){if(!this.t.routes[t])throw new Error("Ziggy error: route '"+t+"' is not in the route list.");this.i=new r(t,this.t.routes[t],e({},this.t,{absolute:n})),this.s=this.o(i)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:e})=>e===t)).filter(t=>"_query"!==t).reduce((t,r)=>e({},t,{[r]:this.s[r]}),{});return this.i.compile(this.s)+t.stringify(e({},r,this.s._query),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,e){const i=window.location.host+window.location.pathname,[n,s]=Object.entries(this.t.routes).find(([e,n])=>new r(t,n,this.t).matchesUrl(i));if(!t)return n;const o=new RegExp("^"+t.replace(".","\\.").replace("*",".*")+"$").test(n);return e?(e=this.o(e,new r(n,s,this.t)),Object.entries(this.u(s)).filter(([t])=>e.hasOwnProperty(t)).every(([t,r])=>e[t]==r)):o}get params(){return this.u(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}o(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const i=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,n)=>e({},t,{[i[n].name]:r}),{}):1!==i.length||t[i[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[i[0].name]:t}),e({},this.h(r),this.l(t,r.bindings))}h(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},i)=>e({},t,{[r]:this.t.defaults[r]}),{})}l(t,r={}){return Object.entries(t).reduce((t,[i,n])=>{if(!n||"object"!=typeof n||Array.isArray(n)||"_query"===i)return e({},t,{[i]:n});if(!n.hasOwnProperty(r[i])){if(!n.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+i+"' parameter is missing route model binding key '"+r[i]+"'.");r[i]="id"}return e({},t,{[i]:n[r[i]]})},{})}u(r){var i;let n=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const s=(t,r="",i)=>{const[n,s]=[t,r].map(t=>t.split(i));return s.reduce((t,r,i)=>/^{[^}?]+\??}$/.test(r)&&n[i]?e({},t,{[r.replace(/^{|\??}$/g,"")]:n[i]}):t,{})};return e({},s(window.location.host,r.domain,"."),s(n,r.uri,"/"),t.parse(null===(i=window.location.search)||void 0===i?void 0:i.replace(/^\?/,"")))}valueOf(){return this.toString()}check(t){return this.has(t)}}return function(t,e,r,n){const s=new i(t,e,r,n);return t?s.toString():s}}); +//# sourceMappingURL=index.js.map diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 00000000..26cf8092 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../src/js/Route.js","../src/js/Router.js","../src/js/index.js"],"sourcesContent":["/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nexport default class Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n","import { parse, stringify } from 'qs';\nimport Route from './Route';\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nexport default class Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n","import Router from './Router';\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","_this$template$match","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.m.js b/dist/index.m.js new file mode 100644 index 00000000..9a49444f --- /dev/null +++ b/dist/index.m.js @@ -0,0 +1,2 @@ +import{stringify as t,parse as r}from"qs";function e(){return(e=Object.assign||function(t){for(var r=1;r({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))?t:[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const r=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp("^"+r+"$").test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(r,e)=>{var i;if([null,void 0].includes(t[e])&&this.parameterSegments.find(({name:t})=>t===e).required)throw new Error("Ziggy error: '"+e+"' parameter is required for route '"+this.name+"'.");return encodeURIComponent(null!=(i=t[e])?i:"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class s extends String{constructor(t,r,s=!0,n){var o;if(super(),this.t=null!=(o=null!=n?n:Ziggy)?o:null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy,t){if(!this.t.routes[t])throw new Error("Ziggy error: route '"+t+"' is not in the route list.");this.i=new i(t,this.t.routes[t],e({},this.t,{absolute:s})),this.s=this.o(r)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:r})=>r===t)).filter(t=>"_query"!==t).reduce((t,r)=>e({},t,{[r]:this.s[r]}),{});return this.i.compile(this.s)+t(e({},r,this.s._query),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,r){const e=window.location.host+window.location.pathname,[s,n]=Object.entries(this.t.routes).find(([r,s])=>new i(t,s,this.t).matchesUrl(e));if(!t)return s;const o=new RegExp("^"+t.replace(".","\\.").replace("*",".*")+"$").test(s);return r?(r=this.o(r,new i(s,n,this.t)),Object.entries(this.h(n)).filter(([t])=>r.hasOwnProperty(t)).every(([t,e])=>r[t]==e)):o}get params(){return this.h(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}o(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const i=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,s)=>e({},t,{[i[s].name]:r}),{}):1!==i.length||t[i[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[i[0].name]:t}),e({},this.u(r),this.l(t,r.bindings))}u(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},i)=>e({},t,{[r]:this.t.defaults[r]}),{})}l(t,r={}){return Object.entries(t).reduce((t,[i,s])=>{if(!s||"object"!=typeof s||Array.isArray(s)||"_query"===i)return e({},t,{[i]:s});if(!s.hasOwnProperty(r[i])){if(!s.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+i+"' parameter is missing route model binding key '"+r[i]+"'.");r[i]="id"}return e({},t,{[i]:s[r[i]]})},{})}h(t){var i;let s=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const n=(t,r="",i)=>{const[s,n]=[t,r].map(t=>t.split(i));return n.reduce((t,r,i)=>/^{[^}?]+\??}$/.test(r)&&s[i]?e({},t,{[r.replace(/^{|\??}$/g,"")]:s[i]}):t,{})};return e({},n(window.location.host,t.domain,"."),n(s,t.uri,"/"),r(null===(i=window.location.search)||void 0===i?void 0:i.replace(/^\?/,"")))}valueOf(){return this.toString()}check(t){return this.has(t)}}export default function(t,r,e,i){const n=new s(t,r,e,i);return t?n.toString():n} +//# sourceMappingURL=index.m.js.map diff --git a/dist/index.m.js.map b/dist/index.m.js.map new file mode 100644 index 00000000..d2c708ad --- /dev/null +++ b/dist/index.m.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.m.js","sources":["../src/js/Route.js","../src/js/Router.js","../src/js/index.js"],"sourcesContent":["/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nexport default class Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n","import { parse, stringify } from 'qs';\nimport Route from './Route';\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nexport default class Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n","import Router from './Router';\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","_this$template$match","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/dist/route.es.js b/dist/route.es.js deleted file mode 100644 index e07530c3..00000000 --- a/dist/route.es.js +++ /dev/null @@ -1,2 +0,0 @@ -import{stringify as t,parse as r}from"qs";class e{constructor(t,r,e){this.name=t,this.definition=r,this.bindings=r.bindings??{},this.config={absolute:!0,...e}}get template(){return`${this.config.absolute?this.definition.domain?`${this.config.url.match(/^\w+:\/\//)[0]}${this.definition.domain}${this.config.port?":"+this.config.port:""}`:this.config.url:""}/${this.definition.uri}`}get parameterSegments(){var t;return(null===(t=this.template.match(/{[^}?]+\??}/g))||void 0===t?void 0:t.map(t=>({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))??[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const r=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp(`^${r}$`).test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(r,e)=>{if([null,void 0].includes(t[e])&&this.parameterSegments.find(({name:t})=>t===e).required)throw new Error(`Ziggy error: '${e}' parameter is required for route '${this.name}'.`);return encodeURIComponent(t[e]??"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class i extends String{constructor(t,r,i=!0,s){if(super(),this.t=s??Ziggy??(null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy),t){if(!this.t.routes[t])throw new Error(`Ziggy error: route '${t}' is not in the route list.`);this.i=new e(t,this.t.routes[t],{...this.t,absolute:i}),this.s=this.h(r)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:r})=>r===t)).filter(t=>"_query"!==t).reduce((t,r)=>({...t,[r]:this.s[r]}),{});return this.i.compile(this.s)+t({...r,...this.s.o},{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,r){const i=window.location.host+window.location.pathname,[s,n]=Object.entries(this.t.routes).find(([r,s])=>new e(t,s,this.t).matchesUrl(i));if(!t)return s;const h=new RegExp(`^${t.replace(".","\\.").replace("*",".*")}$`).test(s);return r?(r=this.h(r,new e(s,n,this.t)),Object.entries(this.u(n)).filter(([t])=>r.hasOwnProperty(t)).every(([t,e])=>r[t]==e)):h}get params(){return this.u(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}h(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const e=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,i)=>({...t,[e[i].name]:r}),{}):1!==e.length||t[e[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[e[0].name]:t}),{...this.l(r),...this.g(t,r.bindings)}}l(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},e)=>({...t,[r]:this.t.defaults[r]}),{})}g(t,r={}){return Object.entries(t).reduce((t,[e,i])=>{if(!i||"object"!=typeof i||Array.isArray(i)||"_query"===e)return{...t,[e]:i};if(!i.hasOwnProperty(r[e])){if(!i.hasOwnProperty("id"))throw new Error(`Ziggy error: object passed as '${e}' parameter is missing route model binding key '${r[e]}'.`);r[e]="id"}return{...t,[e]:i[r[e]]}},{})}u(t){var e;let i=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const s=(t,r="",e)=>{const[i,s]=[t,r].map(t=>t.split(e));return s.reduce((t,r,e)=>/^{[^}?]+\??}$/.test(r)&&i[e]?{...t,[r.replace(/^{|\??}$/g,"")]:i[e]}:t,{})};return{...s(window.location.host,t.domain,"."),...s(i,t.uri,"/"),...r(null===(e=window.location.search)||void 0===e?void 0:e.replace(/^\?/,""))}}valueOf(){return this.toString()}check(t){return this.has(t)}}export default function(t,r,e,s){const n=new i(t,r,e,s);return t?n.toString():n} -//# sourceMappingURL=route.es.js.map diff --git a/dist/route.es.js.map b/dist/route.es.js.map deleted file mode 100644 index 5d8c1d45..00000000 --- a/dist/route.es.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"route.es.js","sources":["../src/js/route.js"],"sourcesContent":["import { parse, stringify } from 'qs';\n\n/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nclass Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nclass Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/dist/route.js.map b/dist/route.js.map deleted file mode 100644 index b7ee8aac..00000000 --- a/dist/route.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"route.js","sources":["../src/js/route.js"],"sourcesContent":["import { parse, stringify } from 'qs';\n\n/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nclass Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nclass Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","_this$template$match","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/dist/route.m.js b/dist/route.m.js deleted file mode 100644 index 08d05309..00000000 --- a/dist/route.m.js +++ /dev/null @@ -1,2 +0,0 @@ -import{stringify as t,parse as r}from"qs";function e(){return(e=Object.assign||function(t){for(var r=1;r({name:t.replace(/{|\??}/g,""),required:!/\?}$/.test(t)})))?t:[]}matchesUrl(t){if(!this.definition.methods.includes("GET"))return!1;const r=this.template.replace(/\/{[^}?]*\?}/g,"(/[^/?]+)?").replace(/{[^}]+}/g,"[^/?]+").replace(/^\w+:\/\//,"");return new RegExp("^"+r+"$").test(t.replace(/\/+$/,"").split("?").shift())}compile(t){return this.parameterSegments.length?this.template.replace(/{([^}?]+)\??}/g,(r,e)=>{var i;if([null,void 0].includes(t[e])&&this.parameterSegments.find(({name:t})=>t===e).required)throw new Error("Ziggy error: '"+e+"' parameter is required for route '"+this.name+"'.");return encodeURIComponent(null!=(i=t[e])?i:"")}).replace(/\/+$/,""):this.template.replace(/\/+$/,"")}}class s extends String{constructor(t,r,s=!0,n){var o;if(super(),this.t=null!=(o=null!=n?n:Ziggy)?o:null===globalThis||void 0===globalThis?void 0:globalThis.Ziggy,t){if(!this.t.routes[t])throw new Error("Ziggy error: route '"+t+"' is not in the route list.");this.i=new i(t,this.t.routes[t],e({},this.t,{absolute:s})),this.s=this.o(r)}}toString(){const r=Object.keys(this.s).filter(t=>!this.i.parameterSegments.some(({name:r})=>r===t)).filter(t=>"_query"!==t).reduce((t,r)=>e({},t,{[r]:this.s[r]}),{});return this.i.compile(this.s)+t(e({},r,this.s.h),{addQueryPrefix:!0,arrayFormat:"indices",encodeValuesOnly:!0,skipNulls:!0})}current(t,r){const e=window.location.host+window.location.pathname,[s,n]=Object.entries(this.t.routes).find(([r,s])=>new i(t,s,this.t).matchesUrl(e));if(!t)return s;const o=new RegExp("^"+t.replace(".","\\.").replace("*",".*")+"$").test(s);return r?(r=this.o(r,new i(s,n,this.t)),Object.entries(this.u(n)).filter(([t])=>r.hasOwnProperty(t)).every(([t,e])=>r[t]==e)):o}get params(){return this.u(this.t.routes[this.current()])}has(t){return Object.keys(this.t.routes).includes(t)}o(t={},r=this.i){t=["string","number"].includes(typeof t)?[t]:t;const i=r.parameterSegments.filter(({name:t})=>!this.t.defaults[t]);return Array.isArray(t)?t=t.reduce((t,r,s)=>e({},t,{[i[s].name]:r}),{}):1!==i.length||t[i[0].name]||!t.hasOwnProperty(Object.values(r.bindings)[0])&&!t.hasOwnProperty("id")||(t={[i[0].name]:t}),e({},this.l(r),this.g(t,r.bindings))}l(t){return t.parameterSegments.filter(({name:t})=>this.t.defaults[t]).reduce((t,{name:r},i)=>e({},t,{[r]:this.t.defaults[r]}),{})}g(t,r={}){return Object.entries(t).reduce((t,[i,s])=>{if(!s||"object"!=typeof s||Array.isArray(s)||"_query"===i)return e({},t,{[i]:s});if(!s.hasOwnProperty(r[i])){if(!s.hasOwnProperty("id"))throw new Error("Ziggy error: object passed as '"+i+"' parameter is missing route model binding key '"+r[i]+"'.");r[i]="id"}return e({},t,{[i]:s[r[i]]})},{})}u(t){var i;let s=window.location.pathname.replace(this.t.url.replace(/^\w*:\/\/[^/]+/,""),"").replace(/^\/+/,"");const n=(t,r="",i)=>{const[s,n]=[t,r].map(t=>t.split(i));return n.reduce((t,r,i)=>/^{[^}?]+\??}$/.test(r)&&s[i]?e({},t,{[r.replace(/^{|\??}$/g,"")]:s[i]}):t,{})};return e({},n(window.location.host,t.domain,"."),n(s,t.uri,"/"),r(null===(i=window.location.search)||void 0===i?void 0:i.replace(/^\?/,"")))}valueOf(){return this.toString()}check(t){return this.has(t)}}export default function(t,r,e,i){const n=new s(t,r,e,i);return t?n.toString():n} -//# sourceMappingURL=route.m.js.map diff --git a/dist/route.m.js.map b/dist/route.m.js.map deleted file mode 100644 index f76b8e5c..00000000 --- a/dist/route.m.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"route.m.js","sources":["../src/js/route.js"],"sourcesContent":["import { parse, stringify } from 'qs';\n\n/**\n * A Laravel route. This class represents one route and its configuration and metadata.\n */\nclass Route {\n /**\n * @param {String} name - Route name.\n * @param {Object} definition - Route definition.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, definition, config) {\n this.name = name;\n this.definition = definition;\n this.bindings = definition.bindings ?? {};\n this.config = { absolute: true, ...config };\n }\n\n /**\n * Get a 'template' of the complete URL for this route.\n *\n * @example\n * https://{team}.ziggy.dev/user/{user}\n *\n * @return {String} Route template.\n */\n get template() {\n // If we're building just a path there's no origin, otherwise: if this route has a\n // domain configured we construct the origin with that, if not we use the app URL\n const origin = !this.config.absolute ? '' : this.definition.domain\n ? `${this.config.url.match(/^\\w+:\\/\\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}`\n : this.config.url;\n\n return `${origin}/${this.definition.uri}`;\n }\n\n /**\n * Get an array of objects representing the parameters that this route accepts.\n *\n * @example\n * [{ name: 'team', required: true }, { name: 'user', required: false }]\n *\n * @return {Array} Parameter segments.\n */\n get parameterSegments() {\n return this.template.match(/{[^}?]+\\??}/g)?.map((segment) => ({\n name: segment.replace(/{|\\??}/g, ''),\n required: !/\\?}$/.test(segment),\n })) ?? [];\n }\n\n /**\n * Get whether this route's template matches the given URL.\n *\n * @param {String} url - URL to check.\n * @return {Boolean} Whether this route matches.\n */\n matchesUrl(url) {\n if (!this.definition.methods.includes('GET')) return false;\n\n // Transform the route's template into a regex that will match a hydrated URL,\n // by replacing its parameter segments with matchers for parameter values\n const pattern = this.template\n .replace(/\\/{[^}?]*\\?}/g, '(\\/[^/?]+)?')\n .replace(/{[^}]+}/g, '[^/?]+')\n .replace(/^\\w+:\\/\\//, '');\n\n return new RegExp(`^${pattern}$`).test(url.replace(/\\/+$/, '').split('?').shift());\n }\n\n /**\n * Hydrate and return a complete URL for this route with the given parameters.\n *\n * @param {Object} params\n * @return {String}\n */\n compile(params) {\n if (!this.parameterSegments.length) return this.template.replace(/\\/+$/, '');\n\n return this.template.replace(/{([^}?]+)\\??}/g, (_, segment) => {\n // If the parameter is missing but is not optional, throw an error\n if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) {\n throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`)\n }\n\n return encodeURIComponent(params[segment] ?? '');\n }).replace(/\\/+$/, '');\n }\n}\n\n/**\n * A collection of Laravel routes. This class constitutes Ziggy's main API.\n */\nclass Router extends String {\n /**\n * @param {String} name - Route name.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Boolean} absolute - Whether to include the URL origin.\n * @param {Object} config - Ziggy configuration.\n */\n constructor(name, params, absolute = true, config) {\n super();\n\n this._config = config ?? Ziggy ?? globalThis?.Ziggy;\n\n if (name) {\n if (!this._config.routes[name]) {\n throw new Error(`Ziggy error: route '${name}' is not in the route list.`);\n }\n\n this._route = new Route(name, this._config.routes[name], { ...this._config, absolute });\n this._params = this._parse(params);\n }\n }\n\n /**\n * Get the compiled URL string for the current route and parameters.\n *\n * @example\n * // with 'posts.show' route 'posts/{post}'\n * (new Router('posts.show', 1)).toString(); // 'https://ziggy.dev/posts/1'\n *\n * @return {String}\n */\n toString() {\n // Get parameters that don't correspond to any route segments to append them to the query\n const unhandled = Object.keys(this._params)\n .filter((key) => !this._route.parameterSegments.some(({ name }) => name === key))\n .filter((key) => key !== '_query')\n .reduce((result, current) => ({ ...result, [current]: this._params[current] }), {});\n\n return this._route.compile(this._params) + stringify({ ...unhandled, ...this._params['_query'] }, {\n addQueryPrefix: true,\n arrayFormat: 'indices',\n encodeValuesOnly: true,\n skipNulls: true,\n });\n }\n\n /**\n * Get the name of the route matching the current window URL, or, given a route name\n * and parameters, check if the current window URL and parameters match that route.\n *\n * @example\n * // at URL https://ziggy.dev/posts/4 with 'posts.show' route 'posts/{post}'\n * route().current(); // 'posts.show'\n * route().current('posts.index'); // false\n * route().current('posts.show'); // true\n * route().current('posts.show', { post: 1 }); // false\n * route().current('posts.show', { post: 4 }); // true\n *\n * @param {String} name - Route name to check.\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @return {(Boolean|String)}\n */\n current(name, params) {\n const url = window.location.host + window.location.pathname;\n\n // Find the first route that matches the current URL\n const [current, route] = Object.entries(this._config.routes).find(\n ([_, route]) => new Route(name, route, this._config).matchesUrl(url)\n );\n\n // If a name wasn't passed, return the name of the current route\n if (!name) return current;\n\n // Test the passed name against the current route, matching some\n // basic wildcards, e.g. passing `events.*` matches `events.show`\n const match = new RegExp(`^${name.replace('.', '\\\\.').replace('*', '.*')}$`).test(current);\n\n if (!params) return match;\n\n params = this._parse(params, new Route(current, route, this._config));\n\n // Check that all passed parameters match their values in the current window URL\n return Object.entries(this._dehydrate(route))\n .filter(([key]) => params.hasOwnProperty(key))\n // Use weak equality because all values in the current window URL will be strings\n .every(([key, value]) => params[key] == value);\n }\n\n /**\n * Get all parameter values from the current window URL.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/posts/4?lang=en with 'posts.show' route 'posts/{post}' and domain '{team}.ziggy.dev'\n * route().params; // { team: 'tighten', post: 4, lang: 'en' }\n *\n * @return {Object}\n */\n get params() {\n return this._dehydrate(this._config.routes[this.current()]);\n }\n\n /**\n * Check whether the given route exists.\n *\n * @param {String} name\n * @return {Boolean}\n */\n has(name) {\n return Object.keys(this._config.routes).includes(name);\n }\n\n /**\n * Parse Laravel-style route parameters of any type into a normalized object.\n *\n * @example\n * // with route parameter names 'event' and 'venue'\n * _parse(1); // { event: 1 }\n * _parse({ event: 2, venue: 3 }); // { event: 2, venue: 3 }\n * _parse(['Taylor', 'Matt']); // { event: 'Taylor', venue: 'Matt' }\n * _parse([4, { uuid: 56789, name: 'Grand Canyon' }]); // { event: 4, venue: 56789 }\n *\n * @param {(String|Number|Array|Object)} params - Route parameters.\n * @param {Route} route - Route instance.\n * @return {Object} Normalized complete route parameters.\n */\n _parse(params = {}, route = this._route) {\n // If `params` is a string or integer, wrap it in an array\n params = ['string', 'number'].includes(typeof params) ? [params] : params;\n\n // Separate segments with and without defaults, and fill in the default values\n const segments = route.parameterSegments.filter(({ name }) => !this._config.defaults[name]);\n\n if (Array.isArray(params)) {\n // If the parameters are an array they have to be in order, so we can transform them into\n // an object by keying them with the template segment names in the order they appear\n params = params.reduce((result, current, i) => ({ ...result, [segments[i].name]: current }), {});\n } else if (\n segments.length === 1\n && !params[segments[0].name]\n && (params.hasOwnProperty(Object.values(route.bindings)[0]) || params.hasOwnProperty('id'))\n ) {\n // If there is only one template segment and `params` is an object, that object is\n // ambiguous—it could contain the parameter key and value, or it could be an object\n // representing just the value (e.g. a model); we can inspect it to find out, and\n // if it's just the parameter value, we can wrap it in an object with its key\n params = { [segments[0].name]: params };\n }\n\n return {\n ...this._defaults(route),\n ...this._substituteBindings(params, route.bindings),\n };\n }\n\n /**\n * Populate default parameters for the given route.\n *\n * @example\n * // with default parameters { locale: 'en', country: 'US' } and 'posts.show' route '{locale}/posts/{post}'\n * defaults(...); // { locale: 'en' }\n *\n * @param {Route} route\n * @return {Object} Default route parameters.\n */\n _defaults(route) {\n return route.parameterSegments.filter(({ name }) => this._config.defaults[name])\n .reduce((result, { name }, i) => ({ ...result, [name]: this._config.defaults[name] }), {});\n }\n\n /**\n * Substitute Laravel route model bindings in the given parameters.\n *\n * @example\n * _substituteBindings({ post: { id: 4, slug: 'hello-world', title: 'Hello, world!' } }, { post: 'slug' }); // { post: 'hello-world' }\n *\n * @param {Object} params - Route parameters.\n * @param {Object} bindings - Route model bindings.\n * @return {Object} Normalized route parameters.\n */\n _substituteBindings(params, bindings = {}) {\n return Object.entries(params).reduce((result, [key, value]) => {\n // If the value isn't an object, or if it's an object of explicity query\n // parameters, there's nothing to substitute so we return it as-is\n if (!value || typeof value !== 'object' || Array.isArray(value) || key === '_query') {\n return { ...result, [key]: value };\n }\n\n if (!value.hasOwnProperty(bindings[key])) {\n if (value.hasOwnProperty('id')) {\n // As a fallback, we still accept an 'id' key not explicitly registered as a binding\n bindings[key] = 'id';\n } else {\n throw new Error(`Ziggy error: object passed as '${key}' parameter is missing route model binding key '${bindings[key]}'.`)\n }\n }\n\n return { ...result, [key]: value[bindings[key]] };\n }, {});\n }\n\n /**\n * Get all parameters and their values from the current window URL, based on the given route definition.\n *\n * @example\n * // at URL https://tighten.ziggy.dev/events/8/venues/chicago?zoom=true\n * _dehydrate({ domain: '{team}.ziggy.dev', uri: 'events/{event}/venues/{venue?}' }); // { team: 'tighten', event: 8, venue: 'chicago', zoom: true }\n *\n * @param {Object} route - Route definition.\n * @return {Object} Parameters.\n */\n _dehydrate(route) {\n let pathname = window.location.pathname\n // If this Laravel app is in a subdirectory, trim the subdirectory from the path\n .replace(this._config.url.replace(/^\\w*:\\/\\/[^/]+/, ''), '')\n .replace(/^\\/+/, '');\n\n // Given part of a valid 'hydrated' URL containing all its parameter values,\n // a route template, and a delimiter, extract the parameters as an object\n // E.g. dehydrate('events/{event}/{venue}', 'events/2/chicago', '/'); // { event: 2, venue: 'chicago' }\n const dehydrate = (hydrated, template = '', delimiter) => {\n const [values, segments] = [hydrated, template].map(s => s.split(delimiter));\n\n return segments.reduce((result, current, i) => {\n // Only include template segments that are route parameters\n // AND have a value present in the passed hydrated string\n return /^{[^}?]+\\??}$/.test(current) && values[i]\n ? { ...result, [current.replace(/^{|\\??}$/g, '')]: values[i] }\n : result;\n }, {});\n }\n\n return {\n ...dehydrate(window.location.host, route.domain, '.'), // Domain parameters\n ...dehydrate(pathname, route.uri, '/'), // Path parameters\n ...parse(window.location.search?.replace(/^\\?/, '')), // Query parameters\n };\n }\n\n valueOf() {\n return this.toString();\n }\n\n /**\n * @deprecated since v1.0, use `has()` instead.\n */\n check(name) {\n return this.has(name);\n }\n}\n\nexport default function route(name, params, absolute, config) {\n const router = new Router(name, params, absolute, config);\n\n return name ? router.toString() : router;\n}\n"],"names":["Route","constructor","name","definition","config","this","bindings","absolute","template","domain","url","match","port","uri","parameterSegments","_this$template$match","map","segment","replace","required","test","matchesUrl","methods","includes","pattern","RegExp","split","shift","compile","params","length","_","undefined","find","Error","encodeURIComponent","Router","String","super","_config","Ziggy","globalThis","routes","_route","_params","_parse","toString","unhandled","Object","keys","filter","key","some","reduce","result","current","[object Object]","stringify","addQueryPrefix","arrayFormat","encodeValuesOnly","skipNulls","window","location","host","pathname","route","entries","_dehydrate","hasOwnProperty","every","value","has","segments","defaults","Array","isArray","i","values","_defaults","_substituteBindings","dehydrate","hydrated","delimiter","s","parse","search","_window$location$sear","valueOf","check","router"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index 5680dfd2..f3a32fb3 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,13 @@ "src/js", "dist" ], - "source": "src/js/route.js", - "main": "dist/route.js", - "umd:main": "dist/route.js", - "unpkg": "dist/route.js", - "browser": "dist/route.js", - "module": "dist/route.m.js", - "esmodule": "dist/route.es.js", + "source": "src/js/index.js", + "main": "dist/index.js", + "umd:main": "dist/index.js", + "unpkg": "dist/index.js", + "browser": "dist/index.js", + "module": "dist/index.m.js", + "esmodule": "dist/index.es.js", "repository": { "type": "git", "url": "https://github.com/tighten/ziggy.git" @@ -45,7 +45,7 @@ "prepublishOnly": "npm run build" }, "mangle": { - "regex": "^_" + "regex": "^_(?!query)" }, "dependencies": { "qs": "^6.8.0" diff --git a/src/BladeRouteGenerator.php b/src/BladeRouteGenerator.php index bc57dbff..6ea51d58 100644 --- a/src/BladeRouteGenerator.php +++ b/src/BladeRouteGenerator.php @@ -21,7 +21,7 @@ public function generate($group = false, $nonce = false) return << - var Ziggy = {$payload->toJson()}; + const Ziggy = {$payload->toJson()}; $routeFunction @@ -32,10 +32,10 @@ private function generateMergeJavascript($json, $nonce) { return << - (function() { - var routes = {$json}; + (function () { + const routes = {$json}; - for (var name in routes) { + for (let name in routes) { Ziggy.routes[name] = routes[name]; } })(); @@ -45,7 +45,7 @@ private function generateMergeJavascript($json, $nonce) private function getRouteFilePath() { - return __DIR__ . '/../dist/route.js'; + return __DIR__ . '/../dist/index.js'; } private function getRouteFunction() diff --git a/src/CommandRouteGenerator.php b/src/CommandRouteGenerator.php index 7f6b80c4..b96a1efa 100644 --- a/src/CommandRouteGenerator.php +++ b/src/CommandRouteGenerator.php @@ -40,10 +40,10 @@ private function generate($group = false) $payload = (new Ziggy($group, url($this->option('url'))))->toJson(); return << ({ + name: segment.replace(/{|\??}/g, ''), + required: !/\?}$/.test(segment), + })) ?? []; + } + + /** + * Get whether this route's template matches the given URL. + * + * @param {String} url - URL to check. + * @return {Boolean} Whether this route matches. + */ + matchesUrl(url) { + if (!this.definition.methods.includes('GET')) return false; + + // Transform the route's template into a regex that will match a hydrated URL, + // by replacing its parameter segments with matchers for parameter values + const pattern = this.template + .replace(/\/{[^}?]*\?}/g, '(\/[^/?]+)?') + .replace(/{[^}]+}/g, '[^/?]+') + .replace(/^\w+:\/\//, ''); + + return new RegExp(`^${pattern}$`).test(url.replace(/\/+$/, '').split('?').shift()); + } + + /** + * Hydrate and return a complete URL for this route with the given parameters. + * + * @param {Object} params + * @return {String} + */ + compile(params) { + if (!this.parameterSegments.length) return this.template.replace(/\/+$/, ''); + + return this.template.replace(/{([^}?]+)\??}/g, (_, segment) => { + // If the parameter is missing but is not optional, throw an error + if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) { + throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`) + } + + return encodeURIComponent(params[segment] ?? ''); + }).replace(/\/+$/, ''); + } +} diff --git a/src/js/route.js b/src/js/Router.js similarity index 75% rename from src/js/route.js rename to src/js/Router.js index 4f889334..b5a3cbdb 100644 --- a/src/js/route.js +++ b/src/js/Router.js @@ -1,97 +1,10 @@ import { parse, stringify } from 'qs'; - -/** - * A Laravel route. This class represents one route and its configuration and metadata. - */ -class Route { - /** - * @param {String} name - Route name. - * @param {Object} definition - Route definition. - * @param {Object} config - Ziggy configuration. - */ - constructor(name, definition, config) { - this.name = name; - this.definition = definition; - this.bindings = definition.bindings ?? {}; - this.config = { absolute: true, ...config }; - } - - /** - * Get a 'template' of the complete URL for this route. - * - * @example - * https://{team}.ziggy.dev/user/{user} - * - * @return {String} Route template. - */ - get template() { - // If we're building just a path there's no origin, otherwise: if this route has a - // domain configured we construct the origin with that, if not we use the app URL - const origin = !this.config.absolute ? '' : this.definition.domain - ? `${this.config.url.match(/^\w+:\/\//)[0]}${this.definition.domain}${this.config.port ? `:${this.config.port}` : ''}` - : this.config.url; - - return `${origin}/${this.definition.uri}`; - } - - /** - * Get an array of objects representing the parameters that this route accepts. - * - * @example - * [{ name: 'team', required: true }, { name: 'user', required: false }] - * - * @return {Array} Parameter segments. - */ - get parameterSegments() { - return this.template.match(/{[^}?]+\??}/g)?.map((segment) => ({ - name: segment.replace(/{|\??}/g, ''), - required: !/\?}$/.test(segment), - })) ?? []; - } - - /** - * Get whether this route's template matches the given URL. - * - * @param {String} url - URL to check. - * @return {Boolean} Whether this route matches. - */ - matchesUrl(url) { - if (!this.definition.methods.includes('GET')) return false; - - // Transform the route's template into a regex that will match a hydrated URL, - // by replacing its parameter segments with matchers for parameter values - const pattern = this.template - .replace(/\/{[^}?]*\?}/g, '(\/[^/?]+)?') - .replace(/{[^}]+}/g, '[^/?]+') - .replace(/^\w+:\/\//, ''); - - return new RegExp(`^${pattern}$`).test(url.replace(/\/+$/, '').split('?').shift()); - } - - /** - * Hydrate and return a complete URL for this route with the given parameters. - * - * @param {Object} params - * @return {String} - */ - compile(params) { - if (!this.parameterSegments.length) return this.template.replace(/\/+$/, ''); - - return this.template.replace(/{([^}?]+)\??}/g, (_, segment) => { - // If the parameter is missing but is not optional, throw an error - if ([null, undefined].includes(params[segment]) && this.parameterSegments.find(({ name }) => name === segment).required) { - throw new Error(`Ziggy error: '${segment}' parameter is required for route '${this.name}'.`) - } - - return encodeURIComponent(params[segment] ?? ''); - }).replace(/\/+$/, ''); - } -} +import Route from './Route'; /** * A collection of Laravel routes. This class constitutes Ziggy's main API. */ -class Router extends String { +export default class Router extends String { /** * @param {String} name - Route name. * @param {(String|Number|Array|Object)} params - Route parameters. @@ -340,9 +253,3 @@ class Router extends String { return this.has(name); } } - -export default function route(name, params, absolute, config) { - const router = new Router(name, params, absolute, config); - - return name ? router.toString() : router; -} diff --git a/src/js/index.js b/src/js/index.js new file mode 100644 index 00000000..2b9dd5d3 --- /dev/null +++ b/src/js/index.js @@ -0,0 +1,7 @@ +import Router from './Router'; + +export default function route(name, params, absolute, config) { + const router = new Router(name, params, absolute, config); + + return name ? router.toString() : router; +} diff --git a/tests/Unit/BladeRouteGeneratorTest.php b/tests/Unit/BladeRouteGeneratorTest.php index ae0b01ad..ad1c1f54 100644 --- a/tests/Unit/BladeRouteGeneratorTest.php +++ b/tests/Unit/BladeRouteGeneratorTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Str; use Tests\TestCase; use Tightenco\Ziggy\BladeRouteGenerator; +use Tightenco\Ziggy\Ziggy; class BladeRouteGeneratorTest extends TestCase { @@ -115,4 +116,51 @@ public function can_compile_routes_directive() BladeRouteGenerator::$generated = false; $this->assertSame($script, $compiler->compileString('@routes')); } + + /** @test */ + public function can_output_script_tag() + { + $router = app('router'); + $router->get('posts', $this->noop())->name('posts.index'); + BladeRouteGenerator::$generated = false; + + $json = (new Ziggy)->toJson(); + $routeFunction = file_get_contents(__DIR__ . '/../../dist/index.js'); + + $this->assertSame( + << + const Ziggy = {$json}; + + {$routeFunction} + +HTML, + (new BladeRouteGenerator)->generate() + ); + } + + /** @test */ + public function can_output_merge_script_tag() + { + $router = app('router'); + $router->get('posts', $this->noop())->name('posts.index'); + (new BladeRouteGenerator)->generate(); + + $json = json_encode((new Ziggy)->toArray()['routes']); + + $this->assertSame( + << + (function () { + const routes = {$json}; + + for (let name in routes) { + Ziggy.routes[name] = routes[name]; + } + })(); + +HTML, + (new BladeRouteGenerator)->generate() + ); + } } diff --git a/tests/fixtures/admin.js b/tests/fixtures/admin.js index 1784a0f4..6ac2e9a4 100644 --- a/tests/fixtures/admin.js +++ b/tests/fixtures/admin.js @@ -1,7 +1,7 @@ -var Ziggy = {"url":"http:\/\/ziggy.dev","port":null,"defaults":[],"routes":{"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]}}}; +const Ziggy = {"url":"http:\/\/ziggy.dev","port":null,"defaults":[],"routes":{"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]}}}; if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { - for (var name in window.Ziggy.routes) { + for (let name in window.Ziggy.routes) { Ziggy.routes[name] = window.Ziggy.routes[name]; } } diff --git a/tests/fixtures/custom-url.js b/tests/fixtures/custom-url.js index 41781e9d..cb63e3e0 100644 --- a/tests/fixtures/custom-url.js +++ b/tests/fixtures/custom-url.js @@ -1,7 +1,7 @@ -var Ziggy = {"url":"http:\/\/example.org","port":null,"defaults":{"locale":"en"},"routes":{"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"]}}}; +const Ziggy = {"url":"http:\/\/example.org","port":null,"defaults":{"locale":"en"},"routes":{"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"]}}}; if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { - for (var name in window.Ziggy.routes) { + for (let name in window.Ziggy.routes) { Ziggy.routes[name] = window.Ziggy.routes[name]; } } diff --git a/tests/fixtures/ziggy.js b/tests/fixtures/ziggy.js index b1fd4ae0..8b150c3c 100644 --- a/tests/fixtures/ziggy.js +++ b/tests/fixtures/ziggy.js @@ -1,7 +1,7 @@ -var Ziggy = {"url":"http:\/\/ziggy.dev","port":null,"defaults":[],"routes":{"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"]}}}; +const Ziggy = {"url":"http:\/\/ziggy.dev","port":null,"defaults":[],"routes":{"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"]}}}; if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { - for (var name in window.Ziggy.routes) { + for (let name in window.Ziggy.routes) { Ziggy.routes[name] = window.Ziggy.routes[name]; } } diff --git a/tests/js/route.test.js b/tests/js/route.test.js index 9aa12523..bf58ee19 100644 --- a/tests/js/route.test.js +++ b/tests/js/route.test.js @@ -1,5 +1,5 @@ import assert, { deepEqual, strictEqual as same, throws } from 'assert'; -import route from '../../src/js/route.js'; +import route from '../../src/js'; const defaultWindow = { location: {