From 274cb2fff2a0f870a7215f245be26c11bf96ae92 Mon Sep 17 00:00:00 2001 From: James Forbes Date: Wed, 25 Jan 2023 07:17:06 +1100 Subject: [PATCH 01/68] Release Artifacts for v2.2.3 [skip ci] --- README.md | 2 +- docs/recent-changes.md | 8 ++ mithril.js | 222 +++++++++++++++++++---------------------- mithril.min.js | 2 +- package-lock.json | 4 +- package.json | 2 +- stream/stream.min.js | 2 +- 7 files changed, 115 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 17363f7c..0f452c17 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## What is Mithril.js? -A modern client-side JavaScript framework for building Single Page Applications. It's small (9.17 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side JavaScript framework for building Single Page Applications. It's small (9.14 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril.js is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. diff --git a/docs/recent-changes.md b/docs/recent-changes.md index 3f940959..021a3736 100644 --- a/docs/recent-changes.md +++ b/docs/recent-changes.md @@ -1,4 +1,12 @@ +# Release v2.2.3 + +### Patch Changes + +#### [Use markdown for the README badges (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2773) + +Use markdown for the README badges. + # Release v2.2.2 ### Patch Changes diff --git a/mithril.js b/mithril.js index 9b6d65ec..20825dbd 100644 --- a/mithril.js +++ b/mithril.js @@ -159,13 +159,32 @@ hyperscript.fragment = function() { vnode2.children = Vnode.normalizeChildren(vnode2.children) return vnode2 } +var delayedRemoval0 = new WeakMap +function *domFor1({dom, domSize0}, {generation0} = {}) { + if (dom != null) do { + const {nextSibling} = dom + if (delayedRemoval0.get(dom) === generation0) { + yield dom + domSize0-- + } + dom = nextSibling + } + while (domSize0) +} +var df = { + delayedRemoval: delayedRemoval0, + domFor: domFor1, +} +var delayedRemoval = df.delayedRemoval +var domFor0 = df.domFor var _11 = function($window) { var $doc = $window && $window.document - var currentRedraw var nameSpace = { svg: "http://www.w3.org/2000/svg", math: "http://www.w3.org/1998/Math/MathML" } + var currentRedraw + var currentRender function getNameSpace(vnode3) { return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag] } @@ -219,7 +238,7 @@ var _11 = function($window) { } function createText(parent, vnode3, nextSibling) { vnode3.dom = $doc.createTextNode(vnode3.children) - insertNode(parent, vnode3.dom, nextSibling) + insertDOM(parent, vnode3.dom, nextSibling) } var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} function createHTML(parent, vnode3, ns, nextSibling) { @@ -239,14 +258,12 @@ var _11 = function($window) { vnode3.dom = temp.firstChild vnode3.domSize = temp.childNodes.length // Capture nodes to remove, so we don't confuse them. - vnode3.instance = [] var fragment = $doc.createDocumentFragment() var child while (child = temp.firstChild) { - vnode3.instance.push(child) fragment.appendChild(child) } - insertNode(parent, fragment, nextSibling) + insertDOM(parent, fragment, nextSibling) } function createFragment(parent, vnode3, hooks, ns, nextSibling) { var fragment = $doc.createDocumentFragment() @@ -256,7 +273,7 @@ var _11 = function($window) { } vnode3.dom = fragment.firstChild vnode3.domSize = fragment.childNodes.length - insertNode(parent, fragment, nextSibling) + insertDOM(parent, fragment, nextSibling) } function createElement(parent, vnode3, hooks, ns, nextSibling) { var tag = vnode3.tag @@ -270,7 +287,7 @@ var _11 = function($window) { if (attrs2 != null) { setAttrs(vnode3, attrs2, ns) } - insertNode(parent, element, nextSibling) + insertDOM(parent, element, nextSibling) if (!maybeSetContentEditable(vnode3)) { if (vnode3.children != null) { var children2 = vnode3.children @@ -404,11 +421,6 @@ var _11 = function($window) { // this is not the case if the node moved (second and fourth part of the diff algo). We move // the old DOM nodes before updateNode runs because it enables us to use the cached `nextSibling` // variable rather than fetching it using `getNextSibling()`. - // - // The fourth part of the diff currently inserts nodes unconditionally, leading to issues - // like #1791 and #1999. We need to be smarter about those situations where adjascent old - // nodes remain together in the new list in a way that isn't covered by parts one and - // three of the diff algo. function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { if (old === vnodes || old == null && vnodes == null) return else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) @@ -464,9 +476,9 @@ var _11 = function($window) { if (start === end) break if (o.key !== ve.key || oe.key !== v.key) break topSibling = getNextSibling(old, oldStart, nextSibling) - moveNodes(parent, oe, topSibling) + moveDOM(parent, oe, topSibling) if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns) - if (++start <= --end) moveNodes(parent, o, nextSibling) + if (++start <= --end) moveDOM(parent, o, nextSibling) if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns) if (ve.dom != null) nextSibling = ve.dom oldStart++; oldEnd-- @@ -518,7 +530,7 @@ var _11 = function($window) { if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) else { if (lisIndices[li] === i - start) li-- - else moveNodes(parent, v, nextSibling) + else moveDOM(parent, v, nextSibling) } if (v.dom != null) nextSibling = vnodes[i].dom } @@ -566,13 +578,12 @@ var _11 = function($window) { } function updateHTML(parent, old, vnode3, ns, nextSibling) { if (old.children !== vnode3.children) { - removeHTML(parent, old) + removeDOM(parent, old, undefined) createHTML(parent, vnode3, ns, nextSibling) } else { vnode3.dom = old.dom vnode3.domSize = old.domSize - vnode3.instance = old.instance } } function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { @@ -685,45 +696,21 @@ var _11 = function($window) { } return nextSibling } - // This covers a really specific edge case: - // - Parent node is keyed and contains child - // - Child is removed, returns unresolved promise in `onbeforeremove` - // - Parent node is moved in keyed diff - // - Remaining children2 still need moved appropriately - // - // Ideally, I'd track removed nodes as well, but that introduces a lot more - // complexity and I'm2 not exactly interested in doing that. - function moveNodes(parent, vnode3, nextSibling) { - var frag = $doc.createDocumentFragment() - moveChildToFrag(parent, frag, vnode3) - insertNode(parent, frag, nextSibling) - } - function moveChildToFrag(parent, frag, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== "string") { - vnode3 = vnode3.instance - if (vnode3 != null) continue - } else if (vnode3.tag === "<") { - for (var i = 0; i < vnode3.instance.length; i++) { - frag.appendChild(vnode3.instance[i]) - } - } else if (vnode3.tag !== "[") { - // Don't recurse for text nodes *or* elements, just fragments - frag.appendChild(vnode3.dom) - } else if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0] - if (vnode3 != null) continue + // This handles fragments with zombie children2 (removed from vdom, but persisted in DOM through onbeforeremove) + function moveDOM(parent, vnode3, nextSibling) { + if (vnode3.dom != null) { + var target + if (vnode3.domSize == null) { + // don't allocate for the common case + target = vnode3.dom } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i] - if (child != null) moveChildToFrag(parent, frag, child) - } + target = $doc.createDocumentFragment() + for (var dom of domFor0(vnode3)) target.appendChild(dom) } - break + insertDOM(parent, target, nextSibling) } } - function insertNode(parent, dom, nextSibling) { + function insertDOM(parent, dom, nextSibling) { if (nextSibling != null) parent.insertBefore(dom, nextSibling) else parent.appendChild(dom) } @@ -767,61 +754,51 @@ var _11 = function($window) { } } checkState(vnode3, original) + var generation // If we can, try to fast-path it and avoid all the overhead of awaiting if (!mask) { onremove(vnode3) - removeChild(parent, vnode3) + removeDOM(parent, vnode3, generation) } else { + generation = currentRender + for (var dom of domFor0(vnode3)) delayedRemoval.set(dom, generation) if (stateResult != null) { - var next = function () { + stateResult.finally(function () { // eslint-disable-next-line no-bitwise - if (mask & 1) { mask &= 2; if (!mask) reallyRemove() } - } - stateResult.then(next, next) + if (mask & 1) { + // eslint-disable-next-line no-bitwise + mask &= 2 + if (!mask) { + checkState(vnode3, original) + onremove(vnode3) + removeDOM(parent, vnode3, generation) + } + } + }) } if (attrsResult != null) { - var next = function () { + attrsResult.finally(function () { // eslint-disable-next-line no-bitwise - if (mask & 2) { mask &= 1; if (!mask) reallyRemove() } - } - attrsResult.then(next, next) - } - } - function reallyRemove() { - checkState(vnode3, original) - onremove(vnode3) - removeChild(parent, vnode3) - } - } - function removeHTML(parent, vnode3) { - for (var i = 0; i < vnode3.instance.length; i++) { - parent.removeChild(vnode3.instance[i]) - } - } - function removeChild(parent, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== "string") { - vnode3 = vnode3.instance - if (vnode3 != null) continue - } else if (vnode3.tag === "<") { - removeHTML(parent, vnode3) - } else { - if (vnode3.tag !== "[") { - parent.removeChild(vnode3.dom) - if (!Array.isArray(vnode3.children)) break - } - if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0] - if (vnode3 != null) continue - } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i] - if (child != null) removeChild(parent, child) + if (mask & 2) { + // eslint-disable-next-line no-bitwise + mask &= 1 + if (!mask) { + checkState(vnode3, original) + onremove(vnode3) + removeDOM(parent, vnode3, generation) + } } - } + }) } - break + } + } + function removeDOM(parent, vnode3, generation) { + if (vnode3.dom == null) return + if (vnode3.domSize == null) { + // don't allocate for the common case + if (delayedRemoval.get(vnode3.dom) === generation) parent.removeChild(vnode3.dom) + } else { + for (var dom of domFor0(vnode3, {generation})) parent.removeChild(dom) } } function onremove(vnode3) { @@ -968,10 +945,10 @@ var _11 = function($window) { // Styles are equivalent, do nothing. } else if (style == null) { // New style is missing, just clear it. - element.style.cssText = "" + element.style = "" } else if (typeof style !== "object") { // New style is a string, let engine deal with patching. - element.style.cssText = style + element.style = style } else if (old == null || typeof old !== "object") { // `old` is missing or a string, `style` is an object. element.style.cssText = "" @@ -1090,6 +1067,7 @@ var _11 = function($window) { var namespace = dom.namespaceURI currentDOM = dom currentRedraw = typeof redraw === "function" ? redraw : undefined + currentRender = {} try { // First time rendering into a node clears it out if (dom.vnodes == null) dom.textContent = "" @@ -1106,7 +1084,7 @@ var _11 = function($window) { } } var render = _11(typeof window !== "undefined" ? window : null) -var _14 = function(render0, schedule, console) { +var _15 = function(render0, schedule, console) { var subscriptions = [] var pending = false var offset = -1 @@ -1144,7 +1122,7 @@ var _14 = function(render0, schedule, console) { } return {mount: mount, redraw: redraw} } -var mountRedraw0 = _14(render, typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : null, typeof console !== "undefined" ? console : null) +var mountRedraw0 = _15(render, typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : null, typeof console !== "undefined" ? console : null) var buildQueryString = function(object) { if (Object.prototype.toString.call(object) !== "[object Object]") return "" var args = [] @@ -1166,10 +1144,10 @@ var buildQueryString = function(object) { else args.push(encodeURIComponent(key2) + (value1 != null && value1 !== "" ? "=" + encodeURIComponent(value1) : "")) } } -// This exists so I'm5 only saving it once. -var assign = Object.assign || function(target, source) { +// This exists so I'm4 only saving it once. +var assign = Object.assign || function(target1, source) { for (var key3 in source) { - if (hasOwn.call(source, key3)) target[key3] = source[key3] + if (hasOwn.call(source, key3)) target1[key3] = source[key3] } } // Returns `path` from `template` + `params` @@ -1185,10 +1163,10 @@ var buildPathname = function(template, params) { var path = template.slice(0, pathEnd) var query = {} assign(query, params) - var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m4, key1, variadic) { + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m3, key1, variadic) { delete query[key1] // If no such parameter exists, don't interpolate it. - if (params[key1] == null) return m4 + if (params[key1] == null) return m3 // Escape normal parameters, but not variadic ones. return variadic ? params[key1] : encodeURIComponent(String(params[key1])) }) @@ -1206,11 +1184,11 @@ var buildPathname = function(template, params) { if (newHashIndex >= 0) result0 += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex) return result0 } -var _17 = function($window, oncompletion) { +var _18 = function($window, oncompletion) { function PromiseProxy(executor) { return new Promise(executor) } - function fetch(url, args) { + function makeRequest(url, args) { return new Promise(function(resolve, reject) { url = buildPathname(url, args.params) var method = args.method != null ? args.method.toUpperCase() : "GET" @@ -1348,7 +1326,7 @@ var _17 = function($window, oncompletion) { request: function(url, args) { if (typeof url !== "string") { args = url; url = url.url } else if (args == null) args = {} - var promise = fetch(url, args) + var promise = makeRequest(url, args) if (args.background === true) return promise var count = 0 function complete() { @@ -1367,27 +1345,28 @@ var _17 = function($window, oncompletion) { promise.constructor = PromiseProxy promise.then = function() { count++ - var next0 = then.apply(promise, arguments) - next0.then(complete, function(e) { + var next = then.apply(promise, arguments) + next.then(complete, function(e) { complete() if (count === 0) throw e }) - return wrap(next0) + return wrap(next) } return promise } } } } -var request = _17(typeof window !== "undefined" ? window : null, mountRedraw0.redraw) +var request = _18(typeof window !== "undefined" ? window : null, mountRedraw0.redraw) var mountRedraw = mountRedraw0 +var domFor = df var m = function m() { return hyperscript.apply(this, arguments) } m.m = hyperscript m.trust = hyperscript.trust m.fragment = hyperscript.fragment m.Fragment = "[" m.mount = mountRedraw.mount -var m6 = hyperscript +var m5 = hyperscript function decodeURIComponentSave0(str) { try { return decodeURIComponent(str) @@ -1466,8 +1445,8 @@ var compileTemplate = function(template) { // don't also accidentally escape `-` and make it harder to detect it to // ban it from template parameters. /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, - function(m7, key6, extra) { - if (key6 == null) return "\\" + m7 + function(m6, key6, extra) { + if (key6 == null) return "\\" + m6 keys.push({k: key6, r: extra === "..."}) if (extra === "...") return "(.*)" if (extra === ".") return "([^/]+)\\." @@ -1539,7 +1518,7 @@ function decodeURIComponentSave(component) { return component } } -var _26 = function($window, mountRedraw00) { +var _28 = function($window, mountRedraw00) { var callAsync = $window == null // In case Mithril.js' loaded globally without the DOM, let's not break ? null @@ -1638,7 +1617,7 @@ var _26 = function($window, mountRedraw00) { setPath(fallbackRoute, null, {replace: true}) } } - // Set it unconditionally so `m6.route.set` and `m6.route.Link` both work, + // Set it unconditionally so `m5.route.set` and `m5.route.Link` both work, // even if neither `pushState` nor `hashchange` are supported. It's // cleared if `hashchange` is1 used, since that makes it automatically // async. @@ -1710,7 +1689,7 @@ var _26 = function($window, mountRedraw00) { // // We don't strip the other parameters because for convenience we // let them be specified in the selector as well. - var child0 = m6( + var child0 = m5( vnode5.attrs.selector || "a", censor(vnode5.attrs, ["options", "params", "selector", "onclick"]), vnode5.children @@ -1750,13 +1729,13 @@ var _26 = function($window, mountRedraw00) { // would expect. There's a lot more valid ways to click a // link than this, and one might want to not simply click a // link, but right click or command-click it to copy the - // link target, etc. Nope, this isn't just for blind people. + // link target2, etc. Nope, this isn't just for blind people. if ( // Skip if `onclick` prevented default result1 !== false && !e.defaultPrevented && // Ignore everything but left clicks (e.button === 0 || e.which === 0 || e.which === 1) && - // Let the browser handle `target=_blank`, etc. + // Let the browser handle `target2=_blank`, etc. (!e.currentTarget.target || e.currentTarget.target === "_self") && // No modifier keys !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey @@ -1775,7 +1754,7 @@ var _26 = function($window, mountRedraw00) { } return route } -m.route = _26(typeof window !== "undefined" ? window : null, mountRedraw) +m.route = _28(typeof window !== "undefined" ? window : null, mountRedraw) m.render = render m.redraw = mountRedraw.redraw m.request = request.request @@ -1785,6 +1764,7 @@ m.parsePathname = parsePathname m.buildPathname = buildPathname m.vnode = Vnode m.censor = censor +m.domFor = domFor.domFor if (typeof module !== "undefined") module["exports"] = m else window.m = m }()); \ No newline at end of file diff --git a/mithril.min.js b/mithril.min.js index 37dc8262..6446a3cf 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1 +1 @@ -!function(){"use strict";function e(e,t,n,r,o,l){return{tag:e,key:t,attrs:n,children:r,text:o,dom:l,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null==t||"boolean"==typeof t?null:"object"==typeof t?t:e("#",void 0,void 0,String(t),void 0,void 0)},e.normalizeChildren=function(t){var n=[];if(t.length){for(var r=null!=t[0]&&null!=t[0].key,o=1;o0&&(i.className=l.join(" ")),o[e]={tag:n,attrs:i}}(i),a):(a.tag=i,a)}i.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},i.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(t){var n,r=t&&t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function l(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function i(e,t){if(e.state!==t)throw new Error("'vnode.state' must not be modified.")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{i(e,t)}}function s(){try{return r.activeElement}catch(e){return null}}function u(e,t,n,r,o,l,i){for(var a=n;a'+t.children+"",i=i.firstChild):i.innerHTML=t.children,t.dom=i.firstChild,t.domSize=i.childNodes.length,t.instance=[];for(var a,s=r.createDocumentFragment();a=i.firstChild;)t.instance.push(a),s.appendChild(a);w(e,s,o)}function p(e,t,n,r,o,l){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)u(e,n,0,n.length,r,o,l);else if(null==n||0===n.length)k(e,t,0,t.length);else{var i=null!=t[0]&&null!=t[0].key,a=null!=n[0]&&null!=n[0].key,s=0,f=0;if(!i)for(;f=f&&z>=s&&(w=t[E],b=n[z],w.key===b.key);)w!==b&&h(e,w,b,r,o,l),null!=b.dom&&(o=b.dom),E--,z--;for(;E>=f&&z>=s&&(d=t[f],p=n[s],d.key===p.key);)f++,s++,d!==p&&h(e,d,p,r,y(t,f,o),l);for(;E>=f&&z>=s&&s!==z&&d.key===b.key&&w.key===p.key;)g(e,w,S=y(t,f,o)),w!==p&&h(e,w,p,r,S,l),++s<=--z&&g(e,d,o),d!==b&&h(e,d,b,r,o,l),null!=b.dom&&(o=b.dom),f++,w=t[--E],b=n[z],d=t[f],p=n[s];for(;E>=f&&z>=s&&w.key===b.key;)w!==b&&h(e,w,b,r,o,l),null!=b.dom&&(o=b.dom),z--,w=t[--E],b=n[z];if(s>z)k(e,t,f,E+1);else if(f>E)u(e,n,s,z+1,r,o,l);else{var A,j,C=o,O=z-s+1,T=new Array(O),N=0,$=0,L=2147483647,I=0;for($=0;$=s;$--){null==A&&(A=v(t,f,E+1));var R=A[(b=n[$]).key];null!=R&&(L=R>>1)+(r>>>1)+(n&r&1);e[t[a]]0&&(m[o]=t[n-1]),t[n]=o)}}n=t.length,r=t[n-1];for(;n-- >0;)t[n]=r,r=m[r];return m.length=0,t}(T)).length-1,$=z;$>=s;$--)p=n[$],-1===T[$-s]?c(e,p,r,l,o):j[N]===$-s?N--:g(e,p,o),null!=p.dom&&(o=n[$].dom);else for($=z;$>=s;$--)p=n[$],-1===T[$-s]&&c(e,p,r,l,o),null!=p.dom&&(o=n[$].dom)}}else{var P=t.lengthP&&k(e,t,s,t.length),n.length>P&&u(e,n,s,n.length,r,o,l)}}}function h(t,n,r,o,i,s){var u=n.tag;if(u===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{var n;if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate)if(void 0!==(n=a.call(e.attrs.onbeforeupdate,e,t))&&!n)break;if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate)if(void 0!==(n=a.call(e.state.onbeforeupdate,e,t))&&!n)break;return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,e.attrs=t.attrs,e.children=t.children,e.text=t.text,!0}(r,n))return;if("string"==typeof u)switch(null!=r.attrs&&D(r.attrs,r,o),u){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(S(e,t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize,n.instance=t.instance)}(t,n,r,s,i);break;case"[":!function(e,t,n,r,o,l){p(e,t.children,n.children,r,o,l);var i=0,a=n.children;if(n.dom=null,null!=a){for(var s=0;s-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var T,N=/[A-Z]/g;function $(e){return"-"+e.toLowerCase()}function L(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(N,$)}function I(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(L(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(L(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(L(r))}}function R(){this._=n}function P(e,t,r){if(null!=e.events){if(e.events._=n,e.events[t]===r)return;null==r||"function"!=typeof r&&"object"!=typeof r?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}else null==r||"function"!=typeof r&&"object"!=typeof r||(e.events=new R,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}function _(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function D(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return R.prototype=Object.create(null),R.prototype.handleEvent=function(e){var t,n=this["on"+e.type];"function"==typeof n?t=n.call(e.currentTarget,e):"function"==typeof n.handleEvent&&n.handleEvent(e),this._&&!1!==e.redraw&&(0,this._)(),!1===t&&(e.preventDefault(),e.stopPropagation())},function(t,r,o){if(!t)throw new TypeError("DOM element being rendered to does not exist.");if(null!=T&&t.contains(T))throw new TypeError("Node is currently being rendered to and thus is locked.");var l=n,i=T,a=[],u=s(),c=t.namespaceURI;T=t,n="function"==typeof o?o:void 0;try{null==t.vnodes&&(t.textContent=""),r=e.normalizeChildren(Array.isArray(r)?r:[r]),p(t,t.vnodes,r,a,null,"http://www.w3.org/1999/xhtml"===c?void 0:c),t.vnodes=r,null!=u&&s()!==u&&"function"==typeof u.focus&&u.focus();for(var f=0;f=0&&(o.splice(l,2),l<=i&&(i-=2),t(n,[])),null!=r&&(o.push(n,r),t(n,e(r),s))},redraw:s}}(a,"undefined"!=typeof requestAnimationFrame?requestAnimationFrame:null,"undefined"!=typeof console?console:null),u=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o=0&&(v+=e.slice(n,o)),f>=0&&(v+=(n<0?"?":"&")+s.slice(f,p));var m=u(a);return m&&(v+=(n<0&&f<0?"?":"&")+m),r>=0&&(v+=e.slice(r)),d>=0&&(v+=(r<0?"":"&")+s.slice(d)),v},d=function(e,t){function r(e){return new Promise(e)}function o(e,t){for(var r in e.headers)if(n.call(e.headers,r)&&r.toLowerCase()===t)return!0;return!1}return(r.prototype=Promise.prototype,r.__proto__=Promise,{request:function(l,i){"string"!=typeof l?(i=l,l=l.url):null==i&&(i={});var a=function(t,r){return new Promise((function(l,i){t=f(t,r.params);var a,s=null!=r.method?r.method.toUpperCase():"GET",u=r.body,c=(null==r.serialize||r.serialize===JSON.serialize)&&!(u instanceof e.FormData||u instanceof e.URLSearchParams),d=r.responseType||("function"==typeof r.extract?"":"json"),p=new e.XMLHttpRequest,h=!1,v=!1,m=p,y=p.abort;for(var g in p.abort=function(){h=!0,y.call(this)},p.open(s,t,!1!==r.async,"string"==typeof r.user?r.user:void 0,"string"==typeof r.password?r.password:void 0),c&&null!=u&&!o(r,"content-type")&&p.setRequestHeader("Content-Type","application/json; charset=utf-8"),"function"==typeof r.deserialize||o(r,"accept")||p.setRequestHeader("Accept","application/json, text/*"),r.withCredentials&&(p.withCredentials=r.withCredentials),r.timeout&&(p.timeout=r.timeout),p.responseType=d,r.headers)n.call(r.headers,g)&&p.setRequestHeader(g,r.headers[g]);p.onreadystatechange=function(e){if(!h&&4===e.target.readyState)try{var n,o=e.target.status>=200&&e.target.status<300||304===e.target.status||/^file:\/\//i.test(t),a=e.target.response;if("json"===d){if(!e.target.responseType&&"function"!=typeof r.extract)try{a=JSON.parse(e.target.responseText)}catch(e){a=null}}else d&&"text"!==d||null==a&&(a=e.target.responseText);if("function"==typeof r.extract?(a=r.extract(e.target,r),o=!0):"function"==typeof r.deserialize&&(a=r.deserialize(a)),o){if("function"==typeof r.type)if(Array.isArray(a))for(var s=0;s-1&&s.pop();for(var c=0;c0&&(i.className=l.join(" ")),o[e]={tag:n,attrs:i}}function a(e,t){var r=t.attrs,o=n.call(r,"class"),i=o?r.class:r.className;if(t.tag=e.tag,t.attrs={},!l(e.attrs)&&!l(r)){var a={};for(var u in r)n.call(r,u)&&(a[u]=r[u]);r=a}for(var u in e.attrs)n.call(e.attrs,u)&&"className"!==u&&!n.call(r,u)&&(r[u]=e.attrs[u]);for(var u in null==i&&null==e.attrs.className||(r.className=null!=i?null!=e.attrs.className?String(e.attrs.className)+" "+String(i):i:null!=e.attrs.className?e.attrs.className:null),o&&(r.class=null),r)if(n.call(r,u)&&"key"!==u){t.attrs=r;break}return t}function u(n){if(null==n||"string"!=typeof n&&"function"!=typeof n&&"function"!=typeof n.view)throw Error("The selector must be either a string or a component.");var r=t.apply(1,arguments);return"string"==typeof n&&(r.children=e.normalizeChildren(r.children),"["!==n)?a(o[n]||i(n),r):(r.tag=n,r)}u.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},u.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var s=new WeakMap;var f={delayedRemoval:s,domFor:function*({dom:e,domSize0:t},{generation0:n}={}){if(null!=e)do{const{nextSibling:r}=e;s.get(e)===n&&(yield e,t--),e=r}while(t)}},c=f.delayedRemoval,d=f.domFor,p=function(t){var n,r,o=t&&t.document,l={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||l[e.tag]}function a(e,t){if(e.state!==t)throw new Error("'vnode.state' must not be modified.")}function u(e){var t=e.state;try{return this.apply(t,arguments)}finally{a(e,t)}}function s(){try{return o.activeElement}catch(e){return null}}function f(e,t,n,r,o,l,i){for(var a=n;a'+t.children+"",i=i.firstChild):i.innerHTML=t.children,t.dom=i.firstChild,t.domSize=i.childNodes.length;for(var a,u=o.createDocumentFragment();a=i.firstChild;)u.appendChild(a);k(e,u,r)}function h(e,t,n,r,o,l){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,l);else if(null==n||0===n.length)E(e,t,0,t.length);else{var i=null!=t[0]&&null!=t[0].key,a=null!=n[0]&&null!=n[0].key,u=0,s=0;if(!i)for(;s=s&&S>=u&&(m=t[k],v=n[S],m.key===v.key);)m!==v&&y(e,m,v,r,o,l),null!=v.dom&&(o=v.dom),k--,S--;for(;k>=s&&S>=u&&(c=t[s],d=n[u],c.key===d.key);)s++,u++,c!==d&&y(e,c,d,r,b(t,s,o),l);for(;k>=s&&S>=u&&u!==S&&c.key===v.key&&m.key===d.key;)x(e,m,h=b(t,s,o)),m!==d&&y(e,m,d,r,h,l),++u<=--S&&x(e,c,o),c!==v&&y(e,c,v,r,o,l),null!=v.dom&&(o=v.dom),s++,m=t[--k],v=n[S],c=t[s],d=n[u];for(;k>=s&&S>=u&&m.key===v.key;)m!==v&&y(e,m,v,r,o,l),null!=v.dom&&(o=v.dom),S--,m=t[--k],v=n[S];if(u>S)E(e,t,s,k+1);else if(s>k)f(e,n,u,S+1,r,o,l);else{var j,A,C=o,O=S-u+1,T=new Array(O),N=0,$=0,L=2147483647,R=0;for($=0;$=u;$--){null==j&&(j=g(t,s,k+1));var I=j[(v=n[$]).key];null!=I&&(L=I>>1)+(r>>>1)+(n&r&1);e[t[a]]0&&(w[o]=t[n-1]),t[n]=o)}}n=t.length,r=t[n-1];for(;n-- >0;)t[n]=r,r=w[r];return w.length=0,t}(T)).length-1,$=S;$>=u;$--)d=n[$],-1===T[$-u]?p(e,d,r,l,o):A[N]===$-u?N--:x(e,d,o),null!=d.dom&&(o=n[$].dom);else for($=S;$>=u;$--)d=n[$],-1===T[$-u]&&p(e,d,r,l,o),null!=d.dom&&(o=n[$].dom)}}else{var P=t.lengthP&&E(e,t,u,t.length),n.length>P&&f(e,n,u,n.length,r,o,l)}}}function y(t,n,r,o,l,a){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{var n;if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate)if(void 0!==(n=u.call(e.attrs.onbeforeupdate,e,t))&&!n)break;if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate)if(void 0!==(n=u.call(e.state.onbeforeupdate,e,t))&&!n)break;return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,e.attrs=t.attrs,e.children=t.children,e.text=t.text,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&M(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(j(e,t,void 0),v(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,a,l);break;case"[":!function(e,t,n,r,o,l){h(e,t.children,n.children,r,o,l);var i=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var $,L=/[A-Z]/g;function R(e){return"-"+e.toLowerCase()}function I(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(L,R)}function P(e,t,n){if(t===n);else if(null==n)e.style="";else if("object"!=typeof n)e.style=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(I(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(I(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(I(r))}}function F(){this._=n}function _(e,t,r){if(null!=e.events){if(e.events._=n,e.events[t]===r)return;null==r||"function"!=typeof r&&"object"!=typeof r?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}else null==r||"function"!=typeof r&&"object"!=typeof r||(e.events=new F,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}function D(e,t,n){"function"==typeof e.oninit&&u.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(u.bind(e.oncreate,t))}function M(e,t,n){"function"==typeof e.onupdate&&n.push(u.bind(e.onupdate,t))}return F.prototype=Object.create(null),F.prototype.handleEvent=function(e){var t,n=this["on"+e.type];"function"==typeof n?t=n.call(e.currentTarget,e):"function"==typeof n.handleEvent&&n.handleEvent(e),this._&&!1!==e.redraw&&(0,this._)(),!1===t&&(e.preventDefault(),e.stopPropagation())},function(t,o,l){if(!t)throw new TypeError("DOM element being rendered to does not exist.");if(null!=$&&t.contains($))throw new TypeError("Node is currently being rendered to and thus is locked.");var i=n,a=$,u=[],f=s(),c=t.namespaceURI;$=t,n="function"==typeof l?l:void 0,r={};try{null==t.vnodes&&(t.textContent=""),o=e.normalizeChildren(Array.isArray(o)?o:[o]),h(t,t.vnodes,o,u,null,"http://www.w3.org/1999/xhtml"===c?void 0:c),t.vnodes=o,null!=f&&s()!==f&&"function"==typeof f.focus&&f.focus();for(var d=0;d=0&&(o.splice(l,2),l<=i&&(i-=2),t(n,[])),null!=r&&(o.push(n,r),t(n,e(r),u))},redraw:u}}(p,"undefined"!=typeof requestAnimationFrame?requestAnimationFrame:null,"undefined"!=typeof console?console:null),v=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o=0&&(p+=e.slice(n,o)),s>=0&&(p+=(n<0?"?":"&")+u.slice(s,c));var m=v(a);return m&&(p+=(n<0&&s<0?"?":"&")+m),r>=0&&(p+=e.slice(r)),f>=0&&(p+=(r<0?"":"&")+u.slice(f)),p},g=function(e,t){function r(e){return new Promise(e)}function o(e,t){for(var r in e.headers)if(n.call(e.headers,r)&&r.toLowerCase()===t)return!0;return!1}return r.prototype=Promise.prototype,r.__proto__=Promise,{request:function(l,i){"string"!=typeof l?(i=l,l=l.url):null==i&&(i={});var a=function(t,r){return new Promise((function(l,i){t=y(t,r.params);var a,u=null!=r.method?r.method.toUpperCase():"GET",s=r.body,f=(null==r.serialize||r.serialize===JSON.serialize)&&!(s instanceof e.FormData||s instanceof e.URLSearchParams),c=r.responseType||("function"==typeof r.extract?"":"json"),d=new e.XMLHttpRequest,p=!1,m=!1,v=d,h=d.abort;for(var g in d.abort=function(){p=!0,h.call(this)},d.open(u,t,!1!==r.async,"string"==typeof r.user?r.user:void 0,"string"==typeof r.password?r.password:void 0),f&&null!=s&&!o(r,"content-type")&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),"function"==typeof r.deserialize||o(r,"accept")||d.setRequestHeader("Accept","application/json, text/*"),r.withCredentials&&(d.withCredentials=r.withCredentials),r.timeout&&(d.timeout=r.timeout),d.responseType=c,r.headers)n.call(r.headers,g)&&d.setRequestHeader(g,r.headers[g]);d.onreadystatechange=function(e){if(!p&&4===e.target.readyState)try{var n,o=e.target.status>=200&&e.target.status<300||304===e.target.status||/^file:\/\//i.test(t),a=e.target.response;if("json"===c){if(!e.target.responseType&&"function"!=typeof r.extract)try{a=JSON.parse(e.target.responseText)}catch(e){a=null}}else c&&"text"!==c||null==a&&(a=e.target.responseText);if("function"==typeof r.extract?(a=r.extract(e.target,r),o=!0):"function"==typeof r.deserialize&&(a=r.deserialize(a)),o){if("function"==typeof r.type)if(Array.isArray(a))for(var u=0;u-1&&u.pop();for(var f=0;f-1&&(t=n[a][1](t,r()))})),t}),r);return a(t),a},t["fantasy-land/of"]=t;var n=!1;function t(n){var r,i=[],u=[];function c(e){return arguments.length&&e!==t.SKIP&&(n=e,a(c)&&(c._changing(),c._state="active",i.slice().forEach((function(t,e){a(t)&&t(this[e](n))}),u.slice()))),n}return c.constructor=t,c._state=arguments.length&&n!==t.SKIP?"active":"pending",c._parents=[],c._changing=function(){a(c)&&(c._state="changing"),i.forEach((function(n){n._changing()}))},c._map=function(e,r){var a=r?t():t(e(n));return a._parents.push(c),i.push(a),u.push(e),a},c.map=function(n){return c._map(n,"active"!==c._state)},c.toJSON=function(){return null!=n&&"function"==typeof n.toJSON?n.toJSON():n},c["fantasy-land/map"]=c.map,c["fantasy-land/ap"]=function(n){return e((function(n,t){return n()(t())}),[n,c])},c._unregisterChild=function(n){var t=i.indexOf(n);-1!==t&&(i.splice(t,1),u.splice(t,1))},Object.defineProperty(c,"end",{get:function(){return r||((r=t()).map((function(n){return!0===n&&(c._parents.forEach((function(n){n._unregisterChild(c)})),c._state="ended",c._parents.length=i.length=u.length=0),n})),r)}}),c}function e(n,e){var r=e.every((function(n){if(n.constructor!==t)throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream.");return"active"===n._state})),a=r?t(n.apply(null,e.concat([e]))):t(),i=[],u=e.map((function(t){return t._map((function(u){return i.push(t),(r||e.every((function(n){return"pending"!==n._state})))&&(r=!0,a(n.apply(null,e.concat([i]))),i=[]),u}),!0)})),c=a.end.map((function(n){!0===n&&(u.forEach((function(n){n.end(!0)})),c.end(!0))}));return a}function r(n){return e((function(){return n.map((function(n){return n()}))}),n)}function a(n){return"pending"===n._state||"active"===n._state||"changing"===n._state}Object.defineProperty(t,"HALT",{get:function(){return n||console.log("HALT is deprecated and has been renamed to SKIP"),n=!0,t.SKIP}}),"undefined"!=typeof module?module.exports=t:"function"!=typeof window.m||"stream"in window.m?window.m={stream:t}:window.m.stream=t}(); \ No newline at end of file +!function(){"use strict";t.SKIP={},t.lift=function(){var n=arguments[0],t=Array.prototype.slice.call(arguments,1);return r(t).map((function(t){return n.apply(void 0,t)}))},t.scan=function(n,e,r){var a=r.map((function(r){var a=n(e,r);return a!==t.SKIP&&(e=a),a}));return a(e),a},t.merge=r,t.combine=e,t.scanMerge=function(n,t){var r=n.map((function(n){return n[0]})),a=e((function(){var e=arguments[arguments.length-1];return r.forEach((function(r,a){e.indexOf(r)>-1&&(t=n[a][1](t,r()))})),t}),r);return a(t),a},t["fantasy-land/of"]=t;var n=!1;function t(n){var r,i=[],u=[];function c(e){return arguments.length&&e!==t.SKIP&&(n=e,a(c)&&(c._changing(),c._state="active",i.slice().forEach((function(t,e){a(t)&&t(this[e](n))}),u.slice()))),n}function o(){return(r=t()).map((function(n){return!0===n&&(c._parents.forEach((function(n){n._unregisterChild(c)})),c._state="ended",c._parents.length=i.length=u.length=0),n})),r}return c.constructor=t,c._state=arguments.length&&n!==t.SKIP?"active":"pending",c._parents=[],c._changing=function(){a(c)&&(c._state="changing"),i.forEach((function(n){n._changing()}))},c._map=function(e,r){var a=r?t():t(e(n));return a._parents.push(c),i.push(a),u.push(e),a},c.map=function(n){return c._map(n,"active"!==c._state)},c.toJSON=function(){return null!=n&&"function"==typeof n.toJSON?n.toJSON():n},c["fantasy-land/map"]=c.map,c["fantasy-land/ap"]=function(n){return e((function(n,t){return n()(t())}),[n,c])},c._unregisterChild=function(n){var t=i.indexOf(n);-1!==t&&(i.splice(t,1),u.splice(t,1))},Object.defineProperty(c,"end",{get:function(){return r||o()}}),c}function e(n,e){var r=e.every((function(n){if(n.constructor!==t)throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream.");return"active"===n._state})),a=r?t(n.apply(null,e.concat([e]))):t(),i=[],u=e.map((function(t){return t._map((function(u){return i.push(t),(r||e.every((function(n){return"pending"!==n._state})))&&(r=!0,a(n.apply(null,e.concat([i]))),i=[]),u}),!0)})),c=a.end.map((function(n){!0===n&&(u.forEach((function(n){n.end(!0)})),c.end(!0))}));return a}function r(n){return e((function(){return n.map((function(n){return n()}))}),n)}function a(n){return"pending"===n._state||"active"===n._state||"changing"===n._state}Object.defineProperty(t,"HALT",{get:function(){return n||console.log("HALT is deprecated and has been renamed to SKIP"),n=!0,t.SKIP}}),"undefined"!=typeof module?module.exports=t:"function"!=typeof window.m||"stream"in window.m?window.m={stream:t}:window.m.stream=t}(); \ No newline at end of file From 6c1c5cf1064105be6890e0f0d61e93b347c63e3a Mon Sep 17 00:00:00 2001 From: Keita Furui Date: Wed, 15 Feb 2023 11:11:18 +0900 Subject: [PATCH 02/68] hyperscript: handles shared empty attrs, fixes #2821 --- render/hyperscript.js | 2 +- render/tests/test-hyperscript.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/render/hyperscript.js b/render/hyperscript.js index 26ff753e..0015b26e 100644 --- a/render/hyperscript.js +++ b/render/hyperscript.js @@ -38,7 +38,7 @@ function execSelector(state, vnode) { vnode.tag = state.tag vnode.attrs = {} - if (!isEmpty(state.attrs) && !isEmpty(attrs)) { + if (!isEmpty(state.attrs)) { var newAttrs = {} for (var key in attrs) { diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index 59751f44..fcf69d25 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -580,6 +580,15 @@ o.spec("hyperscript", function() { o(nodeB.attrs.className).equals("b") o(nodeB.attrs.a).equals("b") }) + o("handles shared empty attrs (#2821)", function() { + var attrs = {} + + var nodeA = m(".a", attrs) + var nodeB = m(".b", attrs) + + o(nodeA.attrs.className).equals("a") + o(nodeB.attrs.className).equals("b") + }) o("doesnt modify passed attributes object", function() { var attrs = {a: "b"} m(".a", attrs) From a28e91e15bb1dc93f98ac916f145d61dc9ce4f13 Mon Sep 17 00:00:00 2001 From: Matias Kinnunen Date: Sun, 12 Mar 2023 02:39:25 +0200 Subject: [PATCH 03/68] Fix typos in `stream()` docs (#2825) Co-authored-by: Claudia Meadows --- docs/stream.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/stream.md b/docs/stream.md index da1eac40..8e39af84 100644 --- a/docs/stream.md +++ b/docs/stream.md @@ -462,7 +462,7 @@ Pending streams can be created by calling `stream()` with no parameters. var pending = stream() ``` -If a stream is dependent on more than one stream, any of its parent streams is in a pending state, the dependent streams is also in a pending state, and does not update its value. +If a stream is dependent on more than one stream and any of its parent streams is in a pending state, the dependent stream is also in a pending state, and does not update its value. ```javascript var a = stream(5) @@ -558,13 +558,13 @@ console.log(serialized) // logs 123 Unlike libraries like Knockout, Mithril.js streams do not trigger re-rendering of templates. Redrawing happens in response to event handlers defined in Mithril.js component views, route changes, or after [`m.request`](request.md) calls resolve. -If redrawing is desired in response to other asynchronous events (e.g. `setTimeout`/`setInterval`, websocket subscription, 3rd party library event handler, etc), you should manually call [`m.redraw()`](redraw.md) +If redrawing is desired in response to other asynchronous events (e.g. `setTimeout`/`setInterval`, websocket subscription, 3rd party library event handler, etc.), you should manually call [`m.redraw()`](redraw.md). --- ### What is Fantasy Land -[Fantasy Land](https://github.com/fantasyland/fantasy-land) specifies interoperability of common algebraic structures. In plain english, that means that libraries that conform to Fantasy Land specs can be used to write generic functional style code that works regardless of how these libraries implement the constructs. +[Fantasy Land](https://github.com/fantasyland/fantasy-land) specifies interoperability of common algebraic structures. In plain English, that means that libraries that conform to Fantasy Land specs can be used to write generic functional style code that works regardless of how these libraries implement the constructs. For example, say we want to create a generic function called `plusOne`. The naive implementation would look like this: @@ -574,7 +574,7 @@ function plusOne(a) { } ``` -The problem with this implementation is that it can only be used with a number. However it's possible that whatever logic produces a value for `a` could also produce an error state (wrapped in a Maybe or an Either from a library like [Sanctuary](https://github.com/sanctuary-js/sanctuary) or [Ramda-Fantasy](https://github.com/ramda/ramda-fantasy)), or it could be a Mithril.js stream, or a [flyd](https://github.com/paldepind/flyd) stream, etc. Ideally, we wouldn't want to write a similar version of the same function for every possible type that `a` could have and we wouldn't want to be writing wrapping/unwrapping/error handling code repeatedly. +The problem with this implementation is that it can only be used with a number. However it's possible that whatever logic produces a value for `a` could also produce an error state (wrapped in a Maybe or an Either from a library like [Sanctuary](https://github.com/sanctuary-js/sanctuary) or [Ramda-Fantasy](https://github.com/ramda/ramda-fantasy)), or it could be a Mithril.js stream, a [Flyd](https://github.com/paldepind/flyd) stream, etc. Ideally, we wouldn't want to write a similar version of the same function for every possible type that `a` could have and we wouldn't want to be writing wrapping/unwrapping/error handling code repeatedly. This is where Fantasy Land can help. Let's rewrite that function in terms of a Fantasy Land algebra: From a621d9d41126b1e6839b7bff0feab9f11c30c3fd Mon Sep 17 00:00:00 2001 From: tbreuss Date: Wed, 3 May 2023 17:12:19 +0200 Subject: [PATCH 04/68] docs: removed question mark and fixed not working anchor link (github/npm) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f452c17..b1f28a39 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs)   [![Zulip, join chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://mithril.zulipchat.com/) -- [What is Mithril.js?](#what-is-mithriljs?) +- [What is Mithril.js?](#what-is-mithriljs) - [Installation](#installation) - [Documentation](#documentation) - [Getting Help](#getting-help) From 811c216cb40d2c66610b20430a772d502a040599 Mon Sep 17 00:00:00 2001 From: tbreuss Date: Wed, 3 May 2023 16:56:13 +0200 Subject: [PATCH 05/68] docs: fix regex for parsing page title --- scripts/generate-docs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-docs.js b/scripts/generate-docs.js index c6e1584a..c47de617 100644 --- a/scripts/generate-docs.js +++ b/scripts/generate-docs.js @@ -161,7 +161,7 @@ class Generator { ) const markedHtml = marked(body) - const title = body.match(/^#([^\n\r]+)/i) || [] + const title = body.match(/^#\s+([^\n\r]+)/m) || [] let result = this._layout if (title[1]) { From 9ba52b3a054b2d946ae9e765e23c917cc5c94460 Mon Sep 17 00:00:00 2001 From: Claudia Meadows Date: Wed, 17 May 2023 13:08:08 -0700 Subject: [PATCH 06/68] Add missing `m.censor` to API navigation Not sure how I forgot about this when I added the method. --- docs/nav-methods.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/nav-methods.md b/docs/nav-methods.md index 6d15f06e..1384730d 100644 --- a/docs/nav-methods.md +++ b/docs/nav-methods.md @@ -11,6 +11,7 @@ - [m.trust](trust.md) - [m.fragment](fragment.md) - [m.redraw](redraw.md) + - [m.censor](censor.md) - Optional - [Stream](stream.md) - Tooling From 7d7d645046c54fc7c415b52ff2b36857b1609182 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 04:55:33 +0000 Subject: [PATCH 07/68] Bump yaml and lint-staged Bumps [yaml](https://github.com/eemeli/yaml) to 2.2.2 and updates ancestor dependency [lint-staged](https://github.com/okonet/lint-staged). These dependencies need to be updated together. Updates `yaml` from 1.10.2 to 2.2.2 - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/compare/v1.10.2...v2.2.2) Updates `lint-staged` from 12.3.4 to 13.2.1 - [Release notes](https://github.com/okonet/lint-staged/releases) - [Commits](https://github.com/okonet/lint-staged/compare/v12.3.4...v13.2.1) --- updated-dependencies: - dependency-name: yaml dependency-type: indirect - dependency-name: lint-staged dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 1297 +++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 483 insertions(+), 816 deletions(-) diff --git a/package-lock.json b/package-lock.json index 362324a4..7c2a16e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "glob": "^7.1.4", "html-minifier": "^4.0.0", "istanbul": "^0.4.5", - "lint-staged": "^12.3.4", + "lint-staged": "^13.2.1", "locater": "^1.3.0", "marked": "^4.0.10", "minimist": "^1.2.0", @@ -151,19 +151,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -191,25 +178,27 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "type-fest": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -299,15 +288,6 @@ "node": ">=0.8" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -481,25 +461,20 @@ "node": ">= 4.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, + "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { @@ -507,6 +482,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^5.0.0" @@ -534,10 +510,11 @@ "dev": true }, "node_modules/colorette": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", - "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", - "dev": true + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -607,10 +584,11 @@ } }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -666,7 +644,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ecc-jsbn": { "version": "0.1.2", @@ -688,7 +667,8 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.2", @@ -1192,24 +1172,32 @@ "node": ">=0.10.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -1220,6 +1208,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1234,6 +1223,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1243,6 +1233,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1255,6 +1246,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1264,6 +1256,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -1461,6 +1454,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1704,12 +1698,13 @@ } }, "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10.17.0" + "node": ">=14.18.0" } }, "node_modules/humanize-url": { @@ -1759,15 +1754,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1834,6 +1820,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1884,12 +1871,13 @@ } }, "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2094,82 +2082,95 @@ } }, "node_modules/lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/lint-staged": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.4.tgz", - "integrity": "sha512-yv/iK4WwZ7/v0GtVkNb3R82pdL9M+ScpIbJLJNyCXkJ1FGaXvRCOg/SeL59SZtPpqZhE7BD6kPKFLIDUhDx2/w==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", + "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", "dev": true, + "license": "MIT", "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.16", - "commander": "^8.3.0", - "debug": "^4.3.3", - "execa": "^5.1.1", - "lilconfig": "2.0.4", - "listr2": "^4.0.1", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.0", - "string-argv": "^0.3.1", - "supports-color": "^9.2.1", - "yaml": "^1.10.2" + "chalk": "5.3.0", + "commander": "11.0.0", + "debug": "4.3.4", + "execa": "7.2.0", + "lilconfig": "2.1.0", + "listr2": "6.6.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 12" - } - }, - "node_modules/lint-staged/node_modules/supports-color": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", - "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/lint-staged/node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/listr2": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.4.tgz", - "integrity": "sha512-vJOm5KD6uZXjSsrwajr+mNacIjf87gWvlBEltPWLbTkslUscWAzquyK4xfe9Zd4RDgO5nnwFyV06FC+uVR+5mg==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", + "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", "dev": true, + "license": "MIT", "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", "rfdc": "^1.3.0", - "rxjs": "^7.5.4", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^8.1.0" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" @@ -2180,98 +2181,6 @@ } } }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/listr2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -2315,114 +2224,52 @@ "dev": true }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/log-update/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-update/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/lower-case": { @@ -2456,16 +2303,18 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -2493,12 +2342,16 @@ } }, "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { @@ -2656,24 +2509,32 @@ } }, "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/oauth-sign": { @@ -2722,15 +2583,16 @@ } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2767,21 +2629,6 @@ "ospec": "bin/ospec" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -3116,23 +2963,54 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", @@ -3149,15 +3027,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rxjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz", - "integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -3210,13 +3079,15 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -3229,10 +3100,11 @@ } }, "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3353,19 +3225,21 @@ } }, "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } }, "node_modules/string-width": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.0.tgz", - "integrity": "sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3379,10 +3253,11 @@ } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3391,10 +3266,11 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -3467,12 +3343,16 @@ } }, "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-json-comments": { @@ -3552,12 +3432,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3610,12 +3484,6 @@ "node": ">=0.8.0" } }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3766,82 +3634,63 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "node": ">=12" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -3851,12 +3700,13 @@ "dev": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true, + "license": "ISC", "engines": { - "node": ">= 6" + "node": ">= 14" } } }, @@ -3949,16 +3799,6 @@ "dev": true, "requires": {} }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3979,18 +3819,18 @@ "optional": true }, "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "dev": true, "requires": { - "type-fest": "^0.21.3" + "type-fest": "^1.0.2" }, "dependencies": { "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true } } @@ -4059,12 +3899,6 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -4214,19 +4048,13 @@ "source-map": "~0.6.0" } }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" } }, "cli-truncate": { @@ -4255,9 +4083,9 @@ "dev": true }, "colorette": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", - "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "combined-stream": { @@ -4318,9 +4146,9 @@ } }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -4749,21 +4577,27 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", "dev": true, "requires": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "dependencies": { "cross-spawn": { @@ -5135,9 +4969,9 @@ } }, "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", "dev": true }, "humanize-url": { @@ -5172,12 +5006,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5264,9 +5092,9 @@ } }, "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true }, "is-symbol": { @@ -5438,130 +5266,61 @@ } }, "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true }, "lint-staged": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.4.tgz", - "integrity": "sha512-yv/iK4WwZ7/v0GtVkNb3R82pdL9M+ScpIbJLJNyCXkJ1FGaXvRCOg/SeL59SZtPpqZhE7BD6kPKFLIDUhDx2/w==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", + "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", "dev": true, "requires": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.16", - "commander": "^8.3.0", - "debug": "^4.3.3", - "execa": "^5.1.1", - "lilconfig": "2.0.4", - "listr2": "^4.0.1", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.0", - "string-argv": "^0.3.1", - "supports-color": "^9.2.1", - "yaml": "^1.10.2" + "chalk": "5.3.0", + "commander": "11.0.0", + "debug": "4.3.4", + "execa": "7.2.0", + "lilconfig": "2.1.0", + "listr2": "6.6.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.1" }, "dependencies": { - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "supports-color": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", - "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", + "commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "dev": true + }, + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true } } }, "listr2": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.4.tgz", - "integrity": "sha512-vJOm5KD6uZXjSsrwajr+mNacIjf87gWvlBEltPWLbTkslUscWAzquyK4xfe9Zd4RDgO5nnwFyV06FC+uVR+5mg==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", + "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", "dev": true, "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", "rfdc": "^1.3.0", - "rxjs": "^7.5.4", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } + "wrap-ansi": "^8.1.0" } }, "load-json-file": { @@ -5603,84 +5362,31 @@ "dev": true }, "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", "dev": true, "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^6.0.1" } } } @@ -5710,13 +5416,13 @@ "dev": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime-db": { @@ -5735,9 +5441,9 @@ } }, "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true }, "minimatch": { @@ -5871,18 +5577,18 @@ } }, "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "^4.0.0" }, "dependencies": { "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true } } @@ -5921,12 +5627,12 @@ } }, "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^4.0.0" } }, "optionator": { @@ -5952,15 +5658,6 @@ "glob": "^7.1.3" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -6208,19 +5905,36 @@ "dev": true }, "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + } } }, "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "rimraf": { @@ -6232,15 +5946,6 @@ "glob": "^7.1.3" } }, - "rxjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz", - "integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -6297,9 +6002,9 @@ }, "dependencies": { "ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true } } @@ -6397,15 +6102,15 @@ "dev": true }, "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true }, "string-width": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.0.tgz", - "integrity": "sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "requires": { "eastasianwidth": "^0.2.0", @@ -6414,15 +6119,15 @@ }, "dependencies": { "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { "ansi-regex": "^6.0.1" @@ -6477,9 +6182,9 @@ "dev": true }, "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true }, "strip-json-comments": { @@ -6537,12 +6242,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6587,12 +6286,6 @@ } } }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6709,61 +6402,35 @@ "dev": true }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^6.0.1" } } } @@ -6775,9 +6442,9 @@ "dev": true }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true } } diff --git a/package.json b/package.json index bf369024..02fac2f6 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "glob": "^7.1.4", "html-minifier": "^4.0.0", "istanbul": "^0.4.5", - "lint-staged": "^12.3.4", + "lint-staged": "^13.2.1", "locater": "^1.3.0", "marked": "^4.0.10", "minimist": "^1.2.0", From f1fcaa604114e853a3a69cbf766d1f43d32146f2 Mon Sep 17 00:00:00 2001 From: tbreuss Date: Fri, 19 May 2023 11:45:56 +0200 Subject: [PATCH 08/68] fix strange behavior, bump marked.js version up --- examples/editor/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/editor/index.html b/examples/editor/index.html index 32e2da0b..dea4874f 100644 --- a/examples/editor/index.html +++ b/examples/editor/index.html @@ -12,7 +12,7 @@ h1,h2,h3,h4,h5,h6,p {margin:0 0 10px;}
- + - - -``` - -To make things simpler you can try out Mithril.js right here. This is a live playground with Mithril.js preloaded that - by the way - is also built in Mithril. - -```js -var root = document.body - -// Your code here - -m.mount(root, { - view: function() { - return m("h1", "Try me out") - } -}) -``` -> *[Click here to open the sample on flems.io](https://flems.io/mithril@next#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4s+LLQCuDABQATWtSk4G+AEa15ATwoACYAB00e03oBuEGAHdEesDOrEI9WQEoDxs970AnGMRSviZYsgDkhACMYfphACq+2no4etLEYW5eZqzGrG6UIHAwsE4uaAg8ACyIAExsHCCYOHj41HACNPSMzDxsALqsQA)* - ---- - -### Hello world - -Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better) - -```javascript -var root = document.body - -m.render(root, "Hello world") -``` - -Now, let's change the text to something else. Add this line of code under the previous one: - -```javascript -m.render(root, "My first app") -``` - -As you can see, you use the same code to both create and update HTML. Mithril.js automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch. - -#### Live Example - -```js -var root = document.body - -m.render(root, "Hello World") -``` - ---- - -### DOM elements - -Let's wrap our text in an `

` tag. - -```javascript -m.render(root, m("h1", "My first app")) -``` - -The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `

`: - -```javascript -m("h1", {class: "title"}, "My first app") -``` - -If you want to have multiple elements: - -```javascript -[ - m("h1", {class: "title"}, "My first app"), - m("button", "A button"), -] -``` - -And so on: - -```javascript -m("main", [ - m("h1", {class: "title"}, "My first app"), - m("button", "A button"), -]) -``` - -#### Live Example - -```js -var root = document.body - -m.render(root, [ - m("main", [ - m("h1", {class: "title"}, "My first app"), - m("button", "A button"), - ]) -]) -``` - -Note: If you prefer `` syntax, [it's possible to use it via a Babel plugin](jsx.md). - -```jsx -// HTML syntax via Babel's JSX plugin -
-

My first app

- -
-``` - ---- - -### Components - -A Mithril.js component is just an object with a `view` function. Here's the code above as a component: - -```javascript -var Hello = { - view: function() { - return m("main", [ - m("h1", {class: "title"}, "My first app"), - m("button", "A button"), - ]) - } -} -``` - -To activate the component, we use `m.mount`. - -```javascript -m.mount(root, Hello) -``` - -As you would expect, doing so creates this markup: - -```html -
-

My first app

- -
-``` - -The `m.mount` function is similar to `m.render`, but instead of rendering some HTML only once, it activates Mithril.js' auto-redrawing system. To understand what that means, let's add some events: - -```javascript -var count = 0 // added a variable - -var Hello = { - view: function() { - return m("main", [ - m("h1", {class: "title"}, "My first app"), - // changed the next line - m("button", {onclick: function() {count++}}, count + " clicks"), - ]) - } -} - -m.mount(root, Hello) -``` - -We defined an `onclick` event on the button, which increments a variable `count` (which was declared at the top). We are now also rendering the value of that variable in the button label. - -You can now update the label of the button by clicking the button. Since we used `m.mount`, you don't need to manually call `m.render` to apply the changes in the `count` variable to the HTML; Mithril.js does it for you. - -If you're wondering about performance, it turns out Mithril.js is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril.js actually updates. - -#### Live Example - -```js -var root = document.body -var count = 0 // added a variable - -var Hello = { - view: function() { - return m("main", [ - m("h1", { - class: "title" - }, "My first app"), - m("button", { - onclick: function() {count++} - }, count + " clicks") - ]) - } -} - -m.mount(root, Hello) -``` - ---- - -### Routing - -Routing just means going from one screen to another in an application with several screens. - -Let's add a splash page that appears before our click counter. First we create a component for it: - -```javascript -var Splash = { - view: function() { - return m("a", {href: "#!/hello"}, "Enter!") - } -} -``` - -As you can see, this component simply renders a link to `#!/hello`. The `#!` part is known as a hashbang, and it's a common convention used in Single Page Applications to indicate that the stuff after it (the `/hello` part) is a route path. - -Now that we're going to have more than one screen, we use `m.route` instead of `m.mount`. - -```javascript -m.route(root, "/splash", { - "/splash": Splash, - "/hello": Hello, -}) -``` - -The `m.route` function still has the same auto-redrawing functionality that `m.mount` does, and it also enables URL awareness; in other words, it lets Mithril.js know what to do when it sees a `#!` in the URL. - -The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril.js redirects to the default route. So if you open the page in a browser and your URL is `https://localhost`, then you get redirected to `https://localhost/#!/splash`. - -Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `https://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button. - -#### Live Example - -```js -var root = document.body -var count = 0 - -var Hello = { - view: function() { - return m("main", [ - m("h1", { - class: "title" - }, "My first app"), - m("button", { - onclick: function() {count++} - }, count + " clicks"), - ]) - } -} - -var Splash = { - view: function() { - return m("a", { - href: "#!/hello" - }, "Enter!") - } -} - -m.route(root, "/splash", { - "/splash": Splash, - "/hello": Hello, -}) -``` - ---- - -### XHR - -Basically, XHR is just a way to talk to a server. - -Let's change our click counter to make it save data on a server. For the server, we'll use [REM](https://mithril-rem.fly.dev), a mock REST API designed for toy apps like this tutorial. - -First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `body` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work) - -```javascript -var count = 0 -var increment = function() { - m.request({ - method: "PUT", - url: "//mithril-rem.fly.dev/api/tutorial/1", - body: {count: count + 1}, - withCredentials: true, - }) - .then(function(data) { - count = parseInt(data.count) - }) -} -``` - -Calling the increment function [upserts](https://en.wiktionary.org/wiki/upsert) an object `{count: 1}` to the `/api/tutorial/1` endpoint. This endpoint returns an object with the same `count` value that was sent to it. Notice that the `count` variable is only updated after the request completes, and it's updated with the response value from the server now. - -Let's replace the event handler in the component to call the `increment` function instead of incrementing the `count` variable directly: - -```javascript -var Hello = { - view: function() { - return m("main", [ - m("h1", {class: "title"}, "My first app"), - m("button", {onclick: increment}, count + " clicks"), - ]) - } -} -``` - -Clicking the button should now update the count. - -#### Live Example - -```js -var root = document.body -var count = 0 - -var increment = function() { - m.request({ - method: "PUT", - url: "//mithril-rem.fly.dev/api/tutorial/1", - body: {count: count + 1}, - withCredentials: true, - }) - .then(function(data) { - count = parseInt(data.count) - }) -} - -var Hello = { - view: function() { - return m("main", [ - m("h1", { - class: "title" - }, "My first app"), - m("button", { - onclick: increment - }, count + " clicks"), - ]) - } -} - -m.mount(root, Hello) -``` - ---- - -We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR. - -This should be enough to get you started writing the frontend for a real application. Now that you are comfortable with the basics of the Mithril.js API, [be sure to check out the simple application tutorial](simple-application.md), which walks you through building a realistic application. diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index 13c1e4fb..00000000 --- a/docs/installation.md +++ /dev/null @@ -1,274 +0,0 @@ - - -# Installation - -- [CDN and online playground](#cdn) -- [npm](#npm) -- [Quick start with Webpack](#quick-start-with-webpack) - -### CDN and online playground - -If you're new to JavaScript or just want a very simple setup to get your feet wet, you can get Mithril.js from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network): - -```html - -``` - -If you would like to try out mithril without setting up a local environment, you can easily use an online playground at [flems.io/mithril](https://flems.io/mithril). - ---- - -### npm - -```bash -$ npm install mithril --save -``` - -TypeScript type definitions are available from DefinitelyTyped. They can be installed with: - -```bash -$ npm install @types/mithril --save-dev -``` - -For example usage, to file issues or to discuss TypeScript related topics visit: https://github.com/MithrilJS/mithril.d.ts - -Type definitions for pre-release versions of Mithril.js (on the `next` branch) align with the `next` branch of the [types development repo](https://github.com/MithrilJS/mithril.d.ts/tree/next). You can install these types with: - -```bash -$ npm install -D MithrilJS/mithril.d.ts#next -``` - ---- - -### Quick start with Webpack - -1. Initialize the directory as an npm package -```bash -$ npm init --yes -``` - -2. install required tools -```bash -$ npm install mithril --save -$ npm install webpack webpack-cli --save-dev -``` - -3. Add a "start" entry to the scripts section in `package.json`. -```json -{ - "...": "...", - "scripts": { - "start": "webpack ./src/index.js --output-path ./bin --watch" - } -} -``` - -4. Create `src/index.js` file. -```javascript -import m from "mithril"; -m.render(document.body, "hello world"); -``` - -5. create `index.html` -```html - - - - -``` - -6. run bundler -```bash -$ npm start -``` - -7. open `index.html` in a browser - -Optionally, you can include Mithril.js as a global variable using Webpack's provide plugin, to avoid including `import m from "mithril"` across a large number of files: -```js -plugins: [ - new webpack.ProvidePlugin({m: "mithril"}), - // ... -] -``` -Then, you could remove the import line from step 4 (don't forget to restart Webpack if you ran it with `--watch`), and it will work just the same. - -#### Step by step - -For production-level projects, the recommended way of installing Mithril.js is to use npm. - -npm is the default package manager that is bundled with Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node](https://nodejs.org); npm is bundled with that and installed alongside it. - -To use Mithril.js via npm, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`. - -```bash -npm init --yes -# creates a file called package.json -``` - -Then, to install Mithril.js, run: - -```bash -npm install mithril --save -``` - -This will create a folder called `node_modules`, and a `mithril` folder inside of it. It will also add an entry under `dependencies` in the `package.json` file - -You are now ready to start using Mithril. The recommended way to structure code is to modularize it via CommonJS modules: - -```javascript -// index.js -var m = require("mithril") - -m.render(document.body, "hello world") -``` - -Modularization is the practice of separating the code into files. Doing so makes it easier to find code, understand what code relies on what code, and test. - -CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](https://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](https://rollupjs.org/) or [Babel](https://babeljs.io/). - -Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application. - -A popular way for creating a bundle is to setup an npm script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line: - -```bash -npm install webpack webpack-cli --save-dev -``` - -Open the `package.json` that you created earlier, and add an entry to the `scripts` section: - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack src/index.js --output bin/app.js -d --watch" - } -} -``` - -Remember this is a JSON file, so object key names such as `"scripts"` and `"start"` must be inside of double quotes. - -The `-d` flag tells webpack to use development mode, which produces source maps for a better debugging experience. - -The `--watch` flag tells webpack to watch the file system and automatically recreate `app.js` if file changes are detected. - -Now you can run the script via `npm start` in your command line window. This looks up the `webpack` command in the npm path, reads `index.js` and creates a file called `app.js` which includes both Mithril.js and the `hello world` code above. If you want to run the `webpack` command directly from the command line, you need to either add `node_modules/.bin` to your PATH, or install webpack globally via `npm install webpack -g`. It's, however, recommended that you always install webpack locally and use npm scripts, to ensure builds are reproducible in different computers. - -``` -npm start -``` - -Now that you have created a bundle, you can then reference the `bin/app.js` file from an HTML file: - -```html - - - Hello world - - - - - -``` - -As you've seen above, importing a module in CommonJS is done via the `require` function. You can reference npm modules by their library names (e.g. `require("mithril")` or `require("jquery")`), and you can reference your own modules via relative paths minus the file extension (e.g. if you have a file called `mycomponent.js` in the same folder as the file you're importing to, you can import it by calling `require("./mycomponent")`). - -To export a module, assign what you want to export to the special `module.exports` object: - -```javascript -// mycomponent.js -module.exports = { - view: function() {return "hello from a module"} -} -``` - -In the `index.js`, you would then write this code to import that module: - -```javascript -// index.js -var m = require("mithril") - -var MyComponent = require("./mycomponent") - -m.mount(document.body, MyComponent) -``` - -Note that in this example, we're using `m.mount`, which wires up the component to Mithril.js' autoredraw system. In most applications, you will want to use `m.mount` (or `m.route` if your application has multiple screens) instead of `m.render` to take advantage of the autoredraw system, rather than re-rendering manually every time a change occurs. - -#### Production build - -If you open bin/app.js, you'll notice that the Webpack bundle is not minified, so this file is not ideal for a live application. To generate a minified file, open `package.json` and add a new npm script: - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack src/index.js --output bin/app.js -d --watch", - "build": "webpack src/index.js --output bin/app.js -p" - } -} -``` - -You can use hooks in your production environment to run the production build script automatically. Here's an example for [Heroku](https://www.heroku.com/): - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack -d --watch", - "build": "webpack -p", - "heroku-postbuild": "webpack -p" - } -} -``` - ---- - -### Alternate ways to use Mithril.js - -#### Live reload development environment - -Live reload is a feature where code changes automatically trigger the page to reload. [Budo](https://github.com/mattdesl/budo) is one tool that enables live reloading. - -```bash -# 1) install -npm install mithril --save -npm install budo -g - -# 2) add this line into the scripts section in package.json -# "scripts": { -# "start": "budo --live --open index.js" -# } - -# 3) create an `index.js` file - -# 4) run budo -npm start -``` - -The source file `index.js` will be compiled (bundled) and a browser window opens showing the result. Any changes in the source files will instantly get recompiled and the browser will refresh reflecting the changes. - -#### Vanilla - -If you don't have the ability to run a bundler script due to company security policies, there's an options to not use a module system at all: - -```html - - - Hello world - - - - - - -``` - -```javascript -// index.js - -// if a CommonJS environment is not detected, Mithril.js will be created in the global scope -m.render(document.body, "hello world") -``` diff --git a/docs/integrating-libs.md b/docs/integrating-libs.md deleted file mode 100644 index a4d7fd63..00000000 --- a/docs/integrating-libs.md +++ /dev/null @@ -1,128 +0,0 @@ - - -# 3rd Party Integration - -Integration with third party libraries or vanilla JavaScript code can be achieved via [lifecycle methods](lifecycle-methods.md). - -## noUiSlider Example - -```javascript -/** NoUiSlider wrapper component */ -function Slider() { - var slider - - return { - oncreate: function(vnode) { - // Initialize 3rd party lib here - slider = noUiSlider.create(vnode.dom, { - start: 0, - range: {min: 0, max: 100} - }) - slider.on('update', function(values) { - vnode.attrs.onChange(values[0]) - m.redraw() - }) - }, - onremove: function() { - // Cleanup 3rd party lib on removal - slider.destroy() - }, - view: function() { - return m('div') - } - } -} - -/** Demo app component */ -function Demo() { - var showSlider = false - var value = 0 - - return { - view: function() { - return m('.app', - m('p', - m('button', - { - type: 'button', - onclick: function() { - showSlider = !showSlider - } - }, - showSlider ? "Destroy Slider" : "Create Slider" - ) - ), - showSlider && m(Slider, { - onChange: function(v) { - value = v - } - }), - m('p', value) - ) - } - } -} - -m.mount(document.body, Demo) -``` - -[Live Demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoJAAgBytAKoQAylAgATGACdpAdy0YADoe3S6WQ-RHSJYgDpowAVzTViEetNUbtACgCU0sAO0tIAbhg6cGqaWg4h0lowxE5aaEEJofTUSRiMiNLOru70vmFotJqBwemhdWJi0gCSaBDuGGoAXjDSAMxa6tKGkcQAntJqAEbShNowmXXRPjoAvNIVSt6x+DkweTBlFZr46rRYFBm1dYvEIwUADBQL1wZoAOYwBcBYEGgPF1gMAAPAoARnu91Yz2krH80KW21KAHInIZ1PskRcim4PGgyh0nPBqtDQuVKjB8HliFo4Ph6ABhQgYd4HCJQQlwZD3AC6cKu12kWHwSXUBl0AWhsIW7AW9CSWFoYU+hRcONKxP5oQa0npsGZqL6AyGI3GU2knnlio68Ji2hO8GptFGEv5Mv5YQgMF0BWxJTxGoFiWSqXSWF8SPUEDCSL51yhtXj8YckhkABEYArpEZDGYzpY0NZbA5fbjpOmFQFLqFYMRpHBCLRdFtTGswB04PNajXwgSemt7vFakkUmkq3UPV6faq-ZWaoHhyHBeHKcZMSSl0jDGvNQKw0jJk5iMR6NvA4G52fA2MTAV94fj2hT5eBdk1NQANZT4q42fry-1xtm1WaQAEIAKbW04h3S942fOo3Tg0JwKA6QAH5pDsEB0zgR1xiAzDpAKTD6VyRgvEgzC-2kWMz38J5oLrBsIOWaQADJWKXICLgvS8GSZFkvzVPEwgDRC2UJaQ1jCKjYLPWF6MvPctwucSYBo651JhBJE0HIUFRcYhfFOagnBwBh8EmSpRguctaD5NgOBATAcDwHY4AEGh6EYZgeDYbkqDUNB3wQFBOBcngKicCAEW0SgQFScgeBIYhDDgRAGhcQx3zeHYzjESLosggABUEACZ8FBfB7jESMcK0CAD0YfLaCimKtHwfg4uvbgQDgHIIEMUR2DCnqCratyPISvBktS9KxEy7LcqwZrWuKsqKqqmroupBrDxgFbCuWCautGEw8Bw0ZYAcka8B+YhCHq8gqCmpKj1mjK0CynLzDEO6HugEqNoANl+tp-qgDqPO687+sGvzWCAA) - -## Bootstrap FullCalendar Example - -```javascript -/** FullCalendar wrapper component */ -var FullCalendar = { - oncreate: function (vnode) { - console.log('FullCalendar::oncreate') - $(vnode.dom).fullCalendar({ - // put your initial options and callbacks here - }) - }, - onremove: function (vnode) { - // Run any destroy / cleanup methods here. - $(vnode.dom).fullCalendar('destroy') - }, - view: function (vnode) { - return m('div') - } -} - -/** Demo app component */ -function Demo() { - var fullCalendarEl - - function next() { - $(fullCalendarEl).fullCalendar('next') - } - - function prev() { - $(fullCalendarEl).fullCalendar('prev') - } - - return { - view: function (vnode) { - return [ - m('h1', 'Calendar'), - m(FullCalendar, { - oncreate: function(vnode) { - fullCalendarEl = vnode.dom - } - }), - m('button', { - onclick: prev - }, 'Mithril.js Button -'), - m('button', { - onclick: next - }, 'Mithril.js Button +') - ] - } - } -} - -m.mount(document.body, Demo) -``` - -[Live Demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvAHigjQGsACAJxigBeADog4xAJ6w4hGDGKjuhfmBEgSxAA5xEAel3UAJmgBWcfNSi0ArobBQM-C7Sy6MJjAA9d7AEZxdMGsoKGoMWDRDR10AZnwAdnwABkDg0PCmKN58LA4LODhRAD4QAF8KdGxcRAIzShp6RmYa3QAqVu4AMTSAYQzIx24Ad14MTU0YXm46LE16JmJuVt1hNAA3Qe6Qvois7kFuYFXhYnpqfgxGRG4gtGpiCHpuAAo1tFpDGABKQ+OFYjoaDgtFg+CsAHNngByLZQHYDXiIRBnC6MKFfP4nAAkr3en3whhcX3wQW2-SyzyOaBONOI+m4mmsiwkNimHAgD3C3Fomge9Dg3AwkWm4Sgvgw1E4Atk-ExxFKGOp8oof3o-CwtDWMGut3ujzQLzeH2+vyVJ3pACVrAahRJuJ9xLxaHbdNNYELrJpuDhiIQPtLJjB8HKcUb8YSsMTSXDyY5oQ7iE6JOi-uU-msIDAhjrrXqnrjjT8qbT+MRrLwDVh4xA1imlaVVg3qWg2h0ACIwDWC8bTFxzNALJYrNC6vkGjsa55F1bcbgbKbRnaZRwAUSgxwNN1zY+4A88xCnptns5xi9jvDXUd65+he+IddnTZnW7uO80-DWh6px+4p+vu1XKArzJADeGhd8YFrRVHw3WdS3LA1v2PDMsxzV99UNPETSQn94IrbhkGfH9ZyrKFCAARihChuChJcEXRFVN2I71nlhOismonDmO5O5UW1F88zQAtPmnJjuNnM9QLXfY5ywgkXCI7im3EhVGPE0jfCZU40Coo9xJ4ywIEla4ILWRSf3KGiAFkOWUaBuAAIS0p4AFoGPM48NOcnTOI8n8znYYzdxgfc-O4SyoRs31eHspziG07gAGo6w8gBdRTlPCxsNywHIbAYZ5CWoawcAYfBfA+CRqInWhFTKCoQEwHA8HyBAqEBJpiDwMpUqodguAQFBKmampcmi6B6nLcgag0bQ9F0a1NE4cFnFcMa7KgAABcj8B2gBWXR1piqB8DqKhJAmPA4HOCBeXq4bqhADVSq6qgprwWadH0RbltWw6XAWTaACZdoATnwIH-pe062pAC7HuumK7vKB68BMABHaxJgkSbeGm9R4rm760CWlaZl0DGsd4CRNriEHyN0QwIHECnMexmH6nhq6buRhqmse6MwlA3H8c++afrJlxUhCIXl14WmEmSRnmbpQXzw586JEumpEdurrSlS0ogA) diff --git a/docs/jsx.md b/docs/jsx.md deleted file mode 100644 index 10df264f..00000000 --- a/docs/jsx.md +++ /dev/null @@ -1,442 +0,0 @@ - - -# JSX - -- [Description](#description) -- [Setup](#setup) -- [Production build](#production-build) -- [Using Babel with Webpack](#using-babel-with-webpack) -- [Differences with React](#differences-with-react) -- [JSX vs hyperscript](#jsx-vs-hyperscript) -- [Tips and Tricks](#tips-and-tricks) - ---- - -### Description - -JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on you or your team's preferences. - -```jsx -function MyComponent() { - return { - view: () => m("main", [m("h1", "Hello world")]), - }; -} - -// can be written as: - -function MyComponent() { - return { - view: () => ( -
-

Hello world

-
- ), - }; -} -``` - -When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces: - -```jsx -var greeting = "Hello"; -var url = "https://google.com"; -var link = {greeting}!; -// yields Hello! -``` - -Components can be used by using a convention of uppercasing the first letter of the component name or by accessing it as a property: - -```jsx -m.render(document.body, ) -// equivalent to m.render(document.body, m(MyComponent)) -Go home -// equivalent to m(m.route.Link, {href: "/home"}, "Go home") -``` - ---- - -### Setup - -The simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. - -Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command: - -```bash -npm init -y -``` - -If you want to use Webpack and Babel together, [skip to the section below](#using-babel-with-webpack). - -To install Babel as a standalone tool, use this command: - -```bash -npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev -``` - -Create a `.babelrc` file: - -```json -{ - "presets": ["@babel/preset-env"], - "plugins": [ - [ - "@babel/plugin-transform-react-jsx", - { - "pragma": "m", - "pragmaFrag": "'['" - } - ] - ] -} -``` - -To run Babel, setup an npm script. Open `package.json` and add this entry under `"scripts"`: - -```json -{ - "name": "my-project", - "scripts": { - "babel": "babel src --out-dir bin --source-maps" - } -} -``` - -You can now run Babel using this command: - -```bash -npm run babel -``` - -#### Using Babel with Webpack - -If you haven't already installed Webpack as a bundler, use this command: - -```bash -npm install webpack webpack-cli --save-dev -``` - -You can integrate Babel to Webpack by following these steps. - -```bash -npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev -``` - -Create a `.babelrc` file: - -```json -{ - "presets": ["@babel/preset-env"], - "plugins": [ - [ - "@babel/plugin-transform-react-jsx", - { - "pragma": "m", - "pragmaFrag": "'['" - } - ] - ] -} -``` - -Next, create a file called `webpack.config.js` - -```jsx -const path = require("path"); - -module.exports = { - entry: "./src/index.js", - output: { - path: path.resolve(__dirname, "./bin"), - filename: "app.js", - }, - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /\/node_modules\//, - use: { - loader: "babel-loader", - }, - }, - ], - }, - resolve: { - extensions: [".js", ".jsx"], - }, -}; -``` - -For those familiar with Webpack already, please note that adding the Babel options to the `babel-loader` section of your `webpack.config.js` will throw an error, so you need to include them in the separate `.babelrc` file. - -This configuration assumes the source code file for the application entry point is in `src/index.js`, and this will output the bundle to `bin/app.js`. - -To run the bundler, setup an npm script. Open `package.json` and add this entry under `"scripts"`: - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack --mode development --watch" - } -} -``` - -You can now then run the bundler by running this from the command line: - -```bash -npm start -``` - -#### Production build - -To generate a minified file, open `package.json` and add a new npm script called `build`: - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack -d --watch", - "build": "webpack -p" - } -} -``` - -You can use hooks in your production environment to run the production build script automatically. Here's an example for [Heroku](https://www.heroku.com/): - -```json -{ - "name": "my-project", - "scripts": { - "start": "webpack -d --watch", - "build": "webpack -p", - "heroku-postbuild": "webpack -p" - } -} -``` - -#### Making `m` accessible globally - -In order to access `m` globally from all your project first import `webpack` in your `webpack.config.js` like this: - -```js -const webpack = require('webpack') -``` - -Then create a new plugin in the `plugins` property of the Webpack configuration object: - -```js -{ - plugins: [ - new webpack.ProvidePlugin({ - m: "mithril", - }), - ]; -} -``` - -See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more information on `ProvidePlugin`. - ---- - -### Differences with React - -JSX in Mithril has some subtle but important differences compared to React's JSX. - -#### Attribute and style property case conventions - -React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example using `className` instead of `class` and `htmlFor` instead of `for`. In Mithril, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril always falls back to setting attributes if a property doesn't exist which aligns more intuitively with HTML. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example `value`/`checked` for inputs and the `tabindex` global attribute vs the `elem.tabIndex` property on HTML elements. Very rarely do they differ beyond case: the `elem.className` property for the `class` attribute or the `elem.htmlFor` property for the `for` attribute are among the few exceptions. - -Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case. - -#### DOM events - -React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as they're multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background. - -React supports scheduling event listeners during the capture phase (in the first pass, out to in, as opposed to the default bubble phase going in to out in the second pass) by appending `Capture` to that event. Mithril currently lacks such functionality, but it could gain this in the future. If this is necessary you can manually add and remove your own listeners in [lifecycle hooks](lifecycle-methods.md). - ---- - -### JSX vs hyperscript - -JSX and hyperscript are two different syntaxes you can use for specifying vnodes, and they have different tradeoffs: - -- JSX is much more approachable if you're coming from an HTML/XML background and are more comfortable specifying DOM elements with that kind of syntax. It is also slightly cleaner in many cases since it uses fewer punctuation and the attributes contain less visual noise, so many people find it much easier to read. And of course, many common editors provide autocomplete support for DOM elements in the same way they do for HTML. However, it requires an extra build step to use, editor support isn't as broad as it is with normal JS, and it's considerably more verbose. It's also a bit more verbose when dealing with a lot of dynamic content because you have to use interpolations for everything. - -- Hyperscript is more approachable if you come from a backend JS background that doesn't involve much HTML or XML. It's more concise with less redundancy, and it provides a CSS-like sugar for static classes, IDs, and other attributes. It also can be used with no build step at all, although [you can add one if you wish](https://github.com/MithrilJS/mopt). And it's slightly easier to work with in the face of a lot of dynamic content, because you don't need to "interpolate" anything. However, the terseness does make it harder to read for some people, especially those less experienced and coming from a front end HTML/CSS/XML background, and I'm not aware of any plugins that auto-complete parts of hyperscript selectors like IDs, classes, and attributes. - -You can see the tradeoffs come into play in more complex trees. For instance, consider this hyperscript tree, adapted from a real-world project by [@dead-claudia](https://github.com/dead-claudia) with some alterations for clarity and readability: - -```javascript -function SummaryView() { - let tag, posts; - - function init({ attrs }) { - Model.sendView(attrs.tag != null); - if (attrs.tag != null) { - tag = attrs.tag.toLowerCase(); - posts = Model.getTag(tag); - } else { - tag = undefined; - posts = Model.posts; - } - } - - function feed(type, href) { - return m(".feed", [ - type, - m("a", { href }, m("img.feed-icon[src=./feed-icon-16.gif]")), - ]); - } - - return { - oninit: init, - // To ensure the tag gets properly diffed on route change. - onbeforeupdate: init, - view: () => - m(".blog-summary", [ - m("p", "My ramblings about everything"), - - m(".feeds", [ - feed("Atom", "blog.atom.xml"), - feed("RSS", "blog.rss.xml"), - ]), - - tag != null - ? m(TagHeader, { len: posts.length, tag }) - : m(".summary-header", [ - m( - ".summary-title", - "Posts, sorted by most recent." - ), - m(TagSearch), - ]), - - m( - ".blog-list", - posts.map((post) => - m( - m.route.Link, - { - class: "blog-entry", - href: `/posts/${post.url}`, - }, - [ - m( - ".post-date", - post.date.toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }) - ), - - m(".post-stub", [ - m(".post-title", post.title), - m(".post-preview", post.preview, "..."), - ]), - - m(TagList, { post, tag }), - ] - ) - ) - ), - ]), - }; -} -``` - -Here's the exact equivalent of the above code, using JSX instead. You can see how the two syntaxes differ just in this bit, and what tradeoffs apply. - -```jsx -function SummaryView() { - let tag, posts; - - function init({ attrs }) { - Model.sendView(attrs.tag != null); - if (attrs.tag != null) { - tag = attrs.tag.toLowerCase(); - posts = Model.getTag(tag); - } else { - tag = undefined; - posts = Model.posts; - } - } - - function feed(type, href) { - return ( -
- {type} - - - -
- ); - } - - return { - oninit: init, - // To ensure the tag gets properly diffed on route change. - onbeforeupdate: init, - view: () => ( -
-

My ramblings about everything

- -
- {feed("Atom", "blog.atom.xml")} - {feed("RSS", "blog.rss.xml")} -
- - {tag != null ? ( - - ) : ( -
-
- Posts, sorted by most recent -
- -
- )} - -
- {posts.map((post) => ( - - - -
-
{post.title}
-
- {post.preview}... -
-
- - -
- ))} -
-
- ), - }; -} -``` - ---- - -### Tips and tricks - -#### Converting HTML to JSX - -In Mithril.js, well-formed HTML is generally valid JSX. Little more than just pasting raw HTML is required for things to just work. About the only things you'd normally have to do are change unquoted property values like `attr=value` to `attr="value"` and change void elements like `` to ``, this being due to JSX being based on XML and not HTML. - -When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a [community-created HTML-to-Mithril-template converter](https://arthurclemens.github.io/mithril-template-converter/index.html) to do much of it for you. diff --git a/docs/keys.md b/docs/keys.md deleted file mode 100644 index 6ac6b7e8..00000000 --- a/docs/keys.md +++ /dev/null @@ -1,553 +0,0 @@ - - -# Keys - -- [What are keys?](#what-are-keys?) - - [Key restrictions](#key-restrictions) -- [Linking model data in lists of views](#linking-model-data-to-views) -- [Keeping collections of animated objects glitch-free](#keeping-collections-of-animated-objects-glitch-free) -- [Reinitializing views with single-child keyed fragments](#reinitializing-views-with-single-child-keyed-fragments) -- [Common gotchas](#common-gotchas) - - [Wrapping keyed elements](#wrapping-keyed-elements) - - [Putting keys inside the component](#putting-keys-inside-the-component) - - [Keying elements unnecessarily](#keying-elements-unnecessarily) - - [Mixing key types](#mixing-key-types) - - [Hiding keyed elements with holes](#hiding-keyed-elements-with-holes) - - [Duplicate keys](#duplicate-keys) - ---- - -### What are keys? - -Keys represent tracked identities. You can add them to [element, component, and fragment vnodes](vnodes.md) via the magic `key` attribute, and they look something like this when used: - -```javascript -m(".user", {key: user.id}, [/* ... */]) -``` - -They are useful in a few scenarios: - -- When you're rendering model data or other stateful data, you need keys to keep the local state tied to the right subtree. -- When you're independently animating multiple adjacent nodes using CSS and you could remove any one of them individually, you need keys to ensure the animations stick with the elements and don't end up unexpectedly jumping to other nodes. -- When you need to reinitialize a subtree on command, you need to add a key and then change it and redraw whenever you want to reinitialize it. - -#### Key restrictions - -**Important:** For all fragments, their children must contain either exclusively vnodes with key attributes (keyed fragment) or exclusively vnodes without key attributes (unkeyed fragment). Key attributes can only exist on vnodes that support attributes in the first place, namely [element, component, and fragment vnodes](vnodes.md). Other vnodes, like `null`, `undefined`, and strings, can't have attributes of any kind, so they can't have key attributes and thus cannot be used in keyed fragments. - -What this translates to is stuff like `[m(".foo", {key: 1}), null]` and `["foo", m(".bar", {key: 2})]` won't work, but `[m(".foo", {key: 1}), m(".bar", {key: 2})]` and `[m(".foo"), null]` will. If you forget this, you'll get a very helpful error explaining this. - -### Linking model data in lists of views - -When you're rendering lists, especially editable list, you're often dealing with things like editable TODOs and such. These have state and identities, and you have to give Mithril.js the information it needs to track them. - -Suppose we have a simple social media post listing, where you can comment on posts and where you can hide posts for reasons like reporting them. - -```javascript -// `User` and `ComposeWindow` omitted for brevity -function CommentCompose() { - return { - view: function(vnode) { - var post = vnode.attrs.post - return m(ComposeWindow, { - placeholder: "Write your comment...", - submit: function(text) { - return Model.addComment(post, text) - }, - }) - } - } -} - -function Comment() { - return { - view: function(vnode) { - var comment = vnode.attrs.comment - return m(".comment", - m(User, {user: comment.user}), - m(".comment-body", comment.text), - m("a.comment-hide", - {onclick: function() { - Model.hideComment(comment).then(m.redraw) - }}, - "I don't like this" - ) - ) - } - } -} - -function PostCompose() { - return { - view: function(vnode) { - var comment = vnode.attrs.comment - return m(ComposeWindow, { - placeholder: "Write your post...", - submit: Model.createPost, - }) - } - } -} - -function Post(vnode) { - var showComments = false - var commentsFetched = false - - return { - view: function(vnode) { - var post = vnode.attrs.post - var comments = showComments ? Model.getComments(post) : null - return m(".post", - m(User, {user: post.user}), - m(".post-body", post.text), - m(".post-meta", - m("a.post-comment-count", - {onclick: function() { - if (!showComments && !commentsFetched) { - commentsFetched = true - Model.fetchComments(post).then(m.redraw) - } - showComments = !showComments - }}, - post.commentCount, " comment", - post.commentCount === 1 ? "" : "s", - ), - m("a.post-hide", - {onclick: function() { - Model.hidePost(post).then(m.redraw) - }}, - "I don't like this" - ) - ), - showComments ? m(".post-comments", - comments == null - ? m(".comment-list-loading", "Loading...") - : [ - m(".comment-list", comments.map(function(comment) { - return m(Comment, {comment: comment}) - })), - m(CommentCompose, {post: post}), - ] - ) : null - ) - } - } -} - -function Feed() { - Model.fetchPosts().then(m.redraw) - return { - view: function() { - var posts = Model.getPosts() - return m(".feed", - m("h1", "Feed"), - posts == null ? m(".post-list-loading", "Loading...") - : m(".post-view", - m(PostCompose), - m(".post-list", posts.map(function(post) { - return m(Post, {post: post}) - })) - ) - ) - } - } -} -``` - -It encapsulates a lot of functionality as you can tell, but I'd like to zoom into two things: - -```javascript -// In the `Feed` component -m(".post-list", posts.map(function(post) { - return m(Post, {post: post}) -})) - -// In the `Post` component -m(".comment-list", comments.map(function(comment) { - return m(Comment, {comment: comment}) -})) -``` - -Each of these refers to a subtree with associated state Mithril.js has no idea about. (Mithril.js only knows about vnodes, nothing else.) When you leave those unkeyed, things can and will get weird and unexpected. In this case, try clicking on the "N comments" to show the comments, typing into the comment compose box at the bottom of it, then clicking "I don't like this" on a post above it. [Here's a live demo for you to try it out on, complete with a mock model. (Note: if you're on Edge or IE, you may run into issues due to the link's hash length.)](https://flems.io/#0=N4Igxg9gdgzhA2BTEAucD4EMAONEBMQAaEGMAJw1QG0AGIgZgCYB2AXRIDMBLJGG0FEwBbZGgB0ACwAuw+MXRRpiJahAgAvkUEixIYRHyJ44gFb8SkJSulqAOiACueAAQxp5bmGkO7UPwBumOQuALKGxi4AvC4AFJyOUN7c0LEAlC7AfnbSQSFgmGCSiNEuAPIARqaI3uIUiJjKsVCO8PBp2T7+0glJ0ilQLgVFBLEA1ogAnkQu3FDc0hlZ3TnkiNKO5IPDxdQTk2wuAIQxLW0uAPxDhbv7hyhxO4h7U4cxcwvpHd0a2d29yWgLmEmAmAFU8OR0plOjk8i5nIhyAA5XSlTigpHiObKLbrcSIlG6dKwjzrTbbG6jByElAOFwAagRkNRohmAP6qSWpJyAHpeS4AJJGTBtaZCgDk+Bcpmc0mZJXgEDWwmw3AAHpF8Nw1t54OKKo55QsXPgIIgYFAJfKYI5sNhlV0cs7pPyXAAJAAqnoACgBlcQuP0QWawZSYfAzQUuSQRlzSEMAd08ymBkxcEETgwAjo4vGMXMj1vBuJx05CAl5EDzXQKEy5E8qC8EIIlpSbMFB20l4I4jC5sJRsEiXABhMoAJT9MYaRnIMDpKx8tZj0mkuBQ-IA5gtJI4KnUIMJedwYNxMJJRBHMzBeUqVWrNfAALSQefPwcQdWTGvw7jCLcwXIeBSgcGR1wXfljEQLdO2kZ9k24LcZGfTAABZMAAVkQNDxCgYtS0mQ9jzwgiy15DkBlvNZcwtHwQD+F1VnJLZgXEGjHDo2J-0A4CZmWJjlwqQoxi3Sg2weDxOKIGtmJgB1YEQT1JmHB4HAqJUKgcGSlxyDQ0nEaRiigeJEkBEyNIgCpuV05c1g2ViBOXQTuHwB5HDzfBxACND0h0wSXUJVlEHclldH85zIsJH0vAckKXDBCcABk6jWRpEEqapvESpLYks6yIsE35bP00lSp+RiejMzlBhBcFIRgWJIESRYYSXeFCRgUpqDYUlOGVOI-1KWgAG5ZhcAAeIZWyUMbuAZBkMk68RsGcSRYjqxAISRL5SXsikXB9ShhFPRBxFFeBYk6749Mq+EwE2NYlG28gZgddwYEqyigU4dYihe6EnOYhzKRGfBYhpSFtJcb6TJswT9tYzaAYMoyVFMvoBiuyF4cinIHvIJ7pBe0pCRrcqXQp6Ris6WGYb+yQfQgD7Ab2ljQeKcGHHe6RPuIGHqqx3GXUR2rMRexqMQmch2M7M1hDwxxhAqHbgBOqAHgARhmEF1QeJhaH0m68ekQzjIx8zYhLdxhZNnmupia3TZBbALZq7GkVtgKyRBtrvZctyEU87zfLSQqTcCyFQqRcP-bXRB1WkB4paxe9EAV5RE782STcgYRRCUUcZqTmHMRl8g5aPRXldV9WHnoYFMD1lwGENsOc8pjvqeNoqe+7zoae6e6jwL3nSkympTfqdLmladovsFn6GaL-ObEannbdF64wYhkAebpEBGQHZnTdc9lF7hv3Iq35GGtiHniNHouWtR83Yat09WqByK89HmBqAfq5N4LgnbiBdm7LGhIvbX3ZlfOOrl3LBx8tnWyglaQKlel3HImcS4pxlmnDOCdpAoLjjTE2VNKZ9ypoPHIdMVQQACIgAAQpMYUH93AzAWOnW2w8WrDT6gNWIQ0YijXGlNUBSAoBbiMnNBa0DSxxCdtQbgbBsTShOO8ZQCtXIZCUc1JQC1gFKJUWVUkEiVDSMkKUfRTo7KwPwomQ6x1ToQNSGsOA8BGFLDcOsT0-5ECtmIe4hAjCZiYVoLQDI1DKpb2-r9aQ-0o70wSZIF64d4lFCZh9ZODMsm83ScvEea8ckpJXn-WOS4tzrBesnC+rNbJbwJkTF6ZUKkuiqdIPJC4BaYy5HAuxvt7bHFOHPS4x8PriBgCWMAiBoQPDOPAVplV8ZpWUHk2pvSTI4OgVvDJqTITpDNujd+0Ccj2xWmtWI38A6INciHEhEcNhJKaTYNJXccGSSIbHH+RTC7F3rt8vSfcgVLP8EuSQrlEDrJ6ZbDe-TgYHXoYwlhbD7ZvRPlQ-ylT1hlOKTC92cLrk+wOr-NeACT5qMOCcFwCyxmkqUP-QB+BVFTKrHMmlc9QWkgjPgXFSgNmwpPjMbZ8LiWsSOkeU6502ixGoDnPZKNAU9EKavBl98MU1jYK-Y5dTTkJN+bzcl7hKUXJgOtIlLoEFBzucg9uqDlzoJec9SESrsFEM+YnJVFDlzeq0MsoykK+W4LqYSmsW8kXMNYeDelhqmUcHGYsLl1M-D6XSAoPASBzL8DQJrTCKBMIAE5NDaBAEIUQagzAWEUMoVQaBIYlHcJ4bwvgwV0xRlfWJpJKyIETAK92AQoARGgR1SEpQB0RHOmuecBIoYNNgcIXeM6kTaRzguhwS7yAflipsas-M10gB4tDYAMByBgGjjLaK261hGyVfujdz4y27pmA4AAAtDQkG7gp2sElQgeKa-h0xXu9RAAB1OYZpEz1LhMENw+4TprjmFudEoo8CdHhDgzh8x+iindIgJCMhOidvatwHtfasbjqMMOmDPMx2DqMJOjwMAVonzDfOxdedgMrvtfunBwQGhcbjhayK2oYCYA0gQB4toKjwf6FImYQRezxQw1ghMSRVnxXfhRxAeqXJYfPPAXD+H5QxAZImcDmZxAdKA0aAgfppCTCQEIujZ15YGWKEZyZ0zZkN2fEwYFjytPiHlpM+zSApB4eQsZlwDhMBGggC2uOLpAvBfcA5s67nIulFCI0SQYCm7cT0zhiLMh5POaC1XMglA2iGcixkJk3N1QJceXpV1y5oBzFWsGzZsREABB05FHBpReuGWCB07yopOIqZyMN6Qo38SpbCxlmQoEQCxYTE1xLygAgjfIGNhb6XitRey0ZPL6oCsLH0zVkrLgZtzdNpVjAV3Wr1b3o1hi9rKatbOVgGZkgEBzgeIFxojGVo-cQH9+Ac5Wt+vtd+k2d6OPM0QM+KT8Hob7sNGuaAAnHnAGgGAaZYwyN9KEy6VHCxZNIZiFJasH3lxA6nUx8nxDtlHJMic0V3tmeU+Q-AVDdOXQKzWPgCukH-O9xU4NmIvh3ukJh4lkAeSNuRTSP539FUoCD0AwaoDSOoNisGN-btvb8Xkec1RkINGYgM5BzzVjvsF267wGBrsmZ+I52wGDiHAPosgBAymEokxWz5ANeIMPOPBLM+J1soh-Wt7hCMCYHlQb1UcPjLHnO8vIq+r-ZrgDF8xwGv10RpLJGTeafN6K4eqqos2+nTG+3B0EcGoj5FBdaTMiOtD4SfSt72MGufBUQwP5+YxsMrHvvMXH42GfBCowrfBJ457PmaP+u44J+MFIQNReY3apMkLggovxc+qz-7EA0YzRWnlCWCY8YIV8xzmr0xGutcF7yU72ZuMS-07L6vrTFvpoa9aMJ1gd68W850HdYgP8XcIN3d7VPdChwd-skQ1I-cA8XAg9NgE0w9xAF9lwo8wgIgTBp41khVyZ1dbo89W038T4nMh0r54QzVMwg0HZS4+dadoMQ8a8YAAAxBmAgXnfnQjWBI3X-U3VIf-KvajE+YA+jUApjO3WyavP+UoJgxMFgsZDfEwKzA1deDFFweZTlCApvRdO3YgVdWIDvYAdBB+HvOHQSO9HmQfYfaGB+bZSfEAZjdwZ8UQWbPAwXXeTALw+CGNV8YufwyKJfAnFfcQy+UnSKBRWII4NQjQgAMlSOOBjV4P4PwH60EiyL4JSQEOp3IEmwF0ii0PED2RYNT0WDZw2nYkP0wETGPyKim15j+3UN0NKGSM6JYJUw0FP0eQfhjWfiUGfUPgb3MPKJGJ12LmiCiBiE1jGRlwMN9z5la3sPh0COCNn0hQiJdCiMJ1XzyOXEqLnyhVoI3nqIPxF2aNaJPy+3ogv2gGtBAW4FvwDQfzp2Py2LJz6O6KuEcJPjCO4IOP1W4IWI5TaC7iBP7xr2fCdkRIgAjEQ2hgcCShRO1CkRwIcAeIeDlXKObwRKdmhiyLy1dnfl305xNhvigINX4hjQeBjW9W9hvRU0dzmNVCR34n3gTV7y7l6lhzWIWRrAoOTSoL8Dpj4NGFxkqL2S6UOTRn30aLuJaOEN9lENI1iLX3p2kI+iyyIMs3WEVP8zpPXV+gIAiP3UkE1nRJABlPwDxKVSGUWOhJAjhPXScKRKVFRKkXtMxL9K3FxJAGPweGBO8ON3BIXXfyPGAz+Lb1MJBNJP5nOXAXflDXKLpLyV5JPgeB5lZOz1V0fzFOf0oMHgVgMBaliDNAelHnECH3wHFEdLSHTWgizTUE1mYBQCYAADZi0dBy0JAwAYAq0rAa1bA0BGz0xlgXAYZoB4IMQTp9QHgABBTwUUGYUTWAFHJEUsEaf9MFDdNqOckTBAyYZOJAdUA8wYFwUUJCKAZ8LhYQbpGZawcgG8weYIlwY82cxuXbOYZwrHYQeucQbCYQT8v4e9NUbwHdE8-8ncR8zwSLUC8CyCo8wkLdWCtYWYACeCpbEuBgdOG8ucofcgOcZ8CubUZwB4IiiCw8vwYImYYiYDeCsin3TWbAdUNwBAVyFwDSESEi-i5UCiqi7gGilwTWYivwOc7AHlRDLWaS28kEACx8hMbAUClgNChiqAFipHdPROPjTANikSlAmlaARAIS9xbgAAL3ikHXwiEoYSRE4CVBNznyMCgCErM3wCMi1giQAFIhKCLaKlLTzTxzyHhLIwAxghL+olAUdbL4opL6LJTdLEc8AUc4MTQ-ycFUISwtwNYXBkKZB0LGKMqShMdVN4L4r4Izw7LFLUrSLTLN0xKJKmAwrhLyKzKHLLKZL+KRIxIZpA4ABiWgTAFgISyAe8B4EazgeaoSuS-AbErcVC9OSSzqs8rAC80MEsfCQfJUGKsq3Spw3w4yv8razAHa1yhOIS2UdwQiMI6wflNwOSmZQfdYRMRAFQISlSxC58dSzS7S282qxKhqlwWgcQAADiUq-NOvWGMoAD4XAAAqI+NGv8v6wCpATgEuFK463Y0ImxZipwi45i0Ii4ky7q8geZCyxa+SqReuOKhcsG5Kzq0G-bB4BYe8sAKazYOAGm4+HEJEAmpwom-5P7RhTBE6kEi4lASWmOcqgfOWhWkIP86a5UWaqG7W0WkErI+CrGtSiADSiGsC2Gv4F9BCRACoMYBYZ8fYTgCuUQLqTsf8dKQMla+C2gAKzIaaZ6kuDErEtEkAMaYqOcpgTCH24AP2ic1Aj2xDXAkOlwMOlwcJKOmOmwOOoOnExO0O-qrS9O8czO33eOnO3O5Ow8l9B2p2i0O8+YEEZQUupDP87232oul6wOoMhwPO28iOwuhc4uzula8ulOtOtugejukAJu8PJOlOgu8e-2rOoMkMnuuczWQKhe2Oku7OrcbuiutK8QX0z23KohJ8rsYu58AtK+gtLioSva5HEKiGgmo+hSlATAXGkcE+xOM+zykuUafq++2fQ7LmwreAISy6na6K2K-q9ugOqenele-qq2m2u212hugYfWLqe+mDSGzCF2+u92ne0MHgLDPq28tBxoDBlwJgLBuYBoEIXB-Bt2xuohuYEhrhAm0Ip2Gqlm+q+KSGmG1Kr8rhz+H8zCmCuKXC5u-qx+jqoRqCmNeCpalaxqoS9isyzi7ijxPigSo6-q9R1q1EiSyGrSzqvK+8wqh4Eq6QThgfac+C8xgqoqnGmx-qxxh8yTWbcgVxg+imyFA20bQCwG020x+Ro8sW7ovS1wP8gxrWLiniksaUXR6B28gxyiox7pSG3szq5RhSkJ4Gucw2gG42pmnSwmyJiqgyrxhofC4Bludm3hpK0CwR3W7w-WqJyqo0aqv8jmpp02lp-q3Jxm028CjaoRtszNGqbNEALspgFATWTQDgEAPasYbNagQcvQeDSQTweQEgTYeQNAcCDcfkRIbAUSYiXkLZnZl9JgcQSGtCS53cHZsBOYStBQezYcNQSrbgbAWwLQDZtQPBF9XCTWO5hQfZtQI5yCXkRMGy8i7EKAXkdwOWUUCyiiMuYF8QUF2gd5lSPQb535gc0tXQNQDyVyF9BgcQSlhgcF4CSFtcY5mFuFryOYJF2bLsVF-CXkMl-AClqlql3Fz5tAAlv5tgDQIAA) - -Instead of doing what you would expect, it instead gets really confused and does the wrong thing: it closes the comment list you had open and the post after the one you had the comments open on now just persistently shows "Loading..." even though it thinks it's already loaded the comments. This is because the comments are lazily loaded and they just assume the same comment is passed each time (which sounds relatively sane here), but in this case, it's not. This is because of how Mithril.js patches unkeyed fragments: it patches them one by one iteratively in a very simple fashion. So in this case, the diff might look like this: - -- Before: `A, B, C, D, E` -- Patched: `A, B, C -> D, D -> E, E -> (removed)` - -And since the component remains the same (it's always `Comment`), only the attributes change and it's not replaced. - -To fix this bug, you simply add a key, so Mithril.js knows to potentially move state around if necessary to fix the issue. [Here's a live, working example of everything fixed.](https://flems.io/#0=N4Igxg9gdgzhA2BTEAucD4EMAONEBMQAaEGMAJw1QG0AGIgZgCYB2AXRIDMBLJGG0FEwBbZGgB0ACwAuw+MXRRpiJahAgAvkUEixIYRHyJ44gFb8SkJSulqAOiACueAAQxp5bmGkO7UPwBumOQuALKGxi4AvC4AFJyOUN7c0LEAlC7AfnbSQSFgmGCSiNEuAPIARqaI3uIUiJjKsVCO8PBp2T7+0glJ0ilQLgVFBLEA1ogAnkQu3FDc0hlZ3TnkiNKO5IPDxdQTk2wuAIQxLW0uAPxDhbv7hyhxO4h7U4cxcwvpHd0a2d29yWgLmEmAmAFU8OR0plOjk8i5nIhyAA5XSlTigpHiObKLbrcSIlG6dKwjzrTbbG6jByElAOFwAagRkNRohmAP6qSWpJyAHpeS4AJJGTBtaZCgDk+Bcpmc0mZJXgEDWwmw3AAHpF8Nw1t54OKKo55QsXPgIIgYFAJfKYI5sNhlV0cs7pPyXAAJAAqnoACgBlcQuP0QWawZSYfAzQUuSQRlzSEMAd08ymBkxcEETgwAjo4vGMXMj1vBuJx05CAl5EDzXQKEy5E8qC8EIIlpSbMFB20l4I4jC5sJRsEiXABhMoAJT9MYaRnIMDpKx8tZj0mkuBQ-IA5gtJI4KnUIMJedwYNxMJJRBHMzBeUqVWrNfAALSQefPwcQdWTGvw7jCLcwXIeBSgcGR1wXfljEQLdO2kZ9k24LcZGfTAABZMAAVkQNDxCgYtS0mQ9jzwgiy15DkBlvNZcwtHwQD+F1VnJLZgXEGjHDo2J-0A4CZmWJjlwqQoxi3Sg2weDxOKIGtmJgB1YEQT1JmHB4HAqJUKgcGSlxyDQ0nEaRiigeJEkBEyNIgCpuV05c1g2ViBOXQTuHwB5HDzfBxACND0h0wSXUJVlEHclldH85zIsJH0vAckKXDBCcABk6jWRpEEqapvESpLYks6yIsE35bP00lSp+RiejMzlBhBcFIRgWJIESRYYSXeFCRgUpqDYUlOGVOI-1KWgAG5ZhcAAeIZWyUMbuAZBkMk68RsGcSRYjqxAISRL5SXsikXB9ShhFPRBxFFeBYk6749Mq+EwE2NYlG28gZgddwYEqyigU4dYihe6EnOYhzKRGfBYhpSFtJcb6TJswT9tYzaAYMoyVFMvoBiuyF4cinIHvIJ7pBe0pCRrcqXQp6Ris6WGYb+yQfQgD7Ab2ljQeKcGHHe6RPuIGHqqx3GXUR2rMRexqMQmch2M7M1hDwxxhAqHbgBOqAHgARhmEF1QeJhaH0m68ekQzjIx8zYhLdxhZNnmupia3TZBbALZq7GkVtgKyRBtrvZctyEU87zfLSQqTcCyFQqRcP-bXRB1WkB4paxe9EAV5RE782STcgYRRCUUcZqTmHMRl8g5aPRXldV9WHnoYFMD1lwGENsOc8pjvqeNoqe+7zoae6e6jwL3nSkympTfqdLmladovsFn6GaL-ObEannbdF64wYhkAebpEBGQHZnTdc9lF7hv3Iq35GGtiHniNHouWtR83Yat09WqByK89HmBqAfq5N4LgnbiBdm7LGhIvbX3ZlfOOrl3LBx8tnWyglaQKlel3HImcS4pxlmnDOCdpAoLjjTE2VNKZ9ypoPHIdMVQQACIgAAQpMYUH93AzAWOnW2w8WrDT6gNWIQ0YijXGlNUBSAoBbiMnNBa0DSxxCdtQbgbBsTShOO8ZQCtXIZCUc1JQC1gFKJUWVUkEiVDSMkKUfRTo7KwPwomQ6x1ToQNSGsOA8BGFLDcOsT0-5ECtmIe4hAjCZiYVoLQDI1DKpb2-r9aQ-0o70wSZIF64d4lFCZh9ZODMsm83ScvEea8ckpJXn-WOS4tzrBesnC+rNbJbwJkTF6ZUKkuiqdIPJC4BaYy5HAuxvt7bHFOHPS4x8PriBgCWMAiBoQPDOPAVplV8ZpWUHk2pvSTI4OgVvDJqTITpDNujd+0Ccj2xWmtWI38A6INciHEhEcNhJKaTYNJXccGSSIbHH+RTC7F3rt8vSfcgVLP8EuSQrlEDrJ6ZbDe-TgYHXoYwlhbD7ZvRPlQ-ylT1hlOKTC92cLrk+wOr-NeACT5qMOCcFwCyxmkqUP-QB+BVFTKrHMmlc9QWkgjPgXFSgNmwpPjMbZ8LiWsSOkeU6502ixGoDnPZKNAU9EKavBl98MU1jYK-Y5dTTkJN+bzcl7hKUXJgOtIlLoEFBzucg9uqDlzoJec9SESrsFEM+YnJVFDlzeq0MsoykK+W4LqYSmsW8kXMNYeDelhqmUcHGYsLl1M-D6XSAoPASBzL8DQJrTCKBMIAE5NDaBAEIUQagzAWEUMoVQaBIYlHcJ4bwvgwV0xRlfWJpJKyIETAK92AQoARGgR1SEpQB0RHOmuecBIoYNNgcIXeM6kTaRzguhwS7yAflipsas-M10gB4tDYAMByBgGjjLaK261hGyVfujdz4y27pmA4AAAtDQkG7gp2sElQgeKa-h0xXu9RAAB1OYZpEz1LhMENw+4TprjmFudEoo8CdHhDgzh8x+iindIgJCMhOidvatwHtfasbjqMMOmDPMx2DqMJOjwMAVonzDfOxdedgMrvtfunBwQGhcbjhayK2oYCYA0gQB4toKjwf6FImYQRezxQw1ghMSRVnxXfhRxAeqXJYfPPAXD+H5QxAZImcDmZxAdKA0aAgfppCTCQEIujZ15YGWKEZyZ0zZkN2fEwYFjytPiHlpM+zSApB4eQsZlwDhMBGggC2uOLpAvBfcA5s67nIulFCI0SQYCm7cT0zhiLMh5POaC1XMglA2iGcixkJk3N1QJceXpV1y5oBzFWsGzZsREABB05FHBpReuGWCB07yopOIqZyMN6Qo38SpbCxlmQoEQCxYTE1xLygAgjfIGNhb6XitRey0ZPL6oCsLH0zVkrLgZtzdNpVjAV3Wr1b3o1hi9rKatbOVgGZkgEBzgeIFxojGVo-cQH9+Ac5Wt+vtd+k2d6OPM0QM+KT8Hob7sNGuaAAnHnAGgGAaZYwyN9KEy6VHCxZNIZiFJasH3lxA6nUx8nxDtlHJMic0V3tmeU+Q-AVDdOXQKzWPgCukH-O9xU4NmIvh3ukJh4lkAeSNuRTSP539FUoCD0AwaoDSOoNisGN-btvb8Xkec1RkINGYgM5BzzVjvsF267wGBrsmZ+I52wGDiHAPosgBAymEokxWz5ANeIMPOPBLM+J1soh-Wt7hCMCYHlQb1UcPjLHnO8vIq+r-ZrgDF8xwGv10RpLJGTeafN6K4eqqos2+nTG+3B0EcGoj5FBdaTMiOtD4SfSt72MGufBUQwP5+YxsMrHvvMXH42GfBCowrfBJ457PmaP+u44J+MFIQNReY3apMkLggovxc+qz-7EA0YzRWnlCWCY8YIV8xzmr0xGutcF7yU72ZuMS-07L6vrTFvpoa9aMJ1gd68W850HdYgP8XcIN3d7VPdChwd-skQ1I-cA8XAg9NgE0w9xAF9lwo8wgIgTBp41khVyZ1dbo89W038T4nMh0r54QzVMwg0HZS4+dadoMQ8a8YAAAxBmAgXnfnQjWBI3X-U3VIf-KvajE+YA+jUApjO3WyavP+UoJgxMFgsZDfEwKzA1deDFFweZTlCApvRdO3YgVdWIDvYAdBB+HvOHQSO9HmQfYfaGB+bZSfEAZjdwZ8UQWbPAwXXeTALw+CGNV8YufwyKJfAnFfcQy+UnSKBRWII4NQjQgAMlSOOBjV4P4PwH60EiyL4JSQEOp3IEmwF0ii0PED2RYNT0WDZw2nYkP0wETGPyKim15j+3UN0NKGSM6JYJUw0FP0eQfhjWfiUGfUPgb3MPKJGJ12LmiCiBiE1jGRlwMN9z5la3sPh0COCNn0hQiJdCiMJ1XzyOXEqLnyhVoI3nqIPxF2aNaJPy+3ogv2gGtBAW4FvwDQfzp2Py2LJz6O6KuEcJPjCO4IOP1W4IWI5TaC7iBP7xr2fCdkRIgAjEQ2hgcCShRO1CkRwIcAeIeDlXKObwRKdmhiyLy1dnfl305xNhvigINX4n2AeDHzPkANHmZINW9W9hvRU0dzmNVCR34n3gTV7y7l6lhzWIWRrAoOTSoL8Dpj4NGFxkqL2S6UOTRn30aLuJaOEN9lENI1iLX3p2kI+iyyIMs3WDVP8zpPXV+gIAiP3UkE1nRJAEVPwDxKVSGUWOhJAjhPXScKRKVFRKkRdMxODK3FxJAGPweGBO8ON3BIXXfyPGAz+Lb1MJBNJP5nOXAXflDXKLpLyUZKmAeCZXRXcBLJPi5Oz1V0f2lOf0oMHgVgMBaliDNAelHnECH3wHFDdLSHTWgizTUE1gADZNYUAmAAAOYtHQctCQMAGAKtKwGtWwNALs9MZYFwGGaAeCDEE6fUB4AAQU8FFBmFE1gBRyRFLBGn-TBQ3Tak3JEwQMmGTiQHVGvMGBcFFCQigGfC4WEG6RmWsHIHfMHmCJcDvI3Mbl2zmGcKx2EHrnEGwmEBAr+HvTVG8B3XvKgp3B-M8EiwQqQpQtvMJC3QwrWFmAAiwqWxLgYHTnfM3KH3IDnGfArm1GcAeFouQpvL8GCJmGImAywsYp901mwHVDcAQFchcA0hEnoqkuVGYtYu4HYpcE1jor8E3OwB5UQy1jUo-JBGgp-ITGwAQpYEIu4qgH4qR3T0Tj40wEEvkpQJpWgEQFkvcW4AAC94pB18JZKGEkROAlQTc58jAoBZKzN8AjItYIkABSWS6iji3Sh808J8h4SyMAMYWS-qJQFHDy+KVSriuUiyxHPAFHODE0SCnBVCEsLcDWFwPCmQIini4qkoTHVTLCrK+CM8TynSgqhihyzdRS5SpgRKuSpixy7yly9SqSkSMSGaQOAAYloEwBYFksgHvAeHms4C2tks0vwGxK3AIvThUpGsfKwGfNDBLHwkHyVHSsaosqcN8LssgtOswHOoCoTlktlHcEIjCOsH5TcE0pmUH3WETEQBUFkv0pwufCMpMrMo-I6pyu6pcFoHEAnN0tAoevWDsoAD4XAAAqI+AmyCyGmCpATgEufKu63Y0ImxPipwi4vi0Ii4+ysa8geZZynarSqReuTK7cxGvKkahG-bB4BYL8sAVazYOANm4+HEJEKmpwmm-5P7RhTBe6kEi4lAZWmOJqgfDWrWkISCta5UDaic02+WkErIrCkmwyiAYy5GxC9Gv4F9BCRACoMYBYZ8fYTgCuUQLqTsf8dKMM-arC2gaKzIaaP6kuDErEtEkAMaYqTcpgTCMO4ACO5c1AoOxDXAuOlwBOlwcJFOtOmwDOmOnE7O+Oqa0ywupc4u33TOsu8u3Om8l9L2n2i0T8+YEEZQeupDSC0O8Omu-66O8MhwCuj8pO6u7c2u4e-axuvOgugeqeoekAHu8PHOvOquxeyOku8MyMsezczWGKre9Ouu0urcUepuwq8QIM4OiqohX8rsWu58AtF+gtUS2Sy65HeK5Gqmm+7SlATAcmkcO+xOB+kKkuUaKaz+2fQ7EWwreAWSl686tKjKqaweqOles+veqal2t2j2-2rugYfWLqT+mDFGzCP2zuwOs+0MHgLDSaj8ghxoIhlwJgEhuYBoEIchyhgO7umhuYOhrhKm0Ip2dqvmrq+KFGtGgq0CkRz+cCki9CuKCi3uqa7+4amR1CmNLC3a-anq2SoSxykSsSjxSS6S26qawxga1E5SlG0ykayqr8mqh4eq6QYRgfNcrCxx6q2qsmtxqa7x78yTWbcgfxq+pmyFK20bGCmG+2+xzR28hW7oyy1wSCqxrWUS8SksaUcx1Bj8qxlimx7pFG4cka3R7SuJuGzc626G22nm8y6m5J5q6ykJhoKi2BluQW8R3KhC6R827wy2lJlqo0NqyCoWnp+2vpqa8p7m+2pC46mR-szNGqbNEATWZgFATWTQDgEAS6sYbNagGcvQeDSQTweQEgTYeQNAcCDcfkRIbAUSYiXkE5s5l9JgcQFGtCZ53cM5sBOYStBQezYcNQSrbgbAWwLQI5tQPBF9XCTWD5hQS5tQG5yCXkRMdypi7EKAXkdwOWUUZyiiMuWF8QeF2gQFlSPQUF8F6c0tXQNQDyVyF9BgcQZlhgRF4CZFtcW5tFjFryOYHF2bLsfF-CXkBl-AJlllll8l4FtAKliFtgDQIAA) - -```javascript -// In the `Feed` component -m(".post-list", posts.map(function(post) { - return m(Post, {key: post.id, post: post}) -})) - -// In the `Post` component -m(".comment-list", comments.map(function(comment) { - return m(Comment, {key: comment.id, comment: comment}) -})) -``` - -Note that for the comments, while it would technically work without keys in this case, it would similarly break if you were to add anything like nested comments or the ability to edit them, and you'd have to add keys to them. - -### Keeping collections of animated objects glitch-free - -On certain occasions, you might be wanting to animate lists, boxes, and similar. Let's start out with this simple code: - -```javascript -var colors = ["red", "yellow", "blue", "gray"] -var counter = 0 - -function getColor() { - var color = colors[counter] - counter = (counter + 1) % colors.length - return color -} - -function Boxes() { - var boxes = [] - - function add() { - boxes.push({color: getColor()}) - } - - function remove(box) { - var index = boxes.indexOf(box) - boxes.splice(index, 1) - } - - return { - view: function() { - return [ - m("button", {onclick: add}, "Add box, click box to remove"), - m(".container", boxes.map(function(box, i) { - return m(".box", - { - "data-color": box.color, - onclick: function() { remove(box) }, - }, - m(".stretch") - ) - })), - ] - }, - } -} -``` - -It looks pretty innocent, but [try a live example](https://flems.io/#0=N4IgzgxgTg9gNnEAuA2gBgDRoLoZAMwEs4BTMZFUAOwEMBbE5EAOgAsAXOxPCGK9kvyYgAvhmr1GSFgCtyPPgKHSAbjSgACXnBhQwGgLwaUAHRBQSAEzMYNZgJ4kEMAO427IAEZwAriXdmAOZQNPZm2CZUapq8PvwkmkZokZH4cRDshHwagSTsAMLwugAUAJQawCns0VpFibU6eiix8VARVCbsLQL1xd0JGgDUGgCM5QCkDbpgzKRUgeysVRbsPlBUU1CRIilUaVQZWRsAQjAAHmRlFVU1nudkhsbtVfuH2TSWlleVHeyddxcZgAHHxgVjFYDaXRIHJ5QqNMoiUpVHYdX6vTLZCx0GAqEjFAHlH6dTo1QhUSwkM6PAFkZjkylnADy+AJ52Rv3+9xmYCBcEIEHxDKptjGKN2nRWaw2xL+1UIJBcMIxR2+VRJ7Cl62M6rlnToxTMnh87HYfHcwD4EH5EAA1jCPpYxB4AIKfDQA2zWgW2j3nDRmjTY3H+EClDC6jUGszMXj8GjkhLuWkzOg0IHFFV8NlnWyEImRvWavLSjTRlgAmyFjWdWU1oskkCWGjsGgAWihW2QfrOsbqEc5DaLVpt9o0Wao3yDJBxeJz5TE1aLi8H9f1hpYYGLXSWYaXnQ5a-YSPDheecpXnVRqMidGYOLi7GKlhgEB8DH4zDulnstlOgNKEAFDoIFiASJhPBoTwnCA8AnBIN4qHIaQAGYAHYkBGURxBAWgGCYWMwHkEA4yUdgIJgH8KnHRQ23wehiHsGEXSgQgaDgWwwBoJC2zABJCHwABuDQbyoPt40TTQfg0DRLEIXk4FCZVSDOQTIhk-AVLonQlSDVwNBcEIgTUqhRIAAVtEh7HwEIGH0CB7GtEhrg2DQ0EmYAy3UQJyRhAAmGdhNYEhCECDh-MCgzCEsRYIroYTNW4sB8F0OgYVgVsBGKNsABYAFZKUCUphNRGS8vc6i0ygHyqBhNAgpCsL2BhAA2SKXGi2KNDa+KAxCJCUqgNK9My-F8sK4qRPU0Y0AqzyqpquKGtC8KNAC3qOpi1glr6pLBuGjKW3xXKCpIIqSu2XYv39aSNGClbmo0HLAumzauue+Lps0qk6rcjQaBNGATJkhbfLWl7TKurcVggVgXJk7jCDTTFaq0RzSDW-QSBoPi23JNsYBNNz9CoVL2I0ckiCoQgBGBv0oEpKAYRGIFqTAeBoo9RS7RM0SUGbVsOzqAwLEsbANGYaG8lh6jILtYJCYpGEAGJ8Fmi6qH5lt207AxHGcFxxcl7cZc8uXbQVuJLBV-AIHqqbNYFnXhe8PwjalndZZoeXYCtlXZqEh2tcF3XglCd2Tbhs3vYt32lY0ZWAA5k5K2C+NIRDkJAPyRiQFCUNEXAQH5KhbWQyhcMkJg6Bp1hWO4EA1kQaQOHYIEwCQAB6Tu4iBC2+zoTua8WeuzL85g0GYHKh9r+v73JZg5Fg9h7CBKRwGgQggXIkRsBEIA). In that example, click to make a couple boxes, pick a box, and follow its size. We want the size and spin to be tied to the box (denoted by color) and not the position in the grid. You'll notice that instead, the size ends up jumping suddenly up, but it stays constant with location. This means we need to give them keys. - -In this case, giving them unique keys is pretty easy: just create a counter that you increment each time you read it. - -```diff - var colors = ["red", "yellow", "blue", "gray"] - var counter = 0 - - function getColor() { - var color = colors[counter] - counter = (counter + 1) % colors.length - return color - } - - function Boxes() { - var boxes = [] - var nextKey = 0 - - function add() { -- boxes.push({color: getColor()}) -+ var key = nextKey -+ nextKey++ -+ boxes.push({key: key, color: getColor()}) - } - - function remove(box) { - var index = boxes.indexOf(box) - boxes.splice(index, 1) - } - - return { - view: function() { - return [ - m("button", {onclick: add}, "Add box, click box to remove"), - m(".container", boxes.map(function(box, i) { - return m(".box", - { -+ key: box.key, - "data-color": box.color, - onclick: function() { remove(box) }, - }, - m(".stretch") - ) - })), - ] - }, - } - } -``` - -[Here's a fixed demo for you to play with, to see how it works differently.](https://flems.io/#0=N4IgzgxgTg9gNnEAuA2gBgDRoLoZAMwEs4BTMZFUAOwEMBbE5EAOgAsAXOxPCGK9kvyYgAvhmr1GSFgCtyPPgKHSAbjSgACXnBhQwGgLwaUAHRBQSAEzMYNZgJ4kEMAO427IAEZwAriXdmAOZQNPZm2CZUapq8PvwkmkZokZH4cRDshHwagSTsAMLwugAUAJQawCns0VpFibU6eiix8VARVCbsLQL1xd0JGgDUGgCM5QCkDbpgzKRUgeysVRbsPlBUU1CRIilUaVQZWRsAQjAAHmRlFVU1nudkhsbtnTVUJGfsANIk9o-JHR12PtDtkaJZLFdKoCXuoNABrH6PN4fb5haHsZFfH6DQZVTp3C4zAAOPjArGKwAR9iQ8J+tm0uhpuQKdTKIlKVR2AM6wMy2QsdBgKhIxQJ5ShnRhmkIVEs70eBLIzBlcrOAHl8KLzhz0YqZmAiXBCBARSr3rYxpzdp0VmsNhL2C9CCQXDTeUdIXjHexbetjF7JZximZPD52Ow+O5gHwIEaIHCaWDLGIPABBcEaAn0uNwzPnDQRjQCoX+EClDAB710YMsXj8GgyhLuPXMOg0InFd18LVnWyEcWVwO+jbVszMAk2QfezoOwNz71UmkE5hUivo+dzkCWGjsGgAWgZW2QebOzEPa430+9MZzbvSfKokKLJEFwp75TEU8Dn-XV86o5YMAfTyCAljLL92B1S92XLStnm9H9Oi5LlIjoVsYDidhiksGAIB8Bh+HHGBLHsWxTkJUoQAUOgiWIBImE8GhPCcKjwCcEgQSochpAAFhGJARlEcQQFoBgmDPMB5BAOslHYBjiN+YANHwRQ93wehiGpDRUygQgaDgWwwBoLi9zABJCHwABuDQUKoM9FAbN5NChDQNEsQgDTgUI3VIM5LMiVz8F8tSdFdItXA0FwQiJfyqFsgABKl8BCBh9AgexYxIa4Ng0NBJiUtsoECGUaQAJhfazWBIQhAg4MqKsiwhLEWeq6Gsn1jLAFSoDoGlYF3ARij3HiAFY5UCUprK5VyRryioNEK4qqBpNBKuq2r2BpAA2BqXCalqNB2tqCxCLjut68KBpFUbxsmmyAtGNA5oK9QltataarqjRyuOvbmtYd6Ts686+pgK6hpukgJqm7ZdiIs5stcqrPs2jQeIqh6-oO9G2oeoL3hW3KNBoMMYFi1zFpK76MbiuGgJWUDEeJqhCDbB8aXSzLvv0EgaDMvcZT3DD2Fy-QqF0Ns4A0GUiBZgRybzKA5SgGkRiJBGwHgJrMy8+NYtslBt13A86gMCxLGwDRmHpkDWHmxj42CDDZRpABifAnphqhDZ3fdDwMRxnBcS3reAro7aUh24SduJLDd-AIFW+7vaNv3Te8PwQ5t8P7ZoR3YFjt2nqs5OfeN-3glCLOw8ZyO8+jguXY0V2AA426m1izNITjuJAUqW6QHiW9EXAQCNKg4W4ygRMkJg6EIRZdO4EA1kQaQOHYIkwCQAB6He4iJaP7LoHf58X4h4tK5g0GYHjT4X1gl9bGVmDkVj2HsIkpHAaBCCJOSRDYBEEAA) - -### Reinitializing views with single-child keyed fragments - -When you're dealing with stateful entities in models and such, it's often useful to render model views with keys. Suppose you have this layout: - -```javascript -function Layout() { - // ... -} - -function Person() { - // ... -} - -m.route(rootElem, "/", { - "/": Home, - "/person/:id": { - render: function() { - return m(Layout, - m(Person, {id: m.route.param("id")}) - ) - } - }, - // ... -}) -``` - -Chances are, your `Person` component probably looks something like this: - -```javascript -function Person(vnode) { - var personId = vnode.attrs.id - var state = "pending" - var person, error - - m.request("/api/person/:id", {params: {id: personId}}).then( - function(p) { person = p; state = "ready" }, - function(e) { error = e; state = "error" } - ) - - return { - view: function() { - if (state === "pending") return m(LoadingIcon) - if (state === "error") { - return error.code === 404 - ? m(".person-missing", "Person not found.") - : m(".person-error", - "An error occurred. Please try again later" - ) - } - return m(".person", - m(m.route.Link, - { - class: "person-edit", - href: "/person/:id/edit", - params: {id: personId}, - }, - "Edit" - ), - m(".person-name", "Name: ", person.name), - // ... - ) - } - } -} -``` - -Say, you added a way to link to other people from this component, like maybe adding a "manager" field. - -```javascript -function Person(vnode) { - // ... - - return { - view: function() { - // ... - return m(".person", - m(m.route.Link, - { - class: "person-edit", - href: "/person/:id/edit", - params: {id: personId}, - }, - "Edit" - ), - m(".person-name", person.name), - // ... - m(".manager", - "Manager: ", - m(m.route.Link, - { - href: "/person/:id", - params: {id: person.manager.id} - }, - person.manager.name - ) - ), - // ... - ) - } - } -} -``` - -Assuming the person's ID was `1` and the manager's ID was `2`, you'd switch from `/person/1` to `/person/2`, remaining on the same route. But since you used [the route resolver `render` method](route.md#routeresolverrender), the tree was retained and you just changed from `m(Layout, m(Person, {id: "1"}))` to `m(Layout, m(Person, {id: "2"}))`. In this, the `Person` didn't change, and so it doesn't reinitialize the component. But for our case, this is bad, because it means the new user isn't being fetched. This is where keys come in handy. We could change the route resolver to this to fix it: - -```javascript -m.route(rootElem, "/", { - "/": Home, - "/person/:id": { - render: function() { - return m(Layout, - // Wrap it in an array in case we add other elements later on. - // Remember: fragments must contain either only keyed children - // or no keyed children. - [m(Person, - {id: m.route.param("id"), key: m.route.param("id")} - )] - ) - } - }, - // ... -}) -``` - -### Common gotchas - -There's several common gotchas that people run into with keys. Here's some of them, to help you understand why they don't work. - -#### Wrapping keyed elements - -These two snippets don't work the same way: - -```javascript -users.map(function(user) { - return m(".wrapper", [ - m(User, {user: user, key: user.id}) - ]) -}) - -users.map(function(user) { - return m(".wrapper", {key: user.id}, [ - m(User, {user: user}) - ]) -}) -``` - -The first binds the key to the `User` component, but the outer fragment created by `users.map(...)` is entirely unkeyed. Wrapping a keyed element this way doesn't work, and the result could be anything ranging from extra requests each time the list is changed to inner form inputs losing their state. The resulting behavior would similar to the [post list's broken example](#linking-model-data-to-views), but without the issue of state corruption. - -The second binds it to the `.wrapper` element, ensuring the outer fragment *is* keyed. This does what you likely wanted to do all along, and removing a user won't pose any issues with the state of other user instances. - -#### Putting keys inside the component - -Suppose, in the [person example](#reinitializing-views-with-single-child-keyed-fragments), you did this instead: - -```javascript -// AVOID -function Person(vnode) { - var personId = vnode.attrs.id - // ... - - return { - view: function() { - return m.fragment({key: personId}, - // what you previously had in the view - ) - } - } -} -``` - -This won't work, because the key doesn't apply to the component as a whole. It just applies to the view, and so you aren't re-fetching the data like you were hoping for. - -Prefer the solution used there, putting the key in the vnode *using* the component rather than inside the component itself. - -```javascript -// PREFER -return [m(Person, - {id: m.route.param("id"), key: m.route.param("id")} -)] -``` - -#### Keying elements unnecessarily - -It's a common misconception that keys are themselves identities. Mithril.js enforces for all fragments that their children must either all have keys or all lack keys, and will throw an error if you forget this. Suppose you have this layout: - -```javascript -m(".page", - m(".header", {key: "header"}), - m(".body"), - m(".footer"), -) -``` - -This obviously will throw, as `.header` has a key and `.body` and `.footer` both lack keys. But here's the thing: you don't need keys for this. If you find yourself using keys for things like this, the solution isn't to add keys, but to remove them. Only add them if you really, *really* need them. Yes, the underlying DOM nodes have identities, but Mithril.js doesn't need to track those identities to correctly patch them. It practically never does. Only with lists where each entry has some sort of associated state Mithril.js doesn't itself track, whether it be in a model, in a component, or in the DOM itself, do you need keys. - -One last thing: avoid static keys. They're always unnecessary. If you're not computing your `key` attribute, you're probably doing something wrong. - -Note that if you really need a single keyed element in isolation, [use a single-child keyed fragment](#reinitializing-views-with-single-child-keyed-fragments). It's just an array with a single child that's a keyed element, like `[m("div", {key: foo})]`. - -#### Mixing key types - -Keys are read as object property names. This means `1` and `"1"` are treated identically. If you want to keep your hair, don't mix key types if you can help it. If you do, you could wind up with duplicate keys and unexpected behavior. - -```javascript -// AVOID -var things = [ - {id: "1", name: "Book"}, - {id: 1, name: "Cup"}, -] -``` - -If you absolutely must and you have no control over this, use a prefix denoting its type so they remain distinct. - -```javascript -things.map(function(thing) { - return m(".thing", - {key: (typeof thing.id) + ":" + thing.id}, - // ... - ) -}) -``` - -##### Hiding keyed elements with holes - -Holes like `null`, `undefined`, and booleans are considered unkeyed vnodes, so code like this won't work: - -```javascript -// AVOID -things.map(function(thing) { - return shouldShowThing(thing) - ? m(Thing, {key: thing.id, thing: thing}) - : null -}) -``` - -Instead, filter the list before returning it, and Mithril.js will do the right thing. Most of the time, [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) is precisely what you need and you should definitely try it out. - -```javascript -// PREFER -things - .filter(function(thing) { return shouldShowThing(thing) }) - .map(function(thing) { - return m(Thing, {key: thing.id, thing: thing}) - }) -``` - -#### Duplicate keys - -Keys for fragment items *must* be unique, or otherwise, it's unclear and ambiguous what key is supposed to go where. You may also have issues with elements not moving around like they're supposed to. - -```javascript -// AVOID -var things = [ - {id: "1", name: "Book"}, - {id: "1", name: "Cup"}, -] -``` - -Mithril.js uses an empty object to map keys to indices to know how to properly patch keyed fragments. When you have a duplicate key, it's no longer clear where that element moved to, and so Mithril.js will break in that circumstance and do unexpected things on update, especially if the list changed. Distinct keys are required for Mithril.js to properly connect old to new nodes, so you must choose something locally unique to use as a key. diff --git a/docs/layout.html b/docs/layout.html deleted file mode 100644 index 3e0fe4cd..00000000 --- a/docs/layout.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - Mithril.js - - - - - - - - -
-
- -

Mithril [archive-docs]

- -
-
-
-
- [body] -
- License: MIT. © Leo Horie. -
-
- - - - - - - - diff --git a/docs/learning-mithril.md b/docs/learning-mithril.md deleted file mode 100644 index ae628151..00000000 --- a/docs/learning-mithril.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# Learning Resources - -Links to Mithril.js learning content: - -- [Mithril 0-60](https://vimeo.com/showcase/5584199) diff --git a/docs/lifecycle-methods.md b/docs/lifecycle-methods.md deleted file mode 100644 index e95c0e90..00000000 --- a/docs/lifecycle-methods.md +++ /dev/null @@ -1,221 +0,0 @@ - - -# Lifecycle methods - -- [Usage](#usage) -- [The DOM element lifecycle](#the-dom-element-lifecycle) -- [oninit](#oninit) -- [oncreate](#oncreate) -- [onupdate](#onupdate) -- [onbeforeremove](#onbeforeremove) -- [onremove](#onremove) -- [onbeforeupdate](#onbeforeupdate) -- [Avoid anti-patterns](#avoid-anti-patterns) - ---- - -### Usage - -[Components](components.md) and [virtual DOM nodes](vnodes.md) can have lifecycle methods, also known as *hooks*, which are called at various points during the lifetime of a DOM element. - -```javascript -// Sample hook in component -var ComponentWithHook = { - oninit: function(vnode) { - console.log("initialize component") - }, - view: function() { - return "hello" - } -} - -// Sample hook in vnode -function initializeVnode() { - console.log("initialize vnode") -} - -m(ComponentWithHook, {oninit: initializeVnode}) -``` - -All lifecyle methods receive the vnode as their first arguments, and have their `this` keyword bound to `vnode.state`. - -Lifecycle methods are only called as a side effect of a [`m.render()`](render.md) call. They are not called if the DOM is modified outside of Mithril. - ---- - -### The DOM element lifecycle - -A DOM element is typically created and appended to the document. It may then have attributes or child nodes updated when a UI event is triggered and data is changed; and the element may alternatively be removed from the document. - -After an element is removed, it may be temporarily retained in a memory pool. The pooled element may be reused in a subsequent update (in a process called *DOM recycling*). Recycling an element avoids incurring the performance cost of recreating a copy of an element that existed recently. - ---- - -### oninit - -The `oninit(vnode)` hook is called before a vnode is touched by the virtual DOM engine. `oninit` is guaranteed to run before its DOM element is attached to the document, and it is guaranteed to run on parent vnodes before their children, but it does not offer any guarantees regarding the existence of ancestor or descendant DOM elements. You should never access the `vnode.dom` from the `oninit` method. - -This hook does not get called when an element is updated, but it does get called if an element is recycled. - -Like in other hooks, the `this` keyword in the `oninit` callback points to `vnode.state`. - -The `oninit` hook is useful for initializing component state based on arguments passed via `vnode.attrs` or `vnode.children`. - -```javascript -function ComponentWithState() { - var initialData - return { - oninit: function(vnode) { - initialData = vnode.attrs.data - }, - view: function(vnode) { - return [ - // displays data from initialization time: - m("div", "Initial: " + initialData), - // displays current data: - m("div", "Current: " + vnode.attrs.data) - ] - } - } -} - -m(ComponentWithState, {data: "Hello"}) -``` - -You should not modify model data synchronously from this method. Since `oninit` makes no guarantees regarding the status of other elements, model changes created from this method may not be reflected in all parts of the UI until the next render cycle. - ---- - -### oncreate - -The `oncreate(vnode)` hook is called after a DOM element is created and attached to the document. `oncreate` is guaranteed to run at the end of the render cycle, so it is safe to read layout values such as `vnode.dom.offsetHeight` and `vnode.dom.getBoundingClientRect()` from this method. - -This hook does not get called when an element is updated. - -Like in other hooks, the `this` keyword in the `oncreate` callback points to `vnode.state`. DOM elements whose vnodes have an `oncreate` hook do not get recycled. - -The `oncreate` hook is useful for reading layout values that may trigger a repaint, starting animations and for initializing third party libraries that require a reference to the DOM element. - -```javascript -var HeightReporter = { - oncreate: function(vnode) { - console.log("Initialized with height of: ", vnode.dom.offsetHeight) - }, - view: function() {} -} - -m(HeightReporter, {data: "Hello"}) -``` - -You should not modify model data synchronously from this method. Since `oncreate` is run at the end of the render cycle, model changes created from this method will not be reflected in the UI until the next render cycle. - ---- - -### onupdate - -The `onupdate(vnode)` hook is called after a DOM element is updated, while attached to the document. `onupdate` is guaranteed to run at the end of the render cycle, so it is safe to read layout values such as `vnode.dom.offsetHeight` and `vnode.dom.getBoundingClientRect()` from this method. - -This hook is only called if the element existed in the previous render cycle. It is not called when an element is created or when it is recycled. - -DOM elements whose vnodes have an `onupdate` hook do not get recycled. - -The `onupdate` hook is useful for reading layout values that may trigger a repaint, and for dynamically updating UI-affecting state in third party libraries after model data has been changed. - -```javascript -function RedrawReporter() { - var count = 0 - return { - onupdate: function() { - console.log("Redraws so far: ", ++count) - }, - view: function() {} - } -} - -m(RedrawReporter, {data: "Hello"}) -``` - ---- - -### onbeforeremove - -The `onbeforeremove(vnode)` hook is called before a DOM element is detached from the document. If a Promise is returned, Mithril.js only detaches the DOM element after the promise completes. - -This hook is only called on the DOM element that loses its `parentNode`, but it does not get called in its child elements. - -Like in other hooks, the `this` keyword in the `onbeforeremove` callback points to `vnode.state`. DOM elements whose vnodes have an `onbeforeremove` hook do not get recycled. - -```javascript -var Fader = { - onbeforeremove: function(vnode) { - vnode.dom.classList.add("fade-out") - return new Promise(function(resolve) { - setTimeout(resolve, 1000) - }) - }, - view: function() { - return m("div", "Bye") - }, -} -``` - ---- - -### onremove - -The `onremove(vnode)` hook is called before a DOM element is removed from the document. If a `onbeforeremove` hook is also defined, the `onremove` hook runs after the promise returned from `onbeforeremove` is completed. - -This hook is called on any element that is removed from the document, regardless of whether it was directly detached from its parent or whether it is a child of another element that was detached. - -Like in other hooks, the `this` keyword in the `onremove` callback points to `vnode.state`. DOM elements whose vnodes have an `onremove` hook do not get recycled. - -The `onremove` hook is useful for running clean up tasks. - -```javascript -function Timer() { - var timeout = setTimeout(function() { - console.log("timed out") - }, 1000) - - return { - onremove: function() { - clearTimeout(timeout) - }, - view: function() {} - } -} -``` - ---- - -### onbeforeupdate - -The `onbeforeupdate(vnode, old)` hook is called before a vnode is diffed in a update. If this function is defined and returns false, Mithril.js prevents a diff from happening to the vnode, and consequently to the vnode's children. - -This hook by itself does not prevent a virtual DOM subtree from being generated unless the subtree is encapsulated within a component. - -Like in other hooks, the `this` keyword in the `onbeforeupdate` callback points to `vnode.state`. - -This hook is useful to reduce lag in updates in cases where there is a overly large DOM tree. - ---- - -### Avoid anti-patterns - -Although Mithril.js is flexible, some code patterns are discouraged: - -#### Avoid premature optimizations - -You should only use `onbeforeupdate` to skip diffing as a last resort. Avoid using it unless you have a noticeable performance issue. - -Typically performance problems that can be fixed via `onbeforeupdate` boil down to one large array of items. In this context, typically "large" means any array that contains a large number of nodes, be it in a wide spread (the infamous 5000 row table), or in a deep, dense tree. - -If you do have a performance issue, first consider whether the UI presents a good user experience and change it if it doesn't. For example, it's highly unlikely that a user would ever sift through 5000 rows of raw table data, and highly likely that it would be easier for a user to use a search feature that returns only the top few most relevant items. - -If a design-based solution is not feasible, and you must optimize a UI with a large number of DOM element, apply `onbeforeupdate` on the parent node of the largest array and re-evaluate performance. In the vast majority of cases, a single check should be sufficient. In the rare case that it is not, rinse and repeat, but you should be increasingly wary of each new `onbeforeupdate` declaration. Multiple `onbeforeupdate`s are a code smell that indicates prioritization problems in the design workflow. - -Avoid applying the optimization to other areas of your application "just-in-case". Remember that, generally speaking, more code incurs a higher maintenance cost than less code, and `onbeforeupdate` related bugs can be especially difficult to troubleshoot if you rely on object identity for its conditional checks. - -Again, **the `onbeforeupdate` hook should only be used as a last resort.** diff --git a/docs/logo.svg b/docs/logo.svg deleted file mode 100644 index a86fb2b7..00000000 --- a/docs/logo.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/migration-v02x.md b/docs/migration-v02x.md deleted file mode 100644 index 8681c0f9..00000000 --- a/docs/migration-v02x.md +++ /dev/null @@ -1,935 +0,0 @@ -# Migrating from v0.2.x - -v1.x and v2.x are largely API-compatible with v0.2.x, but there are some breaking changes. Migrating to v2.x is nearly identical, so the notes below apply mostly to both. - -If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations. - -- [`m.prop` removed](#mprop-removed) -- [`m.component` removed](#mcomponent-removed) -- [`m.withAttr` removed](#mwithattr-removed) -- [`m.version` removed](#mversion-removed) -- [`config` function](#config-function) -- [Changes in redraw behaviour](#changes-in-redraw-behaviour) - - [No more redraw locks](#no-more-redraw-locks) - - [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers) - - [Synchronous redraw changed](#synchronous-redraw) - - [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed) -- [Component `controller` function](#component-controller-function) -- [Component arguments](#component-arguments) -- [Component vnode children](#component-children) -- [DOM vnode children](#dom-vnode-children) -- [Keys](#keys) -- [`view()` parameters](#view-parameters) -- [Passing components to `m()`](#passing-components-to-m) -- [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute) -- [`m.route.mode`](#mroutemode) -- [`m.route()` and anchor tags](#mroute-and-anchor-tags) -- [Path templates](#path-templates) -- [Reading/writing the current route](#readingwriting-the-current-route) -- [Accessing route params](#accessing-route-params) -- [Building/Parsing query strings](#buildingparsing-query-strings) -- [Preventing unmounting](#preventing-unmounting) -- [Run code on component removal](#run-code-on-component-removal) -- [`m.request`](#mrequest) -- [Default `responseType` for `m.request`](#default-responsetype-for-mrequest) -- [`m.deferred` removed](#mdeferred-removed) -- [`m.sync` removed](#msync-removed) -- [`xlink` namespace required](#xlink-namespace-required) -- [Nested arrays in views](#nested-arrays-in-views) -- [`vnode` equality checks](#vnode-equality-checks) - ---- - -## `m.prop` removed - -In v2.x, `m.prop()` was converted into now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md). - -### v0.2.x - -```javascript -var m = require("mithril") - -var num = m.prop(1) -``` - -### v2.x - -```javascript -var m = require("mithril") -var prop = require("mithril/stream") - -var num = prop(1) -var doubled = num.map(function(n) { return n * 2 }) -``` - ---- - -## `m.component` removed - -In v0.2.x components could be created using either `m(Component)` or `m.component(Component)`. v2.x only support `m(Component)`. - -### v0.2.x - -```javascript -// These are equivalent -m.component(Component) -m(Component) -``` - -### v2.x - -```javascript -m(Component) -``` - ---- - -## `m.withAttr` removed - -In v0.2.x event listeners could use `oninput: m.withAttr("value", func)` and similar. In v2.x, just read them directly from the event's target. It synergized well with `m.prop`, but since that was removed in favor of an out of core solution and v1.x didn't see similar broad, idiomatic usage of streams, `m.withAttr` lost most of its usefulness. - -### v0.2.x - -```javascript -var value = m.prop("") - -// In your view -m("input[type=text]", { - value: value(), - oninput: m.withAttr("value", value), -}) -``` - -### v2.x - -```javascript -var value = "" - -// In your view -m("input[type=text]", { - value: value, - oninput: function (ev) { value = ev.target.value }, -}) -``` - ---- - -## `m.version` removed - -It served little use in general, and you can always add it back yourself. You should prefer feature detection for knowing what features are available, and the v2.x API is designed to better enable this. - ---- - -## `config` function - -In v0.2.x Mithril.js provided a single lifecycle method, `config`. v2.x provide much more fine-grained control over the lifecycle of a vnode. - -### v0.2.x - -```javascript -m("div", { - config: function(element, isInitialized) { - // runs on each redraw - // isInitialized is a boolean representing if the node has been added to the DOM - } -}) -``` - -### v2.x - -More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md). - -```javascript -m("div", { - // Called before the DOM node is created - oninit: function(vnode) { /*...*/ }, - // Called after the DOM node is created - oncreate: function(vnode) { /*...*/ }, - // Called before the node is updated, return false to cancel - onbeforeupdate: function(vnode, old) { /*...*/ }, - // Called after the node is updated - onupdate: function(vnode) { /*...*/ }, - // Called before the node is removed, return a Promise that resolves when - // ready for the node to be removed from the DOM - onbeforeremove: function(vnode) { /*...*/ }, - // Called before the node is removed, but after onbeforeremove calls done() - onremove: function(vnode) { /*...*/ } -}) -``` - -If available the DOM-Element of the vnode can be accessed at `vnode.dom`. - ---- - -## Changes in redraw behaviour - -Mithril.js' rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ: - -### No more redraw locks - -In v0.2.x, Mithril.js allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v2.x without replacement. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change. - -### Cancelling redraw from event handlers - -`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`. - -#### v0.2.x - -```javascript -m("div", { - onclick: function(e) { - m.redraw.strategy("none") - } -}) -``` - -#### v2.x - -```javascript -m("div", { - onclick: function(e) { - e.redraw = false - } -}) -``` - -### Synchronous redraw changed - -In v0.2.x it was possible to force Mithril.js to redraw immediately by passing a truthy value to `m.redraw()`. In v2.x, this functionality was split into two different methods for clarity. - -#### v0.2.x - -```javascript -m.redraw(true) // redraws immediately & synchronously -``` - -#### v2.x - -```javascript -m.redraw() // schedules a redraw on the next requestAnimationFrame tick -m.redraw.sync() // invokes a redraw immediately and waits for it to complete -``` - -### `m.startComputation`/`m.endComputation` removed - -They are considered anti-patterns and have a number of problematic edge cases, so they were removed without replacement in v2.x. - ---- - -## Component `controller` function - -In v2.x, there is no more `controller` property in components - use `oninit` instead. - -### v0.2.x - -```javascript -m.mount(document.body, { - controller: function() { - var ctrl = this - - ctrl.fooga = 1 - }, - - view: function(ctrl) { - return m("p", ctrl.fooga) - } -}) -``` - -### v2.x - -```javascript -m.mount(document.body, { - oninit: function(vnode) { - vnode.state.fooga = 1 - }, - - view: function(vnode) { - return m("p", vnode.state.fooga) - } -}) - -// OR - -m.mount(document.body, { - // this is bound to vnode.state by default - oninit: function(vnode) { - this.fooga = 1 - }, - - view: function(vnode) { - return m("p", this.fooga) - } -}) -``` - ---- - -## Component arguments - -Arguments to a component in v2.x must be an object, simple values like `String`/`Number`/`Boolean` will be treated as text children. Arguments are accessed within the component by reading them from the `vnode.attrs` object. - -### v0.2.x - -```javascript -var Component = { - controller: function(options) { - // options.fooga === 1 - }, - - view: function(ctrl, options) { - // options.fooga === 1 - } -} - -m("div", m.component(Component, { fooga: 1 })) -``` - -### v2.x - -```javascript -var Component = { - oninit: function(vnode) { - // vnode.attrs.fooga === 1 - }, - - view: function(vnode) { - // vnode.attrs.fooga === 1 - } -} - -m("div", m(Component, { fooga: 1 })) -``` - ---- - -## Component vnode children - -In v0.2.x, component vnode children were not normalized, just passed as extra arguments, and they were not flattened, either. (Internally, it was just returning a partially applied component that was diffed based on the component being partially applied.) In v2.x, component vnode children are passed via `vnode.children` as a resolved array of children, but like v0.2.x, the individual children themselves are not normalized, nor is the children array flattened. - -### v0.2.x - -```javascript -var Component = { - controller: function(value, renderProp) { - // value === "value" - // typeof renderProp === "function" - }, - - view: function(ctrl, value, renderProp) { - // value === "value" - // typeof renderProp === "function" - } -} - -m("div", m.component(Component, "value", function(key) { return "child" })) -``` - -### v2.x - -```javascript -var Component = { - oninit: function(vnode) { - // vnode.children[0] === "value" - // typeof vnode.children[1] === "function" - }, - - view: function(vnode) { - // vnode.children[0] === "value" - // typeof vnode.children[1] === "function" - }, -} - -m("div", m(Component, "value", function(key) { return "child" })) -``` - ---- - -## DOM vnode children - -In v0.2.x, the children of DOM nodes were represented literally with no normalization aside from using the children directly if only a single array child is present. It returned a structure more like this, with the strings represented literally. - -```javascript -m("div", "value", ["nested"]) - -// Becomes: -{ - tag: "div", - attrs: {}, - children: [ - "value", - ["nested"], - ] -} -``` - -In v2.x, children of DOM vnodes are normalized to objects of a single consistent structure. - -```javascript -m("div", "value", ["nested"]) - -// Becomes roughly: -{ - tag: "div", - attrs: null, - children: [ - {tag: "#", children: "value"}, - {tag: "[", children: [ - {tag: "#", children: "nested"}, - ]}, - ] -} -``` - -If only a single text child is present on a DOM vnode, it instead sets `text` to that value. - -```javascript -m("div", "value") - -// Becomes roughly: -{ - tag: "div", - attrs: null, - text: "", - children: undefined, -} -``` - -See [the vnode docs](vnodes.md) for more details on the v2.x vnode structure and how things are normalized. - -*Most of the v2.x vnode properties here are omitted for brevity.* - ---- - -## Keys - -In v0.2.x, you could mix keyed and unkeyed vnodes freely. - -In v2.x, children lists of both fragments and elements must be either all keyed or all unkeyed. Holes are considered unkeyed for the purposes of this check, too - it no longer ignores them. - -If you need to work around it, use the idiom of a fragment containing a single vnode, like `[m("div", {key: whatever})]`. - ---- - -## `view()` parameters - -In v0.2.x view functions are passed a reference to the `controller` instance and (optionally) any options passed to the component. In v2.x they are passed **only** the `vnode`, exactly like the `controller` function. - -### v0.2.x - -```javascript -m.mount(document.body, { - controller: function() {}, - - view: function(ctrl, options) { - // ... - } -}) -``` - -### v2.x - -```javascript -m.mount(document.body, { - oninit: function(vnode) { - // ... - }, - - view: function(vnode) { - // Use vnode.state instead of ctrl - // Use vnode.attrs instead of options - } -}) -``` - ---- - -## Passing components to `m()` - -In v0.2.x you could pass components as the second argument of `m()` w/o any wrapping required. To help with consistency in v2.x they must always be wrapped with a `m()` invocation. - -### v0.2.x - -```javascript -m("div", Component) -``` - -### v2.x - -```javascript -m("div", m(Component)) -``` - ---- - -## Passing vnodes to `m.mount()` and `m.route()` - -In v0.2.x, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object. - -In v2.x, components are required instead in both cases. - -### v0.2.x - -```javascript -m.mount(element, m('i', 'hello')) -m.mount(element, m(Component, attrs)) - -m.route(element, '/', { - '/': m('b', 'bye') -}) -``` - -### v2.x - -```javascript -m.mount(element, {view: function () {return m('i', 'hello')}}) -m.mount(element, {view: function () {return m(Component, attrs)}}) - -m.route(element, '/', { - '/': {view: function () {return m('b', 'bye')}} -}) -``` - ---- - -## `m.route.mode` - -In v0.2.x the routing mode could be set by assigning a string of `"pathname"`, `"hash"`, or `"search"` to `m.route.mode`. In `v.1.x` it is replaced by `m.route.prefix = prefix` where `prefix` can any prefix. If it starts with `#`, it works in "hash" mode, `?` for "search" mode, and any other character (or the empty string) for "pathname" mode. It also supports combinations of the above like `m.route.prefix = "/path/#!"` or `?#`. - -The default was changed to also use a `#!` (hashbang) prefix instead of just `#`. So if you were using the default behavior and want to retain your existing URLs, specify `m.route.prefix = "#"` before initializing the routes. - -### v0.2.x - -```javascript -m.route.mode = "hash" -m.route.mode = "pathname" -m.route.mode = "search" -``` - -### v2.x - -```javascript -// Direct equivalents -m.route.prefix = "#" -m.route.prefix = "" -m.route.prefix = "?" -``` - ---- - -## `m.route()` and anchor tags - -Handling routable links now uses a special built-in component instead of an attribute. If you were using this on ` -``` - -`vnode = m(m.route.Link, attributes, children)` - -Argument | Type | Required | Description ---------------------- | ------------------------------------ | -------- | --- -`attributes.href` | `Object` | Yes | The target route to navigate to. -`attributes.disabled` | `Boolean` | No | Disables the element accessibly. -`attributes.selector` | `String|Object|Function` | No | A selector for [`m`](hyperscript.md), defaults to `"a"`. -`attributes.options` | `Object` | No | Sets the `options` passed to [`m.route.set`](#mrouteset). -`attributes.params` | `Object` | No | Sets the `params` passed to [`m.route.set`](#mrouteset). -`attributes` | `Object` | No | Any other attributes to be forwarded to `m`. -`children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link. -**returns** | `Vnode` | | A [vnode](vnodes.md). - -##### m.route.param - -Retrieves a route parameter from the last fully resolved route. A route parameter is a key-value pair. Route parameters may come from a few different places: - -- route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`) -- router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`) -- `history.state` (e.g. if history.state is `{foo: "bar"}`, the route parameter has key `foo` and value `"bar"`) - -`value = m.route.param(key)` - -Argument | Type | Required | Description ------------------ | --------------- | -------- | --- -`key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`) -**returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys - -Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.param()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. - -##### m.route.SKIP - -A special value that can be returned from a [route resolver's `onmatch`](#routeresolveronmatch) to skip to the next route. - -#### RouteResolver - -A RouteResolver is a non-component object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. - -If an object can be detected as a component (by the presence of a `view` method or by being a `function`/`class`), it will be treated as such even if it has `onmatch` or `render` methods. Since a RouteResolver is not a component, it does not have lifecycle methods. - -As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules. - -`routeResolver = {onmatch, render}` - -When using components, you could think of them as special sugar for this route resolver, assuming your component is `Home`: - -```javascript -var routeResolver = { - onmatch: function() { return Home }, - render: function(vnode) { return [vnode] }, -} -``` - -##### routeResolver.onmatch - -The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic, data preloading, redirection analytics tracking, etc) - -This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. To render a component asynchronously return a promise that resolves to a component. - -For more information on `onmatch`, see the [advanced component resolution](#advanced-component-resolution) section - -`routeResolver.onmatch(args, requestedPath, route)` - -Argument | Type | Description ---------------- | ---------------------------------------- | --- -`args` | `Object` | The [routing parameters](#routing-parameters) -`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path. -`route` | `String` | The router path requested by the last routing action, excluding interpolated routing parameter values -**returns** | `Component|Promise|undefined` | Returns a component or a promise that resolves to a component - -If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`. - -If `onmatch` returns a promise that gets rejected, the router redirects back to `defaultRoute`. You may override this behavior by calling `.catch` on the promise chain before returning it. - -##### routeResolver.render - -The `render` method is called on every redraw for a matching route. It is similar to the `view` method in components and it exists to simplify [component composition](#wrapping-a-layout-component). It also lets you escape from Mithril.js' normal behavior of replacing the entire subtree. - -`vnode = routeResolver.render(vnode)` - -Argument | Type | Description -------------------- | -------------------- | ----------- -`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"` -`vnode.attrs` | `Object` | A map of URL parameter values -**returns** | `Array|Vnode` | The [vnodes](vnodes.md) to be rendered - -The `vnode` parameter is just `m(Component, m.route.param())` where `Component` is the resolved component for the route (after `routeResolver.onmatch`) and `m.route.param()` is as documented [here](#mrouteparam). If you omit this method, the default return value is `[vnode]`, wrapped in a fragment so you can use [key parameters](#key-parameter). Combined with a `:key` parameter, it becomes a [single-element keyed fragment](keys.md#reinitializing-views-with-single-child-keyed-fragments), since it ends up rendering to something like `[m(Component, {key: m.route.param("key"), ...})]`. - ---- - -#### How it works - -Routing is a system that allows creating Single Page Applications (SPA), i.e. applications that can go from a "page" to another without causing a full browser refresh. - -It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism. - -Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState%28%29_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. - -#### Routing strategies - -The routing strategy dictates how a library might actually implement routing. There are three general strategies that can be used to implement a SPA routing system, and each has different caveats: - -- `m.route.prefix = '#!'` (default) – Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `https://localhost/#!/page1` -- `m.route.prefix = '?'` – Using the querystring. A URL using this strategy typically looks like `https://localhost/?/page1` -- `m.route.prefix = ''` – Using the pathname. A URL using this strategy typically looks like `https://localhost/page1` - -Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState`, because it can fall back to using `onhashchange`. Use this strategy if you want to keep the hashes purely local. - -The querystring strategy allows server-side detection, but it doesn't appear as a normal path. Use this strategy if you want to support and potentially detect anchored links server-side and you are not able to make the changes necessary to support the pathname strategy (like if you're using Apache and can't modify your .htaccess). - -The pathname strategy produces the cleanest looking URLs, but requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs. - -Single page applications that use the hash strategy often use the convention of having an exclamation mark after the hash to indicate that they're using the hash as a routing mechanism and not for the purposes of linking to anchors. The `#!` string is known as a *hashbang*. - -The default strategy uses the hashbang. - ---- - -### Typical usage - -Normally, you need to create a few [components](components.md) to map routes to: - -```javascript -var Home = { - view: function() { - return [ - m(Menu), - m("h1", "Home") - ] - } -} - -var Page1 = { - view: function() { - return [ - m(Menu), - m("h1", "Page 1") - ] - } -} -``` - -In the example above, there are two components: `Home` and `Page1`. Each contains a menu and some text. The menu is itself being defined as a component to avoid repetition: - -```javascript -var Menu = { - view: function() { - return m("nav", [ - m(m.route.Link, {href: "/"}, "Home"), - m(m.route.Link, {href: "/page1"}, "Page 1"), - ]) - } -} -``` - -Now we can define routes and map our components to them: - -```javascript -m.route(document.body, "/", { - "/": Home, - "/page1": Page1, -}) -``` - -Here we specify two routes: `/` and `/page1`, which render their respective components when the user navigates to each URL. - ---- - -### Navigating to different routes - -In the example above, the `Menu` component has two `m.route.Link`s. That creates an element, by default an ``, and sets it up to where if the user clicks on it, it navigates to another route on its own. It doesn't navigate remotely, just locally. - -You can also navigate programmatically, via `m.route.set(route)`. For example, `m.route.set("/page1")`. - -When navigating between routes, the router prefix is handled for you. In other words, leave out the hashbang `#!` (or whatever prefix you set `m.route.prefix` to) when linking Mithril.js routes, including in both `m.route.set` and in `m.route.Link`. - -Do note that when navigating between components, the entire subtree is replaced. Use [a route resolver with a `render` method](#routeresolverrender) if you want to just patch the subtree. - ---- - -### Routing parameters - -Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril.js supports [parameterized routes](paths.md#path-parameters): - -```javascript -var Edit = { - view: function(vnode) { - return [ - m(Menu), - m("h1", "Editing " + vnode.attrs.id) - ] - } -} -m.route(document.body, "/edit/1", { - "/edit/:id": Edit, -}) -``` - -In the example above, we defined a route `/edit/:id`. This creates a dynamic route that matches any URL that starts with `/edit/` and is followed by some data (e.g. `/edit/1`, `edit/234`, etc). The `id` value is then mapped as an attribute of the component's [vnode](vnodes.md) (`vnode.attrs.id`) - -It's possible to have multiple arguments in a route, for example `/edit/:projectID/:userID` would yield the properties `projectID` and `userID` on the component's vnode attributes object. - -#### Key parameter - -When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event. - -To achieve that, it's possible to combine route parameterization with [keys](#reinitializing-views-with-single-child-keyed-fragments) for a very convenient pattern: - -```javascript -m.route(document.body, "/edit/1", { - "/edit/:key": Edit, -}) -``` - -This means that the [vnode](vnodes.md) that is created for the root component of the route has a route parameter object `key`. Route parameters become `attrs` in the vnode. Thus, when jumping from one page to another, the `key` changes and causes the component to be recreated from scratch (since the key tells the virtual dom engine that old and new components are different entities). - -You can take that idea further to create components that recreate themselves when reloaded: - -`m.route.set(m.route.get(), {key: Date.now()})` - -Or even use the [`history state`](#history-state) feature to achieve reloadable components without polluting the URL: - -`m.route.set(m.route.get(), null, {state: {key: Date.now()}})` - -Note that the key parameter works only for component routes. If you're using a route resolver, you'll need to use a [single-child keyed fragment](keys.md#reinitializing-views-with-single-child-keyed-fragments), passing `key: m.route.param("key")`, to accomplish the same. - -#### Variadic routes - -It's also possible to have variadic routes, i.e. a route with an argument that contains URL pathnames that contain slashes: - -```javascript -m.route(document.body, "/edit/pictures/image.jpg", { - "/edit/:file...": Edit, -}) -``` - -#### Handling 404s - -For isomorphic / universal JavaScript app, an url param and a variadic route combined is very useful to display custom 404 error page. - -In a case of 404 Not Found error, the server send back the custom page to client. When Mithril.js is loaded, it will redirect client to the default route because it can't know that route. - -```javascript -m.route(document.body, "/", { - "/": homeComponent, - // [...] - "/:404...": errorPageComponent -}); -``` - -#### History state - -It's possible to take full advantage of the underlying `history.pushState` API to improve user's navigation experience. For example, an application could "remember" the state of a large form when the user leaves a page by navigating away, such that if the user pressed the back button in the browser, they'd have the form filled rather than a blank form. - -For example, you could create a form like this: - -```javascript -var state = { - term: "", - search: function() { - // save the state for this route - // this is equivalent to `history.replaceState({term: state.term}, null, location.href)` - m.route.set(m.route.get(), null, {replace: true, state: {term: state.term}}) - - // navigate away - location.href = "https://google.com/?q=" + state.term - } -} - -var Form = { - oninit: function(vnode) { - state.term = vnode.attrs.term || "" // populated from the `history.state` property if the user presses the back button - }, - view: function() { - return m("form", [ - m("input[placeholder='Search']", { - oninput: function (e) { state.term = e.target.value }, - value: state.term - }), - m("button", {onclick: state.search}, "Search") - ]) - } -} - -m.route(document.body, "/", { - "/": Form, -}) -``` - -This way, if the user searches and presses the back button to return to the application, the input will still be populated with the search term. This technique can improve the user experience of large forms and other apps where non-persisted state is laborious for a user to produce. - ---- - -### Changing router prefix - -The router prefix is a fragment of the URL that dictates the underlying [strategy](#routing-strategies) used by the router. - -```javascript -// set to pathname strategy -m.route.prefix = "" - -// set to querystring strategy -m.route.prefix = "?" - -// set to hash without bang -m.route.prefix = "#" - -// set to pathname strategy on a non-root URL -// e.g. if the app lives under `https://localhost/my-app` and something else -// lives under `https://localhost` -m.route.prefix = "/my-app" -``` - ---- - -### Advanced component resolution - -Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `onmatch()` and/or a `render()` method. Both methods are optional but at least one of them must be present. - -```javascript -m.route(document.body, "/", { - "/": { - onmatch: function(args, requestedPath, route) { - return Home - }, - render: function(vnode) { - return vnode // equivalent to m(Home) - }, - } -}) -``` - -RouteResolvers are useful for implementing a variety of advanced routing use cases. - ---- - -#### Wrapping a layout component - -It's often desirable to wrap all or most of the routed components in a reusable shell (often called a "layout"). In order to do that, you first need to create a component that contains the common markup that will wrap around the various different components: - -```javascript -var Layout = { - view: function(vnode) { - return m(".layout", vnode.children) - } -} -``` - -In the example above, the layout merely consists of a `
` that contains the children passed to the component, but in a real life scenario it could be as complex as needed. - -One way to wrap the layout is to define an anonymous component in the routes map: - -```javascript -// example 1 -m.route(document.body, "/", { - "/": { - view: function() { - return m(Layout, m(Home)) - }, - }, - "/form": { - view: function() { - return m(Layout, m(Form)) - }, - } -}) -``` - -However, note that because the top level component is an anonymous component, jumping from the `/` route to the `/form` route (or vice-versa) will tear down the anonymous component and recreate the DOM from scratch. If the Layout component had [lifecycle methods](lifecycle-methods.md) defined, the `oninit` and `oncreate` hooks would fire on every route change. Depending on the application, this may or may not be desirable. - -If you would prefer to have the Layout component be diffed and maintained intact rather than recreated from scratch, you should instead use a RouteResolver as the root object: - -```javascript -// example 2 -m.route(document.body, "/", { - "/": { - render: function() { - return m(Layout, m(Home)) - }, - }, - "/form": { - render: function() { - return m(Layout, m(Form)) - }, - } -}) -``` - -Note that in this case, if the Layout component has `oninit` and `oncreate` lifecycle methods, they would only fire on the first route change (assuming all routes use the same layout). - -To clarify the difference between the two examples, example 1 is equivalent to this code: - -```javascript -// functionally equivalent to example 1 -var Anon1 = { - view: function() { - return m(Layout, m(Home)) - }, -} -var Anon2 = { - view: function() { - return m(Layout, m(Form)) - }, -} - -m.route(document.body, "/", { - "/": { - render: function() { - return m(Anon1) - } - }, - "/form": { - render: function() { - return m(Anon2) - } - }, -}) -``` - -Since `Anon1` and `Anon2` are different components, their subtrees (including `Layout`) are recreated from scratch. This is also what happens when components are used directly without a RouteResolver. - -In example 2, since `Layout` is the top-level component in both routes, the DOM for the `Layout` component is diffed (i.e. left intact if it has no changes), and only the change from `Home` to `Form` triggers a recreation of that subsection of the DOM. - ---- - -#### Redirection - -The RouteResolver's `onmatch` hook can be used to run logic before the top level component in a route is initialized. You can use either Mithril's `m.route.set()` or native HTML's `history` API. When redirecting with the `history` API, the `onmatch` hook must return a never-resolving Promise to prevent resolution of the matched route. `m.route.set()` cancels resolution of the matched route internally, so this isn't necessary with it. - -##### Example: authentication - -The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. - -```javascript -var isLoggedIn = false - -var Login = { - view: function() { - return m("form", [ - m("button[type=button]", { - onclick: function() { - isLoggedIn = true - m.route.set("/secret") - } - }, "Login") - ]) - } -} - -m.route(document.body, "/secret", { - "/secret": { - onmatch: function() { - if (!isLoggedIn) m.route.set("/login") - else return Home - } - }, - "/login": Login -}) -``` - -When the application loads, `onmatch` is called and since `isLoggedIn` is false, the application redirects to `/login`. Once the user pressed the login button, `isLoggedIn` would be set to true, and the application would redirect to `/secret`. The `onmatch` hook would run once again, and since `isLoggedIn` is true this time, the application would render the `Home` component. - -For the sake of simplicity, in the example above, the user's logged in status is kept in a global variable, and that flag is merely toggled when the user clicks the login button. In a real life application, a user would obviously have to supply proper login credentials, and clicking the login button would trigger a request to a server to authenticate the user: - -```javascript -var Auth = { - username: "", - password: "", - - setUsername: function(value) { - Auth.username = value - }, - setPassword: function(value) { - Auth.password = value - }, - login: function() { - m.request({ - url: "/api/v1/auth", - params: {username: Auth.username, password: Auth.password} - }).then(function(data) { - localStorage.setItem("auth-token", data.token) - m.route.set("/secret") - }) - } -} - -var Login = { - view: function() { - return m("form", [ - m("input[type=text]", { - oninput: function (e) { Auth.setUsername(e.target.value) }, - value: Auth.username - }), - m("input[type=password]", { - oninput: function (e) { Auth.setPassword(e.target.value) }, - value: Auth.password - }), - m("button[type=button]", {onclick: Auth.login}, "Login") - ]) - } -} - -m.route(document.body, "/secret", { - "/secret": { - onmatch: function() { - if (!localStorage.getItem("auth-token")) m.route.set("/login") - else return Home - } - }, - "/login": Login -}) -``` - ---- - -#### Preloading data - -Typically, a component can load data upon initialization. Loading data this way renders the component twice. The first render pass occurs upon routing, and the second fires after the request completes. Take care to note that `loadUsers()` returns a Promise, but any Promise returned by `oninit` is currently ignored. The second render pass comes from the [`background` option for `m.request`](request.md). - -```javascript -var state = { - users: [], - loadUsers: function() { - return m.request("/api/v1/users").then(function(users) { - state.users = users - }) - } -} - -m.route(document.body, "/user/list", { - "/user/list": { - oninit: state.loadUsers, - view: function() { - return state.users.length > 0 ? state.users.map(function(user) { - return m("div", user.id) - }) : "loading" - } - }, -}) -``` - -In the example above, on the first render, the UI displays `"loading"` since `state.users` is an empty array before the request completes. Then, once data is available, the UI redraws and a list of user ids is shown. - -RouteResolvers can be used as a mechanism to preload data before rendering a component in order to avoid UI flickering and thus bypassing the need for a loading indicator: - -```javascript -var state = { - users: [], - loadUsers: function() { - return m.request("/api/v1/users").then(function(users) { - state.users = users - }) - } -} - -m.route(document.body, "/user/list", { - "/user/list": { - onmatch: state.loadUsers, - render: function() { - return state.users.map(function(user) { - return m("div", user.id) - }) - } - }, -}) -``` - -Above, `render` only runs after the request completes, making the ternary operator redundant. - ---- - -#### Code splitting - -In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril.js, this can be accomplished by returning a promise from the `onmatch` hook: - -At its most basic form, one could do the following: - -```javascript -// Home.js -module.export = { - view: function() { - return [ - m(Menu), - m("h1", "Home") - ] - } -} -``` - -```javascript -// index.js -function load(file) { - return m.request({ - method: "GET", - url: file, - extract: function(xhr) { - return new Function("var module = {};" + xhr.responseText + ";return module.exports;") - } - }) -} - -m.route(document.body, "/", { - "/": { - onmatch: function() { - return load("Home.js") - }, - }, -}) -``` - -However, realistically, in order for that to work on a production scale, it would be necessary to bundle all of the dependencies for the `Home.js` module into the file that is ultimately served by the server. - -Fortunately, there are a number of tools that facilitate the task of bundling modules for lazy loading. Here's an example using [native dynamic `import(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import), supported by many bundlers: - -```javascript -m.route(document.body, "/", { - "/": { - onmatch: function() { - return import('./Home.js') - }, - }, -}) -``` - ---- - -### Typed routes - -In certain advanced routing cases, you may want to constrain a value further than just the path itself, only matching something like a numeric ID. You can do that pretty easily by returning `m.route.SKIP` from a route. - -```javascript -m.route(document.body, "/", { - "/view/:id": { - onmatch: function(args) { - if (!/^\d+$/.test(args.id)) return m.route.SKIP - return ItemView - }, - }, - "/view/:name": UserView, -}) -``` - ---- - -### Hidden routes - -In rare circumstances, you may want to hide certain routes for some users, but not all. For instance, a user might be prohibited from viewing a particular user, and instead of showing a permission error, you'd rather pretend it doesn't exist and redirect to a 404 view instead. In this case, you can use `m.route.SKIP` to just pretend the route doesn't exist. - -```javascript -m.route(document.body, "/", { - "/user/:id": { - onmatch: function(args) { - return Model.checkViewable(args.id).then(function(viewable) { - return viewable ? UserView : m.route.SKIP - }) - }, - }, - "/:404...": PageNotFound, -}) -``` - ---- - -### Route cancellation / blocking - -RouteResolver `onmatch` can prevent route resolution by returning a promise that never resolves. This can be used to detect attempted redundant route resolutions and cancel them: - -```javascript -m.route(document.body, "/", { - "/": { - onmatch: function(args, requestedPath) { - if (m.route.get() === requestedPath) - return new Promise(function() {}) - }, - }, -}) -``` - ---- - -### Third-party integration - -In certain situations, you may find yourself needing to interoperate with another framework like React. Here's how you do it: - -- Define all your routes using `m.route` as normal, but make sure you only use it *once*. Multiple route points are not supported. -- When you need to remove routing subscriptions, use `m.mount(root, null)`, using the same root you used `m.route(root, ...)` on. `m.route` uses `m.mount` internally to hook everything up, so it's not magic. - -Here's an example with React: - -```jsx -class Child extends React.Component { - constructor(props) { - super(props) - this.root = React.createRef() - } - - componentDidMount() { - m.route(this.root, "/", { - // ... - }) - } - - componentDidUnmount() { - m.mount(this.root, null) - } - - render() { - return
- } -} -``` - -And here's the rough equivalent with Vue: - -```html -
-``` - -```javascript -Vue.component("my-child", { - template: `
`, - mounted: function() { - m.route(this.$refs.root, "/", { - // ... - }) - }, - destroyed: function() { - m.mount(this.$refs.root, null) - }, -}) -``` diff --git a/docs/signatures.md b/docs/signatures.md deleted file mode 100644 index 7838bf26..00000000 --- a/docs/signatures.md +++ /dev/null @@ -1,87 +0,0 @@ - - -# How to read signatures - -Signature sections typically look like this: - -`vnode = m(selector, attributes, children)` - -Argument | Type | Required | Description ------------- | ------------------------------------ | -------- | --- -`selector` | `String|Object` | Yes | A CSS selector or a component -`attributes` | `Object` | No | HTML attributes or element properties -`children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md). Can be written as [splat arguments](signatures.md#splats) -**returns** | `Vnode` | | A [vnode](vnodes.md) - -The signature line above the table indicates the general syntax of the method, showing the name of the method, the order of its arguments and a suggested variable name for its return value. - -The **Argument** column in the table indicates which part of the signature is explained by the respective table row. The `returns` row displays information about the return value of the method. - -The **Type** column indicates the expected type for the argument. - -A pipe (`|`) indicates that an argument is valid if it has any of the listed types. For example, `String|Object` indicates that `selector` can be a string OR an object. - -Angled brackets (`< >`) after an `Array` indicate the expected type for array items. For exampe, `Array` indicates that the argument must be an array and that all items in that array must be strings. Angled brackets after an `Object` indicate a map. For example, `Object` indicates that the argument must be an object, whose keys are strings and values are [components](components.md) - -Sometimes non-native types may appear to indicate that a specific object signature is required. For example, `Vnode` is an object that has a [virtual DOM node](vnodes.md) structure. - -The **Required** column indicates whether an argument is required or optional. If an argument is optional, you may set it to `null` or `undefined`, or omit it altogether, such that the next argument appears in its place. - ---- - -### Optional arguments - -Function arguments surrounded by square brackets `[ ]` are optional. In the example below, `url` is an optional argument: - -`m.request([url,] options)` - ---- - -### Splats - -A splat argument means that if the argument is an array, you can omit the square brackets and have a variable number of arguments in the method instead. - -In the example at the top, this means that `m("div", {id: "foo"}, ["a", "b", "c"])` can also be written as `m("div", {id: "foo"}, "a", "b", "c")`. - -Splats are useful in some compile-to-JS languages such as CoffeeScript, and also allow helpful shorthands for some common use cases. - ---- - -### Function signatures - -Functions are denoted with an arrow (`->`). The left side of the arrow indicates the types of the input arguments and the right side indicates the type for the return value. - -For example, `parseFloat` has the signature `String -> Number`, i.e. it takes a string as input and returns a number as output. - -Functions with multiple arguments are denoted with parenthesis: `(String, Array) -> Number` - ---- - -### Component signatures - -Components are denoted via calls to `m`, but with the initial selector argument set to a constant named in the relevant prose: - -`vnode = m(m.route.Link, attributes, children)` - -Argument | Type | Required | Description ---------------------- | ------------------------------------ | -------- | --- -`attributes.href` | `Object` | Yes | The target route to navigate to. -`attributes.selector` | `String|Object|Function` | No | This sets the tag name to use. Must be a valid selector for [`m`](hyperscript.md) if given, defaults to `"a"`. -`attributes.options` | `Object` | No | This sets the options passed to [`m.route.set`](#mrouteset). -`attributes` | `Object` | No | Other attributes to apply to the returned vnode may be passed. -`children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md) for this link. -**returns** | `Vnode` | | A [vnode](vnodes.md). - -Children here, if specified, are assumed to be able to be written as [splat arguments](#splats), unless otherwise specified in prose. - -An element with no sensible children and/or attributes may choose to elide the relevant parameter entirely: - -`vnode = m(Component, attributes)` - -Argument | Type | Required | Description ------------------ | -------- | -------- | --- -`attributes.href` | `Object` | Yes | The -`attributes` | `Object` | No | Other attributes to apply to the returned vnode -**returns** | `Vnode` | | A [vnode](vnodes.md) diff --git a/docs/simple-application.md b/docs/simple-application.md deleted file mode 100644 index 38f8aead..00000000 --- a/docs/simple-application.md +++ /dev/null @@ -1,736 +0,0 @@ - - -# Simple application - -Let's develop a simple application that shows off how to do most of the major things you would need to deal with while using Mithril. - -*An interactive example of the end result can be seen [here](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcICqkBOBZA9ktAHQBWYIANCAGYCWMFqA2qAHYCGAthGpjgSJQyFarHxtEkvgDcO2AARYIigLwLgAHTYLdCqHTDxUC5gF1K2vfvwckAGUPGFNAK5sEdCQAoAlBqtrPWwIeFdsHS5iEIBHVwgjby0dINSeeAALQhNNEABxAFEAFVzLFNTrcKgckAz4eAAHMFQAehauOkzsBgBaEKiaKABPYiIZFo4GuhbXHDBSwIq9AHdOjIBhEKJJOg4oZoV4bHiypYBfX0Wg4kyINm83D3gve5CwVyh4f2Sl62VsYgGIwKdRvD7wUYceAcK7WC5XM5lK6wcIhSQmYCIq5QWxIEyPTw+OhIb6w4KhcKRaIQOIJeBJMlpUJZPEKXKFEpURlBKo1OqNZptDpdXr9YiDEZjCZTGZzFq5BQAagUxNOv10q0ym2Qd2eewORxO3Ph5Wut3uBOePjBn1JpqW-2IKOwaPgIIUNvgxsupqxbCuYA4Mgg+PchPudqWITCEQUUVi8USP3VceZ2TZIAAChhOWr1byM-ymq12mtulA+hABsNRhBxpNprMVGB5SAlUocE7UbriKrudYAEaEIYmR3O11536ajZbXW7fYmQ0QSdwn1w7RnbRUcDQOBWthMEAAVlQABYj5QAIwAdjPF4ATAAOO8gRHsbi8dD-RxGETb8SSLqsjyB2Kg-m66jJroEh0GwnSjp2OJ2OBk4yHQEDLKGTwvH4AT2uSMaRN4uTEE22A9ECXpUKBAKUcQXCTA8Yb7t4ZGRim0aUnG3jxvgriIMQjhsAA1pQGgZCENA1C0yCdK27Zkb2SBibAUAcGABwAORkRRTg9J0VaaYiCiKfQ2BGAAch+7YKgqyqKWplkfmuFQXC5uibmwnmvpQ748Hw-wAGL4NgUTkP+EhSPAwGKEFIVcO6UEKDBcHOJaOEyGwhAQN8jpIUg3iZdlxBQkcYBKb4frWGhGFYeGuFJdYnGxlwxG0PFpR4SmQQSO8A7CnVLE5V13UVBAxANCEwaSAAIhANAcOCfj9imjqBsGy34eqnndcZzArborW5GpA4kCd0CdbkgV0OZbqcDwuS+CuFRHSAsENPxvZsB98DMPAQwNBAqiIAAHvAZjMA0amwBAWRQEQ2CqNdt0KPdEBmJ1jUpilP2DThw3AGO3aSOKN1OTw7rjdC2AAOahMQchQPEVWjbojPxAhKhdi6PZmeTEAHQoFzPWkbXncI4uXSA9jqXdH6PSLQSve9n0q79UMcDDcMI6oMvAmjGPUVj6o4-xeM+ATRM8yTjnwFZFPqFT8h0xC7MQCzrNu5zALjj2tv2wLW1BMLguvQO-HwBIxDh-UEh-QDQN9cKhtibkADKQYCyAT3cmY7lCxuW5viAaN8DLQx8RC4ViJFQHoHIijl5XiVXDVmEuMxGVZUQ7GpM1RG5AxsGAhwFf8Z1+1B9Yr2cDI9F3K4E+C9PPHRJX41CaJI2s7oEnzSYmktJRmnL3oxm5P88zZ4LeeKyvuSQOGnVFUQToZAwSBovn1h5wihdsD5Pyn4QCTAaH+GugFpDoF4vxcak15p0BBu6TSABiE+bAYGIG8EgfAKIeAkyHEgIYqcQBHycJjK4uQyFGFyBibkaIEbmwjNvX4-duJN34mJVq34nC+G-mfBEk4qGyXgC0VAxJaEsKancRhHdsI+BfgTQWbDWocPgFw7wcVQpiUUSVeo5k+HGkERuH0gCS4fj4E6DSEVIHRXQIQ4hCgvo-TEtHCOEgRo0EiiYLKoU9gKEvAANgaEggAaioJAHBOAAG5AgMVprBEw94AAMITYleW0NoEeY83TJniTTRJATUlIMWpHdJh0OAgx6KsJAmQTCXmSY0tJ-8sn4NcCNfJhTkkKG6QAZmKekzyWSdKURGpRHoRghgwB8RICA5S4zOy6T0opzSUgNDsEgWCNMTDJMGZktgpEcC6SMPpRACVkwDk1sJGm2A+JsFZCghaTyODzKHNgWRl4QkKDAPgAwSAFAoKQEC+Z4gcTYBMCg3pUL5mbLABrEcCgBw4lgMJeZnS2A7OWZ8kG8z1lAq2SYR8XzLxHlWboUG8AehEHENgKELwZlsDmS0g5Iy9IGS4KgLIwZFDJgpVSuAIU6USBMO4BGBhGV7P9Ac8WI1YXwpMEi3BqK4mLIxcs7ppKcXMucfxEabyPlfJ+X8gFQKkCvJCgjPodg6CzBML0sliL8BVLAHQAAXgSx17yVA9CHFqlIcq1IIsVSitFqrMXdIaQ6vFmy2DbJWUgklDqal1KKckgApJKrJMdI46AuVcm5dyHkQGLear14KAmGt+cSE1wLAj6u9bSzZtqFD2r9boUFIUIVQt6TCww8qVRsHFRAH1yLlUpHReG+NuKNkesjQmzV8y+XUsFfuBlTKMlSrcbHDFXKVB6vzbc0VEKICPhPSeyVr4LAgHFcJJgrBzH+WgWWBg24qh8CLIKGY31rlOnwFwUsIooAAAF7zEGScQU8AGJIMHosPauIB-qAz4GAWA3QGjRTOGYM4QA)* - -First let's create an entry point for the application. Create a file `index.html`: - -```html - - - - - - My Application - - - - - -``` - -The `` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the JavaScript file that controls the application. - -We could create the entire application in a single JavaScript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. - -There are many ways to setup a bundler tool, but most are distributed via npm. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. To download npm, [install Node.js](https://nodejs.org/en/); npm is installed automatically with it. Once you have Node.js and npm installed, open the command line and run this command: - -```bash -npm init -y -``` - -If npm is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file. - ---- - -To install Mithril.js, follow the instructions in the [installation](installation.md) page. Once you have a project skeleton with Mithril.js installed, we are ready to create the application. - -Let's start by creating a module to store our state. Let's create a file called `src/models/User.js` - -```javascript -// src/models/User.js -var User = { - list: [] -} - -module.exports = User -``` - -Now let's add code to load some data from a server. To communicate with a server, we can use Mithril.js' XHR utility, `m.request`. First, we include Mithril.js in the module: - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [] -} - -module.exports = User -``` - -Next we create a function that will trigger an XHR call. Let's call it `loadList` - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [], - loadList: function() { - // TODO: make XHR call - } -} - -module.exports = User -``` - -Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the REM (DEAD LINK, FIXME: https //rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://mithril-rem.fly.dev/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. - -*Note: third-party cookies may have to be enabled for the REM endpoint to work.* - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [], - loadList: function() { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users", - withCredentials: true, - }) - .then(function(result) { - User.list = result.data - }) - }, -} - -module.exports = User -``` - -The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API). - -The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril.js assumes a HTTP response body are in JSON format and automatically parses it into a JavaScript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. - -Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request. - -This simple model exposes two members: `User.list` (an array of user objects), and `User.loadList` (a method that populates `User.list` with server data). - ---- - -Now, let's create a view module so that we can display data from our User model module. - -Create a file called `src/views/UserList.js`. First, let's include Mithril.js and our model, since we'll need to use both: - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") -``` - -Next, let's create a Mithril.js component. A component is simply an object that has a `view` method: - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - view: function() { - // TODO add code here - } -} -``` - -By default, Mithril.js views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and since its syntax is just JavaScript, it's possible to leverage a lot of JavaScript tooling ecosystem. For example: - -- You can use [Babel](es6.md) to transpile ES6+ to ES5 for IE and to transpile [JSX](jsx.md) (an inline HTML-like syntax extension) to appropriate hyperscript calls. -- You can use [ESLint](https://eslint.org/) for easy linting with no special plugins. -- You can use [Terser](https://github.com/terser-js/terser) or [UglifyJS](https://github.com/mishoo/UglifyJS2) (ES5 only) to minify your code easily. -- You can use [Istanbul](https://github.com/istanbuljs/nyc) for code coverage. -- You can use [TypeScript](https://www.typescriptlang.org/) for easy code analysis. (There are [community-supported type definitions available](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mithril), so you don't need to roll your own.) - -Let's start off with hyperscript and create a list of items. Hyperscript is the idiomatic way to use Mithril.js, but [JSX](jsx.md) works pretty similarly. - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - view: function() { - return m(".user-list") - } -} -``` - -The `".user-list"` string is a CSS selector, and as you would expect, `.user-list` represents a class. When a tag is not specified, `div` is the default. So this view is equivalent to `
`. - -Now, let's reference the list of users from the model we created earlier (`User.list`) to dynamically loop through data: - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - view: function() { - return m(".user-list", User.list.map(function(user) { - return m(".user-list-item", user.firstName + " " + user.lastName) - })) - } -} -``` - -Since `User.list` is a JavaScript array, and since hyperscript views are just JavaScript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. - -The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md): - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - oninit: User.loadList, - view: function() { - return m(".user-list", User.list.map(function(user) { - return m(".user-list-item", user.firstName + " " + user.lastName) - })) - } -} -``` - -Notice that we added an `oninit` method to the component, which references `User.loadList`. This means that when the component initializes, User.loadList will be called, triggering an XHR request. When the server returns a response, `User.list` gets populated. - -Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to JavaScript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. - ---- - -Let's render the view from the entry point file `src/index.js` we created earlier: - -```javascript -// src/index.js -var m = require("mithril") - -var UserList = require("./views/UserList") - -m.mount(document.body, UserList) -``` - -The `m.mount` call renders the specified component (`UserList`) into a DOM element (`document.body`), erasing any DOM that was there previously. Opening the HTML file in a browser should now display a list of person names. - ---- - -Right now, the list looks rather plain because we have not defined any styles. So let's add a few of them. Let's first create a file called `styles.css` and include it in the `index.html` file: - -```html - - - - - - My Application - - - - - - -``` - -Now we can style the `UserList` component: - -```css -.user-list { - list-style: none; - margin: 0 0 10px; - padding: 0; -} - -.user-list-item { - background: #fafafa; - border: 1px solid #ddd; - color: #333; - display: block; - margin: 0 0 1px; - padding: 8px 15px; - text-decoration: none; -} - -.user-list-item:hover { - text-decoration: underline; -} -``` - -Reloading the browser window now should display some styled elements. - ---- - -Let's add routing to our application. - -Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril.js is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via JavaScript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). - -We can add routing by changing the `m.mount` call to a `m.route` call: - -```javascript -// src/index.js -var m = require("mithril") - -var UserList = require("./views/UserList") - -m.route(document.body, "/list", { - "/list": UserList -}) -``` - -The `m.route` call specifies that the application will be rendered into `document.body`. The `"/list"` argument is the default route. That means the user will be redirected to that route if they land in a route that does not exist. The `{"/list": UserList}` object declares a map of existing routes, and what components each route resolves to. - -Refreshing the page in the browser should now append `#!/list` to the URL to indicate that routing is working. Since that route render UserList, we should still see the list of people on screen as before. - -The `#!` snippet is known as a hashbang, and it's a commonly used string for implementing client-side routing. It's possible to configure this string it via [`m.route.prefix`](route.md#mrouteprefix). Some configurations require supporting server-side changes, so we'll just continue using the hashbang for the rest of this tutorial. - ---- - -Let's add another route to our application for editing users. First let's create a module called `views/UserForm.js` - -```javascript -// src/views/UserForm.js - -module.exports = { - view: function() { - // TODO implement view - } -} -``` - -Then we can `require` this new module from `src/index.js` - -```javascript -// src/index.js -var m = require("mithril") - -var UserList = require("./views/UserList") -var UserForm = require("./views/UserForm") - -m.route(document.body, "/list", { - "/list": UserList -}) -``` - -And finally, we can create a route that references it: - -```javascript -// src/index.js -var m = require("mithril") - -var UserList = require("./views/UserList") -var UserForm = require("./views/UserForm") - -m.route(document.body, "/list", { - "/list": UserList, - "/edit/:id": UserForm, -}) -``` - -Notice that the new route has a `:id` in it. This is a route parameter; you can think of it as a wild card; the route `/edit/1` would resolve to `UserForm` with an `id` of `"1"`. `/edit/2` would also resolve to `UserForm`, but with an `id` of `"2"`. And so on. - -Let's implement the `UserForm` component so that it can respond to those route parameters: - -```javascript -// src/views/UserForm.js -var m = require("mithril") - -module.exports = { - view: function() { - return m("form", [ - m("label.label", "First name"), - m("input.input[type=text][placeholder=First name]"), - m("label.label", "Last name"), - m("input.input[placeholder=Last name]"), - m("button.button[type=submit]", "Save"), - ]) - } -} -``` - -And let's add some more styles to `styles.css`: - -```css -/* styles.css */ -body, .input, .button { - font: normal 16px Verdana; - margin: 0; -} - -.user-list { - list-style: none; - margin: 0 0 10px; - padding: 0; -} - -.user-list-item { - background: #fafafa; - border: 1px solid #ddd; - color: #333; - display: block; - margin: 0 0 1px; - padding: 8px 15px; - text-decoration: none; -} - -.user-list-item:hover { - text-decoration: underline; -} - -.label { - display: block; - margin: 0 0 5px; -} - -.input { - border: 1px solid #ddd; - border-radius: 3px; - box-sizing: border-box; - display: block; - margin: 0 0 10px; - padding: 10px 15px; - width: 100%; -} - -.button { - background: #eee; - border: 1px solid #ddd; - border-radius: 3px; - color: #333; - display: inline-block; - margin: 0 0 10px; - padding: 10px 15px; - text-decoration: none; -} - -.button:hover { - background: #e8e8e8; -} -``` - -Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/models/User.js`. This is how the code is right now: - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [], - loadList: function() { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users", - withCredentials: true, - }) - .then(function(result) { - User.list = result.data - }) - }, -} - -module.exports = User -``` - -Let's add code to allow us to load a single user - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [], - loadList: function() { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users", - withCredentials: true, - }) - .then(function(result) { - User.list = result.data - }) - }, - - current: {}, - load: function(id) { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users/" + id, - withCredentials: true, - }) - .then(function(result) { - User.current = result - }) - } -} - -module.exports = User -``` - -Notice we added a `User.current` property, and a `User.load(id)` method which populates that property. We can now populate the `UserForm` view using this new method: - -```javascript -// src/views/UserForm.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - oninit: function(vnode) {User.load(vnode.attrs.id)}, - view: function() { - return m("form", [ - m("label.label", "First name"), - m("input.input[type=text][placeholder=First name]", {value: User.current.firstName}), - m("label.label", "Last name"), - m("input.input[placeholder=Last name]", {value: User.current.lastName}), - m("button.button[type=submit]", "Save"), - ]) - } -} -``` - -Similar to the `UserList` component, `oninit` calls `User.load()`. Remember we had a route parameter called `:id` on the `"/edit/:id": UserForm` route? The route parameter becomes an attribute of the `UserForm` component's vnode, so routing to `/edit/1` would make `vnode.attrs.id` have a value of `"1"`. - -Now, let's modify the `UserList` view so that we can navigate from there to a `UserForm`: - -```javascript -// src/views/UserList.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - oninit: User.loadList, - view: function() { - return m(".user-list", User.list.map(function(user) { - return m(m.route.Link, { - class: "user-list-item", - href: "/edit/" + user.id, - }, user.firstName + " " + user.lastName) - })) - } -} -``` - -Here we swapped out the `.user-list-item` vnode with an `m.route.Link` with that class and the same children. We added an `href` that references the route we want. What this means is that clicking the link would change the part of URL that comes after the hashbang `#!` (thus changing the route without unloading the current HTML page). Behind the scenes, it uses an `
` to implement the link, and it all just works. - -If you refresh the page in the browser, you should now be able to click on a person and be taken to a form. You should also be able to press the back button in the browser to go back from the form to the list of people. - ---- - -The form itself still doesn't save when you press "Save". Let's make this form work: - -```javascript -// src/views/UserForm.js -var m = require("mithril") -var User = require("../models/User") - -module.exports = { - oninit: function(vnode) {User.load(vnode.attrs.id)}, - view: function() { - return m("form", { - onsubmit: function(e) { - e.preventDefault() - User.save() - } - }, [ - m("label.label", "First name"), - m("input.input[type=text][placeholder=First name]", { - oninput: function (e) {User.current.firstName = e.target.value}, - value: User.current.firstName - }), - m("label.label", "Last name"), - m("input.input[placeholder=Last name]", { - oninput: function (e) {User.current.lastName = e.target.value}, - value: User.current.lastName - }), - m("button.button[type=submit]", "Save"), - ]) - } -} -``` - -We added `oninput` events to both inputs, that set the `User.current.firstName` and `User.current.lastName` properties when a user types. - -In addition, we declared that a `User.save` method should be called when the "Save" button is pressed. Let's implement that method: - -```javascript -// src/models/User.js -var m = require("mithril") - -var User = { - list: [], - loadList: function() { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users", - withCredentials: true, - }) - .then(function(result) { - User.list = result.data - }) - }, - - current: {}, - load: function(id) { - return m.request({ - method: "GET", - url: "https://mithril-rem.fly.dev/api/users/" + id, - withCredentials: true, - }) - .then(function(result) { - User.current = result - }) - }, - - save: function() { - return m.request({ - method: "PUT", - url: "https://mithril-rem.fly.dev/api/users/" + User.current.id, - body: User.current, - withCredentials: true, - }) - } -} - -module.exports = User -``` - -In the `save` method at the bottom, we used the `PUT` HTTP method to indicate that we are upserting data to the server. - -Now try editing the name of a user in the application. Once you save a change, you should be able to see the change reflected in the list of users. - ---- - -Currently, we're only able to navigate back to the user list via the browser back button. Ideally, we would like to have a menu - or more generically, a layout where we can put global UI elements - -Let's create a file `src/views/Layout.js`: - -```javascript -// src/views/Layout.js -var m = require("mithril") - -module.exports = { - view: function(vnode) { - return m("main.layout", [ - m("nav.menu", [ - m(m.route.Link, {href: "/list"}, "Users") - ]), - m("section", vnode.children) - ]) - } -} -``` - -This component is fairly straightforward, it has a `