2023-03-06 15:26:06 +01:00
/// ivk: patched to fix early console binding
/// ivk: patched to remove promise polyfill and m.request
2025-01-02 18:47:57 +01:00
/// ivk: made esm module
2022-06-27 15:33:32 +02:00
function Vnode ( tag , key , attrs0 , children , text , dom ) {
return { tag : tag , key : key , attrs : attrs0 , children : children , text : text , dom : dom , domSize : undefined , state : undefined , events : undefined , instance : undefined }
2025-01-02 18:47:57 +01:00
}
Vnode . normalize = function ( node ) {
2018-10-23 14:23:02 +02:00
if ( Array . isArray ( node ) ) return Vnode ( "[" , undefined , undefined , Vnode . normalizeChildren ( node ) , undefined , undefined )
2019-08-16 16:25:01 +02:00
if ( node == null || typeof node === "boolean" ) return null
if ( typeof node === "object" ) return node
return Vnode ( "#" , undefined , undefined , String ( node ) , undefined , undefined )
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
Vnode . normalizeChildren = function ( input ) {
2022-06-27 15:33:32 +02:00
var children = [ ]
2019-08-16 16:25:01 +02:00
if ( input . length ) {
var isKeyed = input [ 0 ] != null && input [ 0 ] . key != null
// Note: this is a *very* perf-sensitive check.
// Fun fact: merging the loop like this is somehow faster than splitting
// it, noticeably so.
for ( var i = 1 ; i < input . length ; i ++ ) {
if ( ( input [ i ] != null && input [ i ] . key != null ) !== isKeyed ) {
2022-06-27 15:33:32 +02:00
throw new TypeError (
isKeyed && ( input [ i ] != null || typeof input [ i ] === "boolean" )
? "In fragments, vnodes must either all have keys or none have keys. You may wish to consider using an explicit keyed empty fragment, m.fragment({key: ...}), instead of a hole."
: "In fragments, vnodes must either all have keys or none have keys."
)
2019-08-16 16:25:01 +02:00
}
}
for ( var i = 0 ; i < input . length ; i ++ ) {
2022-06-27 15:33:32 +02:00
children [ i ] = Vnode . normalize ( input [ i ] )
2019-08-16 16:25:01 +02:00
}
2018-10-23 14:23:02 +02:00
}
2022-06-27 15:33:32 +02:00
return children
2019-01-17 09:50:05 +01:00
}
// Call via `hyperscriptVnode0.apply(startOffset, arguments)`
//
// The reason I do it this way, forwarding the arguments and passing the start
// offset in `this`, is so I don't have to create a temporary array in a
// performance-critical path.
//
// In native ES6, I'd instead add a final `...args` parameter to the
// `hyperscript0` and `fragment` factories and define this as
// `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But
2022-06-27 15:33:32 +02:00
// ES5 (what Mithril.js requires thanks to IE support) doesn't give me that luxury,
2019-01-17 09:50:05 +01:00
// and engines aren't nearly intelligent enough to do either of these:
//
// 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to
// another function only to be indexed.
// 2. Elide an `arguments` allocation when it's passed to any function other
// than `Function.prototype.apply` or `Reflect.apply`.
//
// In ES6, it'd probably look closer to this (I'd need to profile it, though):
2022-06-27 15:33:32 +02:00
// var hyperscriptVnode = function(attrs1, ...children0) {
2019-01-17 09:50:05 +01:00
// if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) {
2022-06-27 15:33:32 +02:00
// if (children0.length === 1 && Array.isArray(children0[0])) children0 = children0[0]
2019-01-17 09:50:05 +01:00
// } else {
2022-06-27 15:33:32 +02:00
// children0 = children0.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children0]
2019-01-17 09:50:05 +01:00
// attrs1 = undefined
// }
//
// if (attrs1 == null) attrs1 = {}
2022-06-27 15:33:32 +02:00
// return Vnode("", attrs1.key, attrs1, children0)
2019-01-17 09:50:05 +01:00
// }
2025-01-02 18:47:57 +01:00
var hyperscriptVnode = function ( ) {
2022-06-27 15:33:32 +02:00
var attrs1 = arguments [ this ] , start = this + 1 , children0
2019-01-17 09:50:05 +01:00
if ( attrs1 == null ) {
attrs1 = { }
} else if ( typeof attrs1 !== "object" || attrs1 . tag != null || Array . isArray ( attrs1 ) ) {
attrs1 = { }
start = this
}
if ( arguments . length === start + 1 ) {
2022-06-27 15:33:32 +02:00
children0 = arguments [ start ]
if ( ! Array . isArray ( children0 ) ) children0 = [ children0 ]
2019-01-17 09:50:05 +01:00
} else {
2022-06-27 15:33:32 +02:00
children0 = [ ]
while ( start < arguments . length ) children0 . push ( arguments [ start ++ ] )
2019-01-17 09:50:05 +01:00
}
2022-06-27 15:33:32 +02:00
return Vnode ( "" , attrs1 . key , attrs1 , children0 )
2018-10-23 14:23:02 +02:00
}
2022-06-27 15:33:32 +02:00
// This exists so I'm1 only saving it once.
var hasOwn = { } . hasOwnProperty
2018-10-23 14:23:02 +02:00
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
var selectorCache = { }
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function isEmpty ( object ) {
for ( var key in object ) if ( hasOwn . call ( object , key ) ) return false
return true
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function compileSelector ( selector ) {
var match , tag = "div" , classes = [ ] , attrs = { }
while ( match = selectorParser . exec ( selector ) ) {
var type = match [ 1 ] , value = match [ 2 ]
if ( type === "" && value !== "" ) tag = value
else if ( type === "#" ) attrs . id = value
else if ( type === "." ) classes . push ( value )
else if ( match [ 3 ] [ 0 ] === "[" ) {
var attrValue = match [ 6 ]
if ( attrValue ) attrValue = attrValue . replace ( /\\(["'])/g , "$1" ) . replace ( /\\\\/g , "\\" )
if ( match [ 4 ] === "class" ) classes . push ( attrValue )
else attrs [ match [ 4 ] ] = attrValue === "" ? attrValue : attrValue || true
}
}
if ( classes . length > 0 ) attrs . className = classes . join ( " " )
return selectorCache [ selector ] = { tag : tag , attrs : attrs }
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function execSelector ( state , vnode ) {
var attrs = vnode . attrs
var hasClass = hasOwn . call ( attrs , "class" )
var className = hasClass ? attrs . class : attrs . className
vnode . tag = state . tag
2022-06-27 15:33:32 +02:00
vnode . attrs = { }
2018-10-23 14:23:02 +02:00
if ( ! isEmpty ( state . attrs ) && ! isEmpty ( attrs ) ) {
var newAttrs = { }
2019-01-17 09:50:05 +01:00
for ( var key in attrs ) {
if ( hasOwn . call ( attrs , key ) ) newAttrs [ key ] = attrs [ key ]
2018-10-23 14:23:02 +02:00
}
attrs = newAttrs
}
for ( var key in state . attrs ) {
2025-01-02 18:47:57 +01:00
if ( hasOwn . call ( state . attrs , key ) && key !== "className" && ! hasOwn . call ( attrs , key ) ) {
2018-10-23 14:23:02 +02:00
attrs [ key ] = state . attrs [ key ]
}
}
2019-01-17 09:50:05 +01:00
if ( className != null || state . attrs . className != null ) attrs . className =
2025-01-02 18:47:57 +01:00
className != null
? state . attrs . className != null
? String ( state . attrs . className ) + " " + String ( className )
: className
: state . attrs . className != null
? state . attrs . className
: null
2019-01-17 09:50:05 +01:00
if ( hasClass ) attrs . class = null
2018-10-23 14:23:02 +02:00
for ( var key in attrs ) {
if ( hasOwn . call ( attrs , key ) && key !== "key" ) {
2019-01-17 09:50:05 +01:00
vnode . attrs = attrs
2018-10-23 14:23:02 +02:00
break
}
}
2019-01-17 09:50:05 +01:00
return vnode
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function hyperscript ( selector ) {
if ( selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector . view !== "function" ) {
throw Error ( "The selector must be either a string or a component." ) ;
}
2019-01-17 09:50:05 +01:00
var vnode = hyperscriptVnode . apply ( 1 , arguments )
2018-10-23 14:23:02 +02:00
if ( typeof selector === "string" ) {
2019-03-05 12:14:13 +01:00
vnode . children = Vnode . normalizeChildren ( vnode . children )
if ( selector !== "[" ) return execSelector ( selectorCache [ selector ] || compileSelector ( selector ) , vnode )
2018-10-23 14:23:02 +02:00
}
2019-03-05 12:14:13 +01:00
vnode . tag = selector
return vnode
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
hyperscript . trust = function ( html ) {
2018-10-23 14:23:02 +02:00
if ( html == null ) html = ""
return Vnode ( "<" , undefined , undefined , html , undefined , undefined )
}
2025-01-02 18:47:57 +01:00
hyperscript . fragment = function ( ) {
2019-01-17 09:50:05 +01:00
var vnode2 = hyperscriptVnode . apply ( 0 , arguments )
vnode2 . tag = "["
vnode2 . children = Vnode . normalizeChildren ( vnode2 . children )
return vnode2
2018-10-23 14:23:02 +02:00
}
2022-06-27 15:33:32 +02:00
/* global window */
2025-01-02 18:47:57 +01:00
var _13 = function ( $window ) {
2019-08-16 16:25:01 +02:00
var $doc = $window && $window . document
var currentRedraw
2018-10-23 14:23:02 +02:00
var nameSpace = {
svg : "http://www.w3.org/2000/svg" ,
math : "http://www.w3.org/1998/Math/MathML"
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function getNameSpace ( vnode3 ) {
return vnode3 . attrs && vnode3 . attrs . xmlns || nameSpace [ vnode3 . tag ]
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
//sanity check to discourage people from doing `vnode3.state = ...`
function checkState ( vnode3 , original ) {
2022-06-27 15:33:32 +02:00
if ( vnode3 . state !== original ) throw new Error ( "'vnode.state' must not be modified." )
2019-01-17 09:50:05 +01:00
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
//Note: the hook is passed as the `this` argument to allow proxying the
//arguments without requiring a full array allocation to do so. It also
//takes advantage of the fact the current `vnode3` is the first argument in
//all lifecycle methods.
function callHook ( vnode3 ) {
var original = vnode3 . state
try {
return this . apply ( original , arguments )
} finally {
checkState ( vnode3 , original )
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
// IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when
2019-08-16 16:25:01 +02:00
// inside an iframe. Catch and swallow this error, and heavy-handidly return null.
2019-01-17 09:50:05 +01:00
function activeElement ( ) {
try {
return $doc . activeElement
} catch ( e ) {
return null
}
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//create
function createNodes ( parent , vnodes , start , end , hooks , nextSibling , ns ) {
for ( var i = start ; i < end ; i ++ ) {
2019-01-17 09:50:05 +01:00
var vnode3 = vnodes [ i ]
if ( vnode3 != null ) {
createNode ( parent , vnode3 , hooks , ns , nextSibling )
2018-10-23 14:23:02 +02:00
}
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function createNode ( parent , vnode3 , hooks , ns , nextSibling ) {
var tag = vnode3 . tag
2018-10-23 14:23:02 +02:00
if ( typeof tag === "string" ) {
2019-01-17 09:50:05 +01:00
vnode3 . state = { }
if ( vnode3 . attrs != null ) initLifecycle ( vnode3 . attrs , vnode3 , hooks )
2018-10-23 14:23:02 +02:00
switch ( tag ) {
2025-01-02 18:47:57 +01:00
case "#" :
createText ( parent , vnode3 , nextSibling ) ;
break
case "<" :
createHTML ( parent , vnode3 , ns , nextSibling ) ;
break
case "[" :
createFragment ( parent , vnode3 , hooks , ns , nextSibling ) ;
break
default :
createElement ( parent , vnode3 , hooks , ns , nextSibling )
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
else createComponent ( parent , vnode3 , hooks , ns , nextSibling )
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function createText ( parent , vnode3 , nextSibling ) {
vnode3 . dom = $doc . createTextNode ( vnode3 . children )
insertNode ( parent , vnode3 . dom , nextSibling )
}
2025-01-02 18:47:57 +01:00
var possibleParents = {
caption : "table" ,
thead : "table" ,
tbody : "table" ,
tfoot : "table" ,
tr : "tbody" ,
th : "tr" ,
td : "tr" ,
colgroup : "table" ,
col : "colgroup"
}
2019-01-17 09:50:05 +01:00
function createHTML ( parent , vnode3 , ns , nextSibling ) {
var match0 = vnode3 . children . match ( /^\s*?<(\w+)/im ) || [ ]
// not using the proper parent makes the child element(s) vanish.
// var div = document.createElement("div")
// div.innerHTML = "<td>i</td><td>j</td>"
// console.log(div.innerHTML)
// --> "ij", no <td> in sight.
var temp = $doc . createElement ( possibleParents [ match0 [ 1 ] ] || "div" )
if ( ns === "http://www.w3.org/2000/svg" ) {
temp . innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\">" + vnode3 . children + "</svg>"
temp = temp . firstChild
} else {
temp . innerHTML = vnode3 . children
}
vnode3 . dom = temp . firstChild
vnode3 . domSize = temp . childNodes . length
2019-08-22 09:55:48 +02:00
// Capture nodes to remove, so we don't confuse them.
vnode3 . instance = [ ]
2018-10-23 14:23:02 +02:00
var fragment = $doc . createDocumentFragment ( )
var child
while ( child = temp . firstChild ) {
2019-08-22 09:55:48 +02:00
vnode3 . instance . push ( child )
2018-10-23 14:23:02 +02:00
fragment . appendChild ( child )
}
insertNode ( parent , fragment , nextSibling )
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function createFragment ( parent , vnode3 , hooks , ns , nextSibling ) {
2018-10-23 14:23:02 +02:00
var fragment = $doc . createDocumentFragment ( )
2019-01-17 09:50:05 +01:00
if ( vnode3 . children != null ) {
2022-06-27 15:33:32 +02:00
var children2 = vnode3 . children
createNodes ( fragment , children2 , 0 , children2 . length , hooks , null , ns )
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
vnode3 . dom = fragment . firstChild
vnode3 . domSize = fragment . childNodes . length
2018-10-23 14:23:02 +02:00
insertNode ( parent , fragment , nextSibling )
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function createElement ( parent , vnode3 , hooks , ns , nextSibling ) {
var tag = vnode3 . tag
var attrs2 = vnode3 . attrs
2018-10-23 14:23:02 +02:00
var is = attrs2 && attrs2 . is
2019-01-17 09:50:05 +01:00
ns = getNameSpace ( vnode3 ) || ns
2018-10-23 14:23:02 +02:00
var element = ns ?
is ? $doc . createElementNS ( ns , tag , { is : is } ) : $doc . createElementNS ( ns , tag ) :
is ? $doc . createElement ( tag , { is : is } ) : $doc . createElement ( tag )
2019-01-17 09:50:05 +01:00
vnode3 . dom = element
2018-10-23 14:23:02 +02:00
if ( attrs2 != null ) {
2019-01-17 09:50:05 +01:00
setAttrs ( vnode3 , attrs2 , ns )
2018-10-23 14:23:02 +02:00
}
insertNode ( parent , element , nextSibling )
2019-08-16 16:25:01 +02:00
if ( ! maybeSetContentEditable ( vnode3 ) ) {
2019-01-17 09:50:05 +01:00
if ( vnode3 . children != null ) {
2022-06-27 15:33:32 +02:00
var children2 = vnode3 . children
createNodes ( element , children2 , 0 , children2 . length , hooks , null , ns )
2019-01-17 09:50:05 +01:00
if ( vnode3 . tag === "select" && attrs2 != null ) setLateSelectAttrs ( vnode3 , attrs2 )
2018-10-23 14:23:02 +02:00
}
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function initComponent ( vnode3 , hooks ) {
2018-10-23 14:23:02 +02:00
var sentinel
2019-01-17 09:50:05 +01:00
if ( typeof vnode3 . tag . view === "function" ) {
vnode3 . state = Object . create ( vnode3 . tag )
sentinel = vnode3 . state . view
if ( sentinel . $$reentrantLock$$ != null ) return
2018-10-23 14:23:02 +02:00
sentinel . $$reentrantLock$$ = true
} else {
2019-01-17 09:50:05 +01:00
vnode3 . state = void 0
sentinel = vnode3 . tag
if ( sentinel . $$reentrantLock$$ != null ) return
2018-10-23 14:23:02 +02:00
sentinel . $$reentrantLock$$ = true
2019-01-17 09:50:05 +01:00
vnode3 . state = ( vnode3 . tag . prototype != null && typeof vnode3 . tag . prototype . view === "function" ) ? new vnode3 . tag ( vnode3 ) : vnode3 . tag ( vnode3 )
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
initLifecycle ( vnode3 . state , vnode3 , hooks )
if ( vnode3 . attrs != null ) initLifecycle ( vnode3 . attrs , vnode3 , hooks )
vnode3 . instance = Vnode . normalize ( callHook . call ( vnode3 . state . view , vnode3 ) )
if ( vnode3 . instance === vnode3 ) throw Error ( "A view cannot return the vnode it received as argument" )
2018-10-23 14:23:02 +02:00
sentinel . $$reentrantLock$$ = null
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function createComponent ( parent , vnode3 , hooks , ns , nextSibling ) {
initComponent ( vnode3 , hooks )
if ( vnode3 . instance != null ) {
createNode ( parent , vnode3 . instance , hooks , ns , nextSibling )
vnode3 . dom = vnode3 . instance . dom
vnode3 . domSize = vnode3 . dom != null ? vnode3 . instance . domSize : 0
2025-01-02 18:47:57 +01:00
} else {
2019-01-17 09:50:05 +01:00
vnode3 . domSize = 0
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//update
2019-01-17 09:50:05 +01:00
/ * *
* @ param { Element | Fragment } parent - the parent element
2019-08-16 16:25:01 +02:00
* @ param { Vnode [ ] | null } old - the list of vnodes of the last ` render0() ` call for
2019-01-17 09:50:05 +01:00
* this part of the tree
2019-08-16 16:25:01 +02:00
* @ param { Vnode [ ] | null } vnodes - as above , but for the current ` render0() ` call .
* @ param { Function [ ] } hooks - an accumulator of post - render0 hooks ( oncreate / onupdate )
* @ param { Element | null } nextSibling - the next DOM node if we ' re dealing with a
2019-01-17 09:50:05 +01:00
* fragment that is not the last item in its
* parent
* @ param { 'svg' | 'math' | String | null } ns ) - the current XML namespace , if any
* @ returns void
* /
// This function diffs and patches lists of vnodes, both keyed and unkeyed.
//
// We will:
//
// 1. describe its general structure
// 2. focus on the diff algorithm optimizations
// 3. discuss DOM node operations.
// ## Overview:
//
// The updateNodes() function:
// - deals with trivial cases
// - determines whether the lists are keyed or unkeyed based on the first non-null node
// of each list.
// - diffs them and patches the DOM if needed (that's the brunt of the code)
// - manages the leftovers: after diffing, are there:
// - old nodes left to remove?
// - new nodes to insert?
// deal with them!
//
// The lists are only iterated over once, with an exception for the nodes in `old` that
// are visited in the fourth part of the diff and in the `removeNodes` loop.
// ## Diffing
//
// Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837
// may be good for context on longest increasing subsequence-based logic for moving nodes.
//
// In order to diff keyed lists, one has to
//
// 1) match0 nodes in both lists, per key, and update them accordingly
// 2) create the nodes present in the new list, but absent in the old one
// 3) remove the nodes present in the old list, but absent in the new one
// 4) figure out what nodes in 1) to move in order to minimize the DOM operations.
//
2019-08-16 16:25:01 +02:00
// To achieve 1) one can create a dictionary of keys => index (for the old list), then0 iterate
2019-01-17 09:50:05 +01:00
// over the new list and for each new vnode3, find the corresponding vnode3 in the old list using
// the map.
// 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new
// and must be created.
// For the removals, we actually remove the nodes that have been updated from the old list.
// The nodes that remain in that list after 1) and 2) have been performed can be safely removed.
// The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS)
// algorithm.
//
// the longest increasing subsequence is the list of nodes that can remain in place. Imagine going
// from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices
// corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would
// match0 the above lists, for example).
//
// In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We
// can update those nodes without moving them, and only call `insertNode` on `4` and `5`.
//
// @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually
// the longest increasing subsequence *of old nodes still present in the new list*).
//
// It is a general algorithm that is fireproof in all circumstances, but it requires the allocation
// and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`,
// the `LIS` and a temporary one to create the LIS).
//
// So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of
// the LIS and can be updated without moving them.
//
// If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with
// the exception of the last node if the list is fully reversed).
//
2019-08-16 16:25:01 +02:00
// ## Finding the next sibling.
2019-01-17 09:50:05 +01:00
//
// `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations.
2019-08-16 16:25:01 +02:00
// When the list is being traversed top-down, at any index, the DOM nodes up to the previous
2019-01-17 09:50:05 +01:00
// vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old
2019-08-16 16:25:01 +02:00
// list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`.
2019-01-17 09:50:05 +01:00
//
// In the other scenarios (swaps, upwards traversal, map-based diff),
// the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the
// bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node
2019-08-16 16:25:01 +02:00
// as the next sibling (cached in the `nextSibling` variable).
2019-01-17 09:50:05 +01:00
// ## DOM node moves
//
// In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However,
// 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 runs0 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 ) {
2018-10-23 14:23:02 +02:00
if ( old === vnodes || old == null && vnodes == null ) return
2019-01-17 09:50:05 +01:00
else if ( old == null || old . length === 0 ) createNodes ( parent , vnodes , 0 , vnodes . length , hooks , nextSibling , ns )
2019-08-22 09:55:48 +02:00
else if ( vnodes == null || vnodes . length === 0 ) removeNodes ( parent , old , 0 , old . length )
2018-10-23 14:23:02 +02:00
else {
2019-08-16 16:25:01 +02:00
var isOldKeyed = old [ 0 ] != null && old [ 0 ] . key != null
var isKeyed0 = vnodes [ 0 ] != null && vnodes [ 0 ] . key != null
var start = 0 , oldStart = 0
if ( ! isOldKeyed ) while ( oldStart < old . length && old [ oldStart ] == null ) oldStart ++
if ( ! isKeyed0 ) while ( start < vnodes . length && vnodes [ start ] == null ) start ++
if ( isOldKeyed !== isKeyed0 ) {
2019-08-22 09:55:48 +02:00
removeNodes ( parent , old , oldStart , old . length )
2019-01-17 09:50:05 +01:00
createNodes ( parent , vnodes , start , vnodes . length , hooks , nextSibling , ns )
2019-08-16 16:25:01 +02:00
} else if ( ! isKeyed0 ) {
// Don't index past the end of either list (causes deopts).
2019-01-17 09:50:05 +01:00
var commonLength = old . length < vnodes . length ? old . length : vnodes . length
2019-08-16 16:25:01 +02:00
// Rewind if necessary to the first non-null index on either side.
2019-01-17 09:50:05 +01:00
// We could alternatively either explicitly create or remove nodes when `start !== oldStart`
// but that would be optimizing for sparse lists which are more rare than dense ones.
start = start < oldStart ? start : oldStart
for ( ; start < commonLength ; start ++ ) {
o = old [ start ]
v = vnodes [ start ]
if ( o === v || o == null && v == null ) continue
else if ( o == null ) createNode ( parent , v , hooks , ns , getNextSibling ( old , start + 1 , nextSibling ) )
2019-08-22 09:55:48 +02:00
else if ( v == null ) removeNode ( parent , o )
2019-01-17 09:50:05 +01:00
else updateNode ( parent , o , v , hooks , getNextSibling ( old , start + 1 , nextSibling ) , ns )
2018-10-23 14:23:02 +02:00
}
2019-08-22 09:55:48 +02:00
if ( old . length > commonLength ) removeNodes ( parent , old , start , old . length )
2019-01-17 09:50:05 +01:00
if ( vnodes . length > commonLength ) createNodes ( parent , vnodes , start , vnodes . length , hooks , nextSibling , ns )
} else {
// keyed diff
var oldEnd = old . length - 1 , end = vnodes . length - 1 , map , o , v , oe , ve , topSibling
// bottom-up
while ( oldEnd >= oldStart && end >= start ) {
oe = old [ oldEnd ]
ve = vnodes [ end ]
2019-08-16 16:25:01 +02:00
if ( oe . key !== ve . key ) break
if ( oe !== ve ) updateNode ( parent , oe , ve , hooks , nextSibling , ns )
if ( ve . dom != null ) nextSibling = ve . dom
oldEnd -- , end --
2019-01-17 09:50:05 +01:00
}
// top-down
while ( oldEnd >= oldStart && end >= start ) {
o = old [ oldStart ]
v = vnodes [ start ]
2019-08-16 16:25:01 +02:00
if ( o . key !== v . key ) break
oldStart ++ , start ++
if ( o !== v ) updateNode ( parent , o , v , hooks , getNextSibling ( old , oldStart , nextSibling ) , ns )
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
// swaps and list reversals
while ( oldEnd >= oldStart && end >= start ) {
2019-08-16 16:25:01 +02:00
if ( start === end ) break
if ( o . key !== ve . key || oe . key !== v . key ) break
topSibling = getNextSibling ( old , oldStart , nextSibling )
2019-08-22 09:55:48 +02:00
moveNodes ( parent , oe , topSibling )
2019-08-16 16:25:01 +02:00
if ( oe !== v ) updateNode ( parent , oe , v , hooks , topSibling , ns )
2019-08-22 09:55:48 +02:00
if ( ++ start <= -- end ) moveNodes ( parent , o , nextSibling )
2019-08-16 16:25:01 +02:00
if ( o !== ve ) updateNode ( parent , o , ve , hooks , nextSibling , ns )
if ( ve . dom != null ) nextSibling = ve . dom
2025-01-02 18:47:57 +01:00
oldStart ++ ;
oldEnd --
2019-01-17 09:50:05 +01:00
oe = old [ oldEnd ]
ve = vnodes [ end ]
o = old [ oldStart ]
v = vnodes [ start ]
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
// bottom up once again
while ( oldEnd >= oldStart && end >= start ) {
2019-08-16 16:25:01 +02:00
if ( oe . key !== ve . key ) break
if ( oe !== ve ) updateNode ( parent , oe , ve , hooks , nextSibling , ns )
if ( ve . dom != null ) nextSibling = ve . dom
oldEnd -- , end --
2019-01-17 09:50:05 +01:00
oe = old [ oldEnd ]
ve = vnodes [ end ]
}
2019-08-22 09:55:48 +02:00
if ( start > end ) removeNodes ( parent , old , oldStart , oldEnd + 1 )
2019-01-17 09:50:05 +01:00
else if ( oldStart > oldEnd ) createNodes ( parent , vnodes , start , end + 1 , hooks , nextSibling , ns )
2018-10-23 14:23:02 +02:00
else {
2019-01-17 09:50:05 +01:00
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
2025-01-02 18:47:57 +01:00
var originalNextSibling = nextSibling , vnodesLength = end - start + 1 , oldIndices = new Array ( vnodesLength ) , li = 0 , i = 0 ,
pos = 2147483647 , matched = 0 , map , lisIndices
2019-01-17 09:50:05 +01:00
for ( i = 0 ; i < vnodesLength ; i ++ ) oldIndices [ i ] = - 1
for ( i = end ; i >= start ; i -- ) {
if ( map == null ) map = getKeyMap ( old , oldStart , oldEnd + 1 )
ve = vnodes [ i ]
2019-08-16 16:25:01 +02:00
var oldIndex = map [ ve . key ]
if ( oldIndex != null ) {
pos = ( oldIndex < pos ) ? oldIndex : - 1 // becomes -1 if nodes were re-ordered
2025-01-02 18:47:57 +01:00
oldIndices [ i - start ] = oldIndex
2019-08-16 16:25:01 +02:00
oe = old [ oldIndex ]
old [ oldIndex ] = null
if ( oe !== ve ) updateNode ( parent , oe , ve , hooks , nextSibling , ns )
if ( ve . dom != null ) nextSibling = ve . dom
matched ++
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
}
nextSibling = originalNextSibling
2019-08-22 09:55:48 +02:00
if ( matched !== oldEnd - oldStart + 1 ) removeNodes ( parent , old , oldStart , oldEnd + 1 )
2019-01-17 09:50:05 +01:00
if ( matched === 0 ) createNodes ( parent , vnodes , start , end + 1 , hooks , nextSibling , ns )
else {
if ( pos === - 1 ) {
// the indices of the indices of the items that are part of the
// longest increasing subsequence in the oldIndices list
lisIndices = makeLisIndices ( oldIndices )
li = lisIndices . length - 1
for ( i = end ; i >= start ; i -- ) {
v = vnodes [ i ]
if ( oldIndices [ i - start ] === - 1 ) createNode ( parent , v , hooks , ns , nextSibling )
else {
if ( lisIndices [ li ] === i - start ) li --
2019-08-22 09:55:48 +02:00
else moveNodes ( parent , v , nextSibling )
2019-01-17 09:50:05 +01:00
}
if ( v . dom != null ) nextSibling = vnodes [ i ] . dom
}
} else {
for ( i = end ; i >= start ; i -- ) {
v = vnodes [ i ]
2025-01-02 18:47:57 +01:00
if ( oldIndices [ i - start ] === - 1 ) createNode ( parent , v , hooks , ns , nextSibling )
2019-01-17 09:50:05 +01:00
if ( v . dom != null ) nextSibling = vnodes [ i ] . dom
}
2018-10-23 14:23:02 +02:00
}
}
}
}
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateNode ( parent , old , vnode3 , hooks , nextSibling , ns ) {
var oldTag = old . tag , tag = vnode3 . tag
2018-10-23 14:23:02 +02:00
if ( oldTag === tag ) {
2019-01-17 09:50:05 +01:00
vnode3 . state = old . state
vnode3 . events = old . events
if ( shouldNotUpdate ( vnode3 , old ) ) return
2018-10-23 14:23:02 +02:00
if ( typeof oldTag === "string" ) {
2019-01-17 09:50:05 +01:00
if ( vnode3 . attrs != null ) {
updateLifecycle ( vnode3 . attrs , vnode3 , hooks )
2018-10-23 14:23:02 +02:00
}
switch ( oldTag ) {
2025-01-02 18:47:57 +01:00
case "#" :
updateText ( old , vnode3 ) ;
break
case "<" :
updateHTML ( parent , old , vnode3 , ns , nextSibling ) ;
break
case "[" :
updateFragment ( parent , old , vnode3 , hooks , nextSibling , ns ) ;
break
default :
updateElement ( old , vnode3 , hooks , ns )
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
else updateComponent ( parent , old , vnode3 , hooks , nextSibling , ns )
2018-10-23 14:23:02 +02:00
}
else {
2019-08-22 09:55:48 +02:00
removeNode ( parent , old )
2019-01-17 09:50:05 +01:00
createNode ( parent , vnode3 , hooks , ns , nextSibling )
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateText ( old , vnode3 ) {
if ( old . children . toString ( ) !== vnode3 . children . toString ( ) ) {
old . dom . nodeValue = vnode3 . children
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
vnode3 . dom = old . dom
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateHTML ( parent , old , vnode3 , ns , nextSibling ) {
if ( old . children !== vnode3 . children ) {
2019-08-22 09:55:48 +02:00
removeHTML ( parent , old )
2019-01-17 09:50:05 +01:00
createHTML ( parent , vnode3 , ns , nextSibling )
2025-01-02 18:47:57 +01:00
} else {
2019-08-22 09:55:48 +02:00
vnode3 . dom = old . dom
vnode3 . domSize = old . domSize
vnode3 . instance = old . instance
}
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateFragment ( parent , old , vnode3 , hooks , nextSibling , ns ) {
updateNodes ( parent , old . children , vnode3 . children , hooks , nextSibling , ns )
2022-06-27 15:33:32 +02:00
var domSize = 0 , children2 = vnode3 . children
2019-01-17 09:50:05 +01:00
vnode3 . dom = null
2022-06-27 15:33:32 +02:00
if ( children2 != null ) {
for ( var i = 0 ; i < children2 . length ; i ++ ) {
var child = children2 [ i ]
2018-10-23 14:23:02 +02:00
if ( child != null && child . dom != null ) {
2019-01-17 09:50:05 +01:00
if ( vnode3 . dom == null ) vnode3 . dom = child . dom
2018-10-23 14:23:02 +02:00
domSize += child . domSize || 1
}
}
2019-01-17 09:50:05 +01:00
if ( domSize !== 1 ) vnode3 . domSize = domSize
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateElement ( old , vnode3 , hooks , ns ) {
var element = vnode3 . dom = old . dom
ns = getNameSpace ( vnode3 ) || ns
if ( vnode3 . tag === "textarea" ) {
if ( vnode3 . attrs == null ) vnode3 . attrs = { }
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
updateAttrs ( vnode3 , old . attrs , vnode3 . attrs , ns )
2019-08-16 16:25:01 +02:00
if ( ! maybeSetContentEditable ( vnode3 ) ) {
2022-06-27 15:33:32 +02:00
updateNodes ( element , old . children , vnode3 . children , hooks , null , ns )
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateComponent ( parent , old , vnode3 , hooks , nextSibling , ns ) {
vnode3 . instance = Vnode . normalize ( callHook . call ( vnode3 . state . view , vnode3 ) )
if ( vnode3 . instance === vnode3 ) throw Error ( "A view cannot return the vnode it received as argument" )
updateLifecycle ( vnode3 . state , vnode3 , hooks )
if ( vnode3 . attrs != null ) updateLifecycle ( vnode3 . attrs , vnode3 , hooks )
if ( vnode3 . instance != null ) {
if ( old . instance == null ) createNode ( parent , vnode3 . instance , hooks , ns , nextSibling )
else updateNode ( parent , old . instance , vnode3 . instance , hooks , nextSibling , ns )
vnode3 . dom = vnode3 . instance . dom
vnode3 . domSize = vnode3 . instance . domSize
2025-01-02 18:47:57 +01:00
} else if ( old . instance != null ) {
2019-08-22 09:55:48 +02:00
removeNode ( parent , old . instance )
2019-01-17 09:50:05 +01:00
vnode3 . dom = undefined
vnode3 . domSize = 0
2025-01-02 18:47:57 +01:00
} else {
2019-01-17 09:50:05 +01:00
vnode3 . dom = old . dom
vnode3 . domSize = old . domSize
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function getKeyMap ( vnodes , start , end ) {
var map = Object . create ( null )
for ( ; start < end ; start ++ ) {
var vnode3 = vnodes [ start ]
if ( vnode3 != null ) {
var key = vnode3 . key
if ( key != null ) map [ key ] = start
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
return map
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
// Lifted from ivi https://github.com/ivijs/ivi/
// takes a list of unique numbers (-1 is special and can
// occur multiple times) and returns an array with the indices
// of the items that are part of the longest increasing
2022-06-27 15:33:32 +02:00
// subsequence
2019-08-16 16:25:01 +02:00
var lisTemp = [ ]
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function makeLisIndices ( a ) {
2019-08-16 16:25:01 +02:00
var result = [ 0 ]
var u = 0 , v = 0 , i = 0
var il = lisTemp . length = a . length
for ( var i = 0 ; i < il ; i ++ ) lisTemp [ i ] = a [ i ]
for ( var i = 0 ; i < il ; ++ i ) {
if ( a [ i ] === - 1 ) continue
2019-01-17 09:50:05 +01:00
var j = result [ result . length - 1 ]
if ( a [ j ] < a [ i ] ) {
2019-08-16 16:25:01 +02:00
lisTemp [ i ] = j
2019-01-17 09:50:05 +01:00
result . push ( i )
continue
}
u = 0
v = result . length - 1
while ( u < v ) {
2019-08-16 16:25:01 +02:00
// Fast integer average without overflow.
// eslint-disable-next-line no-bitwise
var c = ( u >>> 1 ) + ( v >>> 1 ) + ( u & v & 1 )
2019-01-17 09:50:05 +01:00
if ( a [ result [ c ] ] < a [ i ] ) {
u = c + 1
2025-01-02 18:47:57 +01:00
} else {
2019-01-17 09:50:05 +01:00
v = c
}
}
if ( a [ i ] < a [ result [ u ] ] ) {
2019-08-16 16:25:01 +02:00
if ( u > 0 ) lisTemp [ i ] = result [ u - 1 ]
2019-01-17 09:50:05 +01:00
result [ u ] = i
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
u = result . length
v = result [ u - 1 ]
while ( u -- > 0 ) {
result [ u ] = v
2019-08-16 16:25:01 +02:00
v = lisTemp [ v ]
2019-01-17 09:50:05 +01:00
}
2019-08-16 16:25:01 +02:00
lisTemp . length = 0
2019-01-17 09:50:05 +01:00
return result
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function getNextSibling ( vnodes , i , nextSibling ) {
for ( ; i < vnodes . length ; i ++ ) {
if ( vnodes [ i ] != null && vnodes [ i ] . dom != null ) return vnodes [ i ] . dom
}
return nextSibling
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
// This covers a really specific edge case:
// - Parent node is keyed and contains child
// - Child is removed, returns unresolved promise0 in `onbeforeremove`
// - Parent node is moved in keyed diff
2022-06-27 15:33:32 +02:00
// - Remaining children2 still need moved appropriately
2019-08-22 09:55:48 +02:00
//
// Ideally, I'd track removed nodes as well, but that introduces a lot more
2022-06-27 15:33:32 +02:00
// complexity and I'm2 not exactly interested in doing that.
2019-08-22 09:55:48 +02:00
function moveNodes ( parent , vnode3 , nextSibling ) {
var frag = $doc . createDocumentFragment ( )
moveChildToFrag ( parent , frag , vnode3 )
insertNode ( parent , frag , nextSibling )
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
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
} else {
for ( var i = 0 ; i < vnode3 . children . length ; i ++ ) {
var child = vnode3 . children [ i ]
if ( child != null ) moveChildToFrag ( parent , frag , child )
}
}
break
}
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function insertNode ( parent , dom , nextSibling ) {
2019-01-17 09:50:05 +01:00
if ( nextSibling != null ) parent . insertBefore ( dom , nextSibling )
2018-10-23 14:23:02 +02:00
else parent . appendChild ( dom )
2025-01-02 18:47:57 +01:00
}
2019-08-16 16:25:01 +02:00
function maybeSetContentEditable ( vnode3 ) {
if ( vnode3 . attrs == null || (
vnode3 . attrs . contenteditable == null && // attribute
vnode3 . attrs . contentEditable == null // property
2019-08-22 09:55:48 +02:00
) ) return false
2022-06-27 15:33:32 +02:00
var children2 = vnode3 . children
if ( children2 != null && children2 . length === 1 && children2 [ 0 ] . tag === "<" ) {
var content = children2 [ 0 ] . children
2019-01-17 09:50:05 +01:00
if ( vnode3 . dom . innerHTML !== content ) vnode3 . dom . innerHTML = content
2025-01-02 18:47:57 +01:00
} else if ( children2 != null && children2 . length !== 0 ) throw new Error ( "Child node of a contenteditable must be trusted." )
2019-08-22 09:55:48 +02:00
return true
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//remove
2019-08-22 09:55:48 +02:00
function removeNodes ( parent , vnodes , start , end ) {
2018-10-23 14:23:02 +02:00
for ( var i = start ; i < end ; i ++ ) {
2019-01-17 09:50:05 +01:00
var vnode3 = vnodes [ i ]
2019-08-22 09:55:48 +02:00
if ( vnode3 != null ) removeNode ( parent , vnode3 )
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
function removeNode ( parent , vnode3 ) {
var mask = 0
2019-01-17 09:50:05 +01:00
var original = vnode3 . state
2019-08-22 09:55:48 +02:00
var stateResult , attrsResult
2019-01-17 09:50:05 +01:00
if ( typeof vnode3 . tag !== "string" && typeof vnode3 . state . onbeforeremove === "function" ) {
var result = callHook . call ( vnode3 . state . onbeforeremove , vnode3 )
2018-10-23 14:23:02 +02:00
if ( result != null && typeof result . then === "function" ) {
2019-08-22 09:55:48 +02:00
mask = 1
stateResult = result
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
if ( vnode3 . attrs && typeof vnode3 . attrs . onbeforeremove === "function" ) {
var result = callHook . call ( vnode3 . attrs . onbeforeremove , vnode3 )
2018-10-23 14:23:02 +02:00
if ( result != null && typeof result . then === "function" ) {
2019-08-22 09:55:48 +02:00
// eslint-disable-next-line no-bitwise
mask |= 2
attrsResult = result
2018-10-23 14:23:02 +02:00
}
}
2019-08-22 09:55:48 +02:00
checkState ( vnode3 , original )
// If we can, try to fast-path it and avoid all the overhead of awaiting
if ( ! mask ) {
onremove ( vnode3 )
removeChild ( parent , vnode3 )
} else {
if ( stateResult != null ) {
var next = function ( ) {
// eslint-disable-next-line no-bitwise
2025-01-02 18:47:57 +01:00
if ( mask & 1 ) {
mask &= 2 ;
if ( ! mask ) reallyRemove ( )
}
2019-08-22 09:55:48 +02:00
}
stateResult . then ( next , next )
}
if ( attrsResult != null ) {
var next = function ( ) {
// eslint-disable-next-line no-bitwise
2025-01-02 18:47:57 +01:00
if ( mask & 2 ) {
mask &= 1 ;
if ( ! mask ) reallyRemove ( )
}
2019-08-22 09:55:48 +02:00
}
attrsResult . then ( next , next )
}
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
function reallyRemove ( ) {
checkState ( vnode3 , original )
onremove ( vnode3 )
removeChild ( parent , vnode3 )
}
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
function removeHTML ( parent , vnode3 ) {
for ( var i = 0 ; i < vnode3 . instance . length ; i ++ ) {
parent . removeChild ( vnode3 . instance [ i ] )
}
}
2025-01-02 18:47:57 +01:00
2019-08-22 09:55:48 +02:00
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 !== "[" ) {
2019-01-17 09:50:05 +01:00
parent . removeChild ( vnode3 . dom )
2019-08-22 09:55:48 +02:00
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 )
}
2018-10-23 14:23:02 +02:00
}
}
2019-08-22 09:55:48 +02:00
break
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function onremove ( vnode3 ) {
if ( typeof vnode3 . tag !== "string" && typeof vnode3 . state . onremove === "function" ) callHook . call ( vnode3 . state . onremove , vnode3 )
if ( vnode3 . attrs && typeof vnode3 . attrs . onremove === "function" ) callHook . call ( vnode3 . attrs . onremove , vnode3 )
if ( typeof vnode3 . tag !== "string" ) {
if ( vnode3 . instance != null ) onremove ( vnode3 . instance )
2018-10-23 14:23:02 +02:00
} else {
2022-06-27 15:33:32 +02:00
var children2 = vnode3 . children
if ( Array . isArray ( children2 ) ) {
for ( var i = 0 ; i < children2 . length ; i ++ ) {
var child = children2 [ i ]
2018-10-23 14:23:02 +02:00
if ( child != null ) onremove ( child )
}
}
}
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//attrs2
2019-01-17 09:50:05 +01:00
function setAttrs ( vnode3 , attrs2 , ns ) {
2022-06-27 15:33:32 +02:00
// If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur.
//
// Also, the DOM does things to inputs based on the value, so it needs set first.
// See: https://github.com/MithrilJS/mithril.js/issues/2622
if ( vnode3 . tag === "input" && attrs2 . type != null ) vnode3 . dom . setAttribute ( "type" , attrs2 . type )
var isFileInput = attrs2 != null && vnode3 . tag === "input" && attrs2 . type === "file"
2019-01-17 09:50:05 +01:00
for ( var key in attrs2 ) {
2022-06-27 15:33:32 +02:00
setAttr ( vnode3 , key , null , attrs2 [ key ] , ns , isFileInput )
2019-01-17 09:50:05 +01:00
}
}
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function setAttr ( vnode3 , key , old , value , ns , isFileInput ) {
if ( key === "key" || key === "is" || value == null || isLifecycleMethod ( key ) || ( old === value && ! isFormAttribute ( vnode3 , key ) ) && typeof value !== "object" || key === "type" && vnode3 . tag === "input" ) return
2019-01-17 09:50:05 +01:00
if ( key [ 0 ] === "o" && key [ 1 ] === "n" ) return updateEvent ( vnode3 , key , value )
if ( key . slice ( 0 , 6 ) === "xlink:" ) vnode3 . dom . setAttributeNS ( "http://www.w3.org/1999/xlink" , key . slice ( 6 ) , value )
else if ( key === "style" ) updateStyle ( vnode3 . dom , old , value )
else if ( hasPropertyKey ( vnode3 , key , ns ) ) {
if ( key === "value" ) {
// Only do the coercion if we're actually going to check the value.
/* eslint-disable no-implicit-coercion */
2018-10-23 14:23:02 +02:00
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
2022-06-27 15:33:32 +02:00
//setting input[type0=file][value] to same value causes an error to be generated if it's non-empty
if ( ( vnode3 . tag === "input" || vnode3 . tag === "textarea" ) && vnode3 . dom . value === "" + value && ( isFileInput || vnode3 . dom === activeElement ( ) ) ) return
2018-10-23 14:23:02 +02:00
//setting select[value] to same value while having select open blinks select dropdown in Chrome
2019-01-17 09:50:05 +01:00
if ( vnode3 . tag === "select" && old !== null && vnode3 . dom . value === "" + value ) return
2018-10-23 14:23:02 +02:00
//setting option[value] to same value while having select open blinks select dropdown in Chrome
2019-01-17 09:50:05 +01:00
if ( vnode3 . tag === "option" && old !== null && vnode3 . dom . value === "" + value ) return
2022-06-27 15:33:32 +02:00
//setting input[type0=file][value] to different value is an error if it's non-empty
// Not ideal, but it at least works around the most common source of uncaught exceptions for now.
2025-01-02 18:47:57 +01:00
if ( isFileInput && "" + value !== "" ) {
console . error ( "`value` is read-only on file inputs!" ) ;
return
}
2019-01-17 09:50:05 +01:00
/* eslint-enable no-implicit-coercion */
2018-10-23 14:23:02 +02:00
}
2022-06-27 15:33:32 +02:00
vnode3 . dom [ key ] = value
2019-01-17 09:50:05 +01:00
} else {
2018-10-23 14:23:02 +02:00
if ( typeof value === "boolean" ) {
2019-01-17 09:50:05 +01:00
if ( value ) vnode3 . dom . setAttribute ( key , "" )
else vnode3 . dom . removeAttribute ( key )
2025-01-02 18:47:57 +01:00
}
2019-01-17 09:50:05 +01:00
else vnode3 . dom . setAttribute ( key === "className" ? "class" : key , value )
2025-01-02 18:47:57 +01:00
}
2019-01-17 09:50:05 +01:00
}
function removeAttr ( vnode3 , key , old , ns ) {
if ( key === "key" || key === "is" || old == null || isLifecycleMethod ( key ) ) return
2022-06-27 15:33:32 +02:00
if ( key [ 0 ] === "o" && key [ 1 ] === "n" ) updateEvent ( vnode3 , key , undefined )
2019-01-17 09:50:05 +01:00
else if ( key === "style" ) updateStyle ( vnode3 . dom , old , null )
else if (
hasPropertyKey ( vnode3 , key , ns )
&& key !== "className"
2022-06-27 15:33:32 +02:00
&& key !== "title" // creates "null" as title
2019-01-17 09:50:05 +01:00
&& ! ( key === "value" && (
vnode3 . tag === "option"
|| vnode3 . tag === "select" && vnode3 . dom . selectedIndex === - 1 && vnode3 . dom === activeElement ( )
) )
&& ! ( vnode3 . tag === "input" && key === "type" )
) {
vnode3 . dom [ key ] = null
} else {
var nsLastIndex = key . indexOf ( ":" )
if ( nsLastIndex !== - 1 ) key = key . slice ( nsLastIndex + 1 )
if ( old !== false ) vnode3 . dom . removeAttribute ( key === "className" ? "class" : key )
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function setLateSelectAttrs ( vnode3 , attrs2 ) {
if ( "value" in attrs2 ) {
2025-01-02 18:47:57 +01:00
if ( attrs2 . value === null ) {
2019-01-17 09:50:05 +01:00
if ( vnode3 . dom . selectedIndex !== - 1 ) vnode3 . dom . value = null
} else {
var normalized = "" + attrs2 . value // eslint-disable-line no-implicit-coercion
if ( vnode3 . dom . value !== normalized || vnode3 . dom . selectedIndex === - 1 ) {
vnode3 . dom . value = normalized
}
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
if ( "selectedIndex" in attrs2 ) setAttr ( vnode3 , "selectedIndex" , null , attrs2 . selectedIndex , undefined )
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateAttrs ( vnode3 , old , attrs2 , ns ) {
2022-06-27 15:33:32 +02:00
if ( old && old === attrs2 ) {
console . warn ( "Don't reuse attrs object, use new object for every redraw, this will throw in next major" )
}
2018-10-23 14:23:02 +02:00
if ( attrs2 != null ) {
2022-06-27 15:33:32 +02:00
// If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur.
//
// Also, the DOM does things to inputs based on the value, so it needs set first.
// See: https://github.com/MithrilJS/mithril.js/issues/2622
if ( vnode3 . tag === "input" && attrs2 . type != null ) vnode3 . dom . setAttribute ( "type" , attrs2 . type )
var isFileInput = vnode3 . tag === "input" && attrs2 . type === "file"
2019-01-17 09:50:05 +01:00
for ( var key in attrs2 ) {
2022-06-27 15:33:32 +02:00
setAttr ( vnode3 , key , old && old [ key ] , attrs2 [ key ] , ns , isFileInput )
2018-10-23 14:23:02 +02:00
}
}
2019-01-17 09:50:05 +01:00
var val
2018-10-23 14:23:02 +02:00
if ( old != null ) {
2019-01-17 09:50:05 +01:00
for ( var key in old ) {
if ( ( ( val = old [ key ] ) != null ) && ( attrs2 == null || attrs2 [ key ] == null ) ) {
removeAttr ( vnode3 , key , val , ns )
2018-10-23 14:23:02 +02:00
}
}
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function isFormAttribute ( vnode3 , attr ) {
2025-01-02 18:47:57 +01:00
return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode3 . dom === activeElement ( ) || vnode3 . tag
=== "option" && vnode3 . dom . parentNode === $doc . activeElement
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function isLifecycleMethod ( attr ) {
return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function hasPropertyKey ( vnode3 , key , ns ) {
// Filter out namespaced keys
return ns === undefined && (
// If it's a custom element, just keep it.
vnode3 . tag . indexOf ( "-" ) > - 1 || vnode3 . attrs != null && vnode3 . attrs . is ||
// If it's a normal element, let's try to avoid a few browser bugs.
key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height" // && key !== "type"
// Defer the property check until *after* we check everything.
) && key in vnode3 . dom
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//style
2019-01-17 09:50:05 +01:00
var uppercaseRegex = /[A-Z]/g
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function toLowerCase ( capital ) { return "-" + capital . toLowerCase ( ) }
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function normalizeKey ( key ) {
return key [ 0 ] === "-" && key [ 1 ] === "-" ? key :
key === "cssFloat" ? "float" :
key . replace ( uppercaseRegex , toLowerCase )
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
function updateStyle ( element , old , style ) {
2019-01-17 09:50:05 +01:00
if ( old === style ) {
// Styles are equivalent, do nothing.
} else if ( style == null ) {
// New style is missing, just clear it.
element . style . cssText = ""
} else if ( typeof style !== "object" ) {
// New style is a string, let engine deal with patching.
element . style . cssText = style
} else if ( old == null || typeof old !== "object" ) {
// `old` is missing or a string, `style` is an object.
element . style . cssText = ""
// Add new style properties
for ( var key in style ) {
var value = style [ key ]
if ( value != null ) element . style . setProperty ( normalizeKey ( key ) , String ( value ) )
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
} else {
// Both old & new are (different) objects.
// Update style properties that have changed
for ( var key in style ) {
var value = style [ key ]
if ( value != null && ( value = String ( value ) ) !== String ( old [ key ] ) ) {
element . style . setProperty ( normalizeKey ( key ) , value )
}
}
// Remove style properties that no longer exist
for ( var key in old ) {
if ( old [ key ] != null && style [ key ] == null ) {
element . style . removeProperty ( normalizeKey ( key ) )
2018-10-23 14:23:02 +02:00
}
}
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
// Here's an explanation of how this works:
// 1. The event names are always (by design) prefixed by `on`.
// 2. The EventListener interface accepts either a function or an object
2019-08-16 16:25:01 +02:00
// with a `handleEvent` method.
2019-01-17 09:50:05 +01:00
// 3. The object does not inherit from `Object.prototype`, to avoid
// any potential interference with that (e.g. setters).
// 4. The event name is remapped to the handler0 before calling it.
// 5. In function-based event handlers, `ev.target === this`. We replicate
// that below.
// 6. In function-based event handlers, `return false` prevents the default
// action and stops event propagation. We replicate that below.
2019-08-16 16:25:01 +02:00
function EventDict ( ) {
// Save this, so the current redraw is correctly tracked.
this . _ = currentRedraw
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
EventDict . prototype = Object . create ( null )
EventDict . prototype . handleEvent = function ( ev ) {
var handler0 = this [ "on" + ev . type ]
var result
if ( typeof handler0 === "function" ) result = handler0 . call ( ev . currentTarget , ev )
else if ( typeof handler0 . handleEvent === "function" ) handler0 . handleEvent ( ev )
2019-08-16 16:25:01 +02:00
if ( this . _ && ev . redraw !== false ) ( 0 , this . _ ) ( )
2019-01-17 09:50:05 +01:00
if ( result === false ) {
ev . preventDefault ( )
ev . stopPropagation ( )
}
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//event
2019-01-17 09:50:05 +01:00
function updateEvent ( vnode3 , key , value ) {
if ( vnode3 . events != null ) {
2022-06-27 15:33:32 +02:00
vnode3 . events . _ = currentRedraw
2019-01-17 09:50:05 +01:00
if ( vnode3 . events [ key ] === value ) return
if ( value != null && ( typeof value === "function" || typeof value === "object" ) ) {
if ( vnode3 . events [ key ] == null ) vnode3 . dom . addEventListener ( key . slice ( 2 ) , vnode3 . events , false )
vnode3 . events [ key ] = value
} else {
if ( vnode3 . events [ key ] != null ) vnode3 . dom . removeEventListener ( key . slice ( 2 ) , vnode3 . events , false )
vnode3 . events [ key ] = undefined
2018-10-23 14:23:02 +02:00
}
2019-01-17 09:50:05 +01:00
} else if ( value != null && ( typeof value === "function" || typeof value === "object" ) ) {
vnode3 . events = new EventDict ( )
vnode3 . dom . addEventListener ( key . slice ( 2 ) , vnode3 . events , false )
vnode3 . events [ key ] = value
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2018-10-23 14:23:02 +02:00
//lifecycle
2019-01-17 09:50:05 +01:00
function initLifecycle ( source , vnode3 , hooks ) {
if ( typeof source . oninit === "function" ) callHook . call ( source . oninit , vnode3 )
if ( typeof source . oncreate === "function" ) hooks . push ( callHook . bind ( source . oncreate , vnode3 ) )
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function updateLifecycle ( source , vnode3 , hooks ) {
if ( typeof source . onupdate === "function" ) hooks . push ( callHook . bind ( source . onupdate , vnode3 ) )
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function shouldNotUpdate ( vnode3 , old ) {
do {
if ( vnode3 . attrs != null && typeof vnode3 . attrs . onbeforeupdate === "function" ) {
var force = callHook . call ( vnode3 . attrs . onbeforeupdate , vnode3 , old )
if ( force !== undefined && ! force ) break
}
if ( typeof vnode3 . tag !== "string" && typeof vnode3 . state . onbeforeupdate === "function" ) {
var force = callHook . call ( vnode3 . state . onbeforeupdate , vnode3 , old )
if ( force !== undefined && ! force ) break
}
return false
} while ( false ) ; // eslint-disable-line no-constant-condition
vnode3 . dom = old . dom
vnode3 . domSize = old . domSize
vnode3 . instance = old . instance
2019-08-16 16:25:01 +02:00
// One would think having the actual latest attributes would be ideal,
// but it doesn't let us properly diff based on our current internal
// representation. We have to save not only the old DOM info, but also
// the attributes used to create it, as we diff *that*, not against the
// DOM directly (with a few exceptions in `setAttr`). And, of course, we
2022-06-27 15:33:32 +02:00
// need to save the children2 and text as they are conceptually not
2019-08-16 16:25:01 +02:00
// unlike special "attributes" internally.
vnode3 . attrs = old . attrs
vnode3 . children = old . children
vnode3 . text = old . text
2019-01-17 09:50:05 +01:00
return true
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
var currentDOM
2025-01-02 18:47:57 +01:00
return function ( dom , vnodes , redraw ) {
2022-06-27 15:33:32 +02:00
if ( ! dom ) throw new TypeError ( "DOM element being rendered to does not exist." )
if ( currentDOM != null && dom . contains ( currentDOM ) ) {
throw new TypeError ( "Node is currently being rendered to and thus is locked." )
}
var prevRedraw = currentRedraw
var prevDOM = currentDOM
2018-10-23 14:23:02 +02:00
var hooks = [ ]
2019-01-17 09:50:05 +01:00
var active = activeElement ( )
2018-10-23 14:23:02 +02:00
var namespace = dom . namespaceURI
2022-06-27 15:33:32 +02:00
currentDOM = dom
currentRedraw = typeof redraw === "function" ? redraw : undefined
2019-08-16 16:25:01 +02:00
try {
2022-06-27 15:33:32 +02:00
// First time rendering into a node clears it out
if ( dom . vnodes == null ) dom . textContent = ""
vnodes = Vnode . normalizeChildren ( Array . isArray ( vnodes ) ? vnodes : [ vnodes ] )
2019-08-16 16:25:01 +02:00
updateNodes ( dom , dom . vnodes , vnodes , hooks , null , namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace )
2022-06-27 15:33:32 +02:00
dom . vnodes = vnodes
// `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement
if ( active != null && activeElement ( ) !== active && typeof active . focus === "function" ) active . focus ( )
for ( var i = 0 ; i < hooks . length ; i ++ ) hooks [ i ] ( )
2019-08-16 16:25:01 +02:00
} finally {
currentRedraw = prevRedraw
2022-06-27 15:33:32 +02:00
currentDOM = prevDOM
2019-08-16 16:25:01 +02:00
}
2018-10-23 14:23:02 +02:00
}
}
2022-06-27 15:33:32 +02:00
var render = _13 ( typeof window !== "undefined" ? window : null )
2025-01-02 18:47:57 +01:00
var _16 = function ( render0 , schedule ) {
2019-08-16 16:25:01 +02:00
var subscriptions = [ ]
var pending = false
2022-06-27 15:33:32 +02:00
var offset = - 1
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
function sync ( ) {
2022-06-27 15:33:32 +02:00
for ( offset = 0 ; offset < subscriptions . length ; offset += 2 ) {
try { render0 ( subscriptions [ offset ] , Vnode ( subscriptions [ offset + 1 ] ) , redraw ) }
2019-08-16 16:25:01 +02:00
catch ( e ) { console . error ( e ) }
}
2022-06-27 15:33:32 +02:00
offset = - 1
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
function redraw ( ) {
if ( ! pending ) {
pending = true
2025-01-02 18:47:57 +01:00
schedule ( function ( ) {
2019-08-16 16:25:01 +02:00
pending = false
sync ( )
} )
}
}
2025-01-02 18:47:57 +01:00
2019-01-17 09:50:05 +01:00
redraw . sync = sync
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
function mount ( root , component ) {
if ( component != null && component . view == null && typeof component !== "function" ) {
2022-06-27 15:33:32 +02:00
throw new TypeError ( "m.mount expects a component, not a vnode." )
2019-08-16 16:25:01 +02:00
}
var index = subscriptions . indexOf ( root )
if ( index >= 0 ) {
subscriptions . splice ( index , 2 )
2022-06-27 15:33:32 +02:00
if ( index <= offset ) offset -= 2
render0 ( root , [ ] )
2019-08-16 16:25:01 +02:00
}
if ( component != null ) {
subscriptions . push ( root , component )
render0 ( root , Vnode ( component ) , redraw )
}
}
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
return { mount : mount , redraw : redraw }
2018-10-23 14:23:02 +02:00
}
2023-03-06 15:26:06 +01:00
var mountRedraw0 = _16 ( render , typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : null )
2025-01-02 18:47:57 +01:00
export var buildQueryString = function ( object ) {
2019-08-16 16:25:01 +02:00
if ( Object . prototype . toString . call ( object ) !== "[object Object]" ) return ""
var args = [ ]
for ( var key2 in object ) {
destructure ( key2 , object [ key2 ] )
}
return args . join ( "&" )
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
function destructure ( key2 , value1 ) {
if ( Array . isArray ( value1 ) ) {
for ( var i = 0 ; i < value1 . length ; i ++ ) {
destructure ( key2 + "[" + i + "]" , value1 [ i ] )
}
2025-01-02 18:47:57 +01:00
} else if ( Object . prototype . toString . call ( value1 ) === "[object Object]" ) {
2019-08-16 16:25:01 +02:00
for ( var i in value1 ) {
destructure ( key2 + "[" + i + "]" , value1 [ i ] )
}
}
else args . push ( encodeURIComponent ( key2 ) + ( value1 != null && value1 !== "" ? "=" + encodeURIComponent ( value1 ) : "" ) )
2018-10-23 14:23:02 +02:00
}
}
2022-06-27 15:33:32 +02:00
// This exists so I'm5 only saving it once.
2025-01-02 18:47:57 +01:00
var assign = Object . assign || function ( target , source ) {
2022-06-27 15:33:32 +02:00
for ( var key3 in source ) {
if ( hasOwn . call ( source , key3 ) ) target [ key3 ] = source [ key3 ]
}
2019-08-16 16:25:01 +02:00
}
// Returns `path` from `template` + `params`
2025-01-02 18:47:57 +01:00
var buildPathname = function ( template , params ) {
2019-08-16 16:25:01 +02:00
if ( ( /:([^\/\.-]+)(\.{3})?:/ ) . test ( template ) ) {
2022-06-27 15:33:32 +02:00
throw new SyntaxError ( "Template parameter names must be separated by either a '/', '-', or '.'." )
2019-08-16 16:25:01 +02:00
}
if ( params == null ) return template
var queryIndex = template . indexOf ( "?" )
var hashIndex = template . indexOf ( "#" )
var queryEnd = hashIndex < 0 ? template . length : hashIndex
var pathEnd = queryIndex < 0 ? queryEnd : queryIndex
var path = template . slice ( 0 , pathEnd )
var query = { }
assign ( query , params )
2025-01-02 18:47:57 +01:00
var resolved = path . replace ( /:([^\/\.-]+)(\.{3})?/g , function ( m4 , key1 , variadic ) {
2019-08-16 16:25:01 +02:00
delete query [ key1 ]
// If no such parameter exists, don't interpolate it.
2022-06-27 15:33:32 +02:00
if ( params [ key1 ] == null ) return m4
2019-08-16 16:25:01 +02:00
// Escape normal parameters, but not variadic ones.
return variadic ? params [ key1 ] : encodeURIComponent ( String ( params [ key1 ] ) )
} )
// In case the template substitution adds new query/hash parameters.
var newQueryIndex = resolved . indexOf ( "?" )
var newHashIndex = resolved . indexOf ( "#" )
var newQueryEnd = newHashIndex < 0 ? resolved . length : newHashIndex
var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex
var result0 = resolved . slice ( 0 , newPathEnd )
if ( queryIndex >= 0 ) result0 += template . slice ( queryIndex , queryEnd )
if ( newQueryIndex >= 0 ) result0 += ( queryIndex < 0 ? "?" : "&" ) + resolved . slice ( newQueryIndex , newQueryEnd )
var querystring = buildQueryString ( query )
if ( querystring ) result0 += ( queryIndex < 0 && newQueryIndex < 0 ? "?" : "&" ) + querystring
if ( hashIndex >= 0 ) result0 += template . slice ( hashIndex )
if ( newHashIndex >= 0 ) result0 += ( hashIndex < 0 ? "" : "&" ) + resolved . slice ( newHashIndex )
return result0
}
var mountRedraw = mountRedraw0
var m = function m ( ) { return hyperscript . apply ( this , arguments ) }
m . m = hyperscript
m . trust = hyperscript . trust
m . fragment = hyperscript . fragment
2022-06-27 15:33:32 +02:00
m . Fragment = "["
2019-08-16 16:25:01 +02:00
m . mount = mountRedraw . mount
2022-06-27 15:33:32 +02:00
var m6 = hyperscript
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function decodeURIComponentSave0 ( str ) {
try {
return decodeURIComponent ( str )
2025-01-02 18:47:57 +01:00
} catch ( err ) {
2022-06-27 15:33:32 +02:00
return str
}
}
2025-01-02 18:47:57 +01:00
export var parseQueryString = function ( string ) {
2018-10-23 14:23:02 +02:00
if ( string === "" || string == null ) return { }
if ( string . charAt ( 0 ) === "?" ) string = string . slice ( 1 )
2019-08-16 16:25:01 +02:00
var entries = string . split ( "&" ) , counters = { } , data0 = { }
2018-10-23 14:23:02 +02:00
for ( var i = 0 ; i < entries . length ; i ++ ) {
var entry = entries [ i ] . split ( "=" )
2022-06-27 15:33:32 +02:00
var key5 = decodeURIComponentSave0 ( entry [ 0 ] )
var value2 = entry . length === 2 ? decodeURIComponentSave0 ( entry [ 1 ] ) : ""
2019-08-16 16:25:01 +02:00
if ( value2 === "true" ) value2 = true
else if ( value2 === "false" ) value2 = false
var levels = key5 . split ( /\]\[?|\[/ )
var cursor = data0
if ( key5 . indexOf ( "[" ) > - 1 ) levels . pop ( )
2019-01-17 09:50:05 +01:00
for ( var j0 = 0 ; j0 < levels . length ; j0 ++ ) {
var level = levels [ j0 ] , nextLevel = levels [ j0 + 1 ]
2018-10-23 14:23:02 +02:00
var isNumber = nextLevel == "" || ! isNaN ( parseInt ( nextLevel , 10 ) )
if ( level === "" ) {
2019-08-16 16:25:01 +02:00
var key5 = levels . slice ( 0 , j0 ) . join ( )
if ( counters [ key5 ] == null ) {
counters [ key5 ] = Array . isArray ( cursor ) ? cursor . length : 0
}
level = counters [ key5 ] ++
2018-10-23 14:23:02 +02:00
}
2019-08-22 09:55:48 +02:00
// Disallow direct prototype pollution
else if ( level === "__proto__" ) break
if ( j0 === levels . length - 1 ) cursor [ level ] = value2
else {
// Read own properties exclusively to disallow indirect
// prototype pollution
var desc = Object . getOwnPropertyDescriptor ( cursor , level )
if ( desc != null ) desc = desc . value
if ( desc == null ) cursor [ level ] = desc = isNumber ? [ ] : { }
cursor = desc
}
2018-10-23 14:23:02 +02:00
}
}
2019-08-16 16:25:01 +02:00
return data0
2018-10-23 14:23:02 +02:00
}
2019-08-16 16:25:01 +02:00
// Returns `{path1, params}` from `url`
2025-01-02 18:47:57 +01:00
var parsePathname = function ( url ) {
2019-08-16 16:25:01 +02:00
var queryIndex0 = url . indexOf ( "?" )
var hashIndex0 = url . indexOf ( "#" )
var queryEnd0 = hashIndex0 < 0 ? url . length : hashIndex0
var pathEnd0 = queryIndex0 < 0 ? queryEnd0 : queryIndex0
var path1 = url . slice ( 0 , pathEnd0 ) . replace ( /\/{2,}/g , "/" )
if ( ! path1 ) path1 = "/"
else {
if ( path1 [ 0 ] !== "/" ) path1 = "/" + path1
if ( path1 . length > 1 && path1 [ path1 . length - 1 ] === "/" ) path1 = path1 . slice ( 0 , - 1 )
2018-10-23 14:23:02 +02:00
}
2019-08-16 16:25:01 +02:00
return {
path : path1 ,
params : queryIndex0 < 0
? { }
: parseQueryString ( url . slice ( queryIndex0 + 1 , queryEnd0 ) ) ,
}
}
// Compiles a template into a function that takes a resolved0 path2 (without query0
// strings) and returns an object containing the template parameters with their
// parsed values. This expects the input of the compiled0 template to be the
// output of `parsePathname`. Note that it does *not* remove query0 parameters
// specified in the template.
2025-01-02 18:47:57 +01:00
var compileTemplate = function ( template ) {
2019-08-16 16:25:01 +02:00
var templateData = parsePathname ( template )
var templateKeys = Object . keys ( templateData . params )
var keys = [ ]
var regexp = new RegExp ( "^" + templateData . path . replace (
// I escape literal text so people can use things like `:file.:ext` or
2019-08-22 09:55:48 +02:00
// `:lang-:locale` in routes. This is2 all merged into one pass so I
2019-08-16 16:25:01 +02:00
// don't also accidentally escape `-` and make it harder to detect it to
// ban it from template parameters.
/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g ,
2025-01-02 18:47:57 +01:00
function ( m7 , key6 , extra ) {
2022-06-27 15:33:32 +02:00
if ( key6 == null ) return "\\" + m7
2019-08-16 16:25:01 +02:00
keys . push ( { k : key6 , r : extra === "..." } )
if ( extra === "..." ) return "(.*)"
if ( extra === "." ) return "([^/]+)\\."
return "([^/]+)" + ( extra || "" )
}
) + "$" )
2025-01-02 18:47:57 +01:00
return function ( data1 ) {
2019-08-16 16:25:01 +02:00
// First, check the params. Usually, there isn't any, and it's just
// checking a static set.
for ( var i = 0 ; i < templateKeys . length ; i ++ ) {
if ( templateData . params [ templateKeys [ i ] ] !== data1 . params [ templateKeys [ i ] ] ) return false
}
// If no interpolations exist, let's skip all the ceremony
if ( ! keys . length ) return regexp . test ( data1 . path )
var values = regexp . exec ( data1 . path )
if ( values == null ) return false
for ( var i = 0 ; i < keys . length ; i ++ ) {
data1 . params [ keys [ i ] . k ] = keys [ i ] . r ? values [ i + 1 ] : decodeURIComponent ( values [ i + 1 ] )
2018-10-23 14:23:02 +02:00
}
2019-08-16 16:25:01 +02:00
return true
}
}
2022-06-27 15:33:32 +02:00
// Note: this is3 mildly perf-sensitive.
//
// It does *not* use `delete` - dynamic `delete`s usually cause objects to bail
// out into dictionary mode and just generally cause a bunch of optimization
// issues within engines.
//
// Ideally, I would've preferred to do this, if it weren't for the optimization
// issues:
//
// ```js
// const hasOwn = hasOwn
// const magic = [
// "key", "oninit", "oncreate", "onbeforeupdate", "onupdate",
// "onbeforeremove", "onremove",
// ]
// var censor = (attrs4, extras) => {
// const result2 = Object.assign0(Object.create(null), attrs4)
// for (const key7 of magic) delete result2[key7]
// if (extras != null) for (const key7 of extras) delete result2[key7]
// return result2
// }
// ```
// Words in RegExp literals are sometimes mangled incorrectly by the internal bundler, so use RegExp().
var magic = new RegExp ( "^(?:key|oninit|oncreate|onbeforeupdate|onupdate|onbeforeremove|onremove)$" )
2025-01-02 18:47:57 +01:00
var censor = function ( attrs4 , extras ) {
2022-06-27 15:33:32 +02:00
var result2 = { }
if ( extras != null ) {
for ( var key7 in attrs4 ) {
if ( hasOwn . call ( attrs4 , key7 ) && ! magic . test ( key7 ) && extras . indexOf ( key7 ) < 0 ) {
result2 [ key7 ] = attrs4 [ key7 ]
}
}
} else {
for ( var key7 in attrs4 ) {
if ( hasOwn . call ( attrs4 , key7 ) && ! magic . test ( key7 ) ) {
result2 [ key7 ] = attrs4 [ key7 ]
}
}
}
return result2
}
2019-08-16 16:25:01 +02:00
var sentinel0 = { }
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function decodeURIComponentSave ( component ) {
try {
return decodeURIComponent ( component )
2025-01-02 18:47:57 +01:00
} catch ( e ) {
2022-06-27 15:33:32 +02:00
return component
}
}
2025-01-02 18:47:57 +01:00
var _28 = function ( $window , mountRedraw00 ) {
2022-06-27 15:33:32 +02:00
var callAsync0 = $window == null
// In case Mithril.js' loaded globally without the DOM, let's not break
? null
: typeof $window . setImmediate === "function" ? $window . setImmediate : $window . setTimeout
var p = Promise . resolve ( )
var scheduled = false
// state === 0: init
// state === 1: scheduled
// state === 2: done
var ready = false
var state = 0
var compiled , fallbackRoute
var currentResolver = sentinel0 , component , attrs3 , currentPath , lastUpdate
var RouterRoot = {
2025-01-02 18:47:57 +01:00
onbeforeupdate : function ( ) {
2022-06-27 15:33:32 +02:00
state = state ? 2 : 1
return ! ( ! state || sentinel0 === currentResolver )
} ,
2025-01-02 18:47:57 +01:00
onremove : function ( ) {
2022-06-27 15:33:32 +02:00
$window . removeEventListener ( "popstate" , fireAsync , false )
$window . removeEventListener ( "hashchange" , resolveRoute , false )
} ,
2025-01-02 18:47:57 +01:00
view : function ( ) {
2022-06-27 15:33:32 +02:00
if ( ! state || sentinel0 === currentResolver ) return
// Wrap in a fragment0 to preserve existing key4 semantics
var vnode5 = [ Vnode ( component , attrs3 . key , attrs3 ) ]
if ( currentResolver ) vnode5 = currentResolver . render ( vnode5 [ 0 ] )
return vnode5
} ,
}
var SKIP = route . SKIP = { }
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function resolveRoute ( ) {
scheduled = false
// Consider the pathname holistically. The prefix might even be invalid,
// but that's not our problem.
var prefix = $window . location . hash
if ( route . prefix [ 0 ] !== "#" ) {
prefix = $window . location . search + prefix
if ( route . prefix [ 0 ] !== "?" ) {
prefix = $window . location . pathname + prefix
if ( prefix [ 0 ] !== "/" ) prefix = "/" + prefix
}
}
// This seemingly useless `.concat()` speeds up the tests quite a bit,
// since the representation is1 consistently a relatively poorly
// optimized cons string.
var path0 = prefix . concat ( )
2025-01-02 18:47:57 +01:00
. replace ( /(?:%[a-f89][a-f0-9])+/gim , decodeURIComponentSave )
. slice ( route . prefix . length )
2022-06-27 15:33:32 +02:00
var data = parsePathname ( path0 )
assign ( data . params , $window . history . state )
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function reject ( e ) {
console . error ( e )
setPath ( fallbackRoute , null , { replace : true } )
}
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
loop ( 0 )
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
function loop ( i ) {
// state === 0: init
// state === 1: scheduled
// state === 2: done
for ( ; i < compiled . length ; i ++ ) {
if ( compiled [ i ] . check ( data ) ) {
var payload = compiled [ i ] . component
var matchedRoute = compiled [ i ] . route
var localComp = payload
2025-01-02 18:47:57 +01:00
var update = lastUpdate = function ( comp ) {
2022-06-27 15:33:32 +02:00
if ( update !== lastUpdate ) return
if ( comp === SKIP ) return loop ( i + 1 )
2025-01-02 18:47:57 +01:00
component = comp != null && ( typeof comp . view === "function" || typeof comp === "function" ) ? comp : "div"
2022-06-27 15:33:32 +02:00
attrs3 = data . params , currentPath = path0 , lastUpdate = null
currentResolver = payload . render ? payload : null
if ( state === 2 ) mountRedraw00 . redraw ( )
else {
state = 2
mountRedraw00 . redraw . sync ( )
}
}
// There's no understating how much I *wish* I could
// use `async`/`await` here...
if ( payload . view || typeof payload === "function" ) {
payload = { }
update ( localComp )
2025-01-02 18:47:57 +01:00
} else if ( payload . onmatch ) {
2022-06-27 15:33:32 +02:00
p . then ( function ( ) {
return payload . onmatch ( data . params , path0 , matchedRoute )
} ) . then ( update , path0 === fallbackRoute ? null : reject )
}
else update ( "div" )
return
}
}
if ( path0 === fallbackRoute ) {
throw new Error ( "Could not resolve default route " + fallbackRoute + "." )
}
setPath ( fallbackRoute , null , { replace : true } )
}
}
2025-01-02 18:47:57 +01:00
2022-06-27 15:33:32 +02:00
// Set it unconditionally so `m6.route.set` and `m6.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.
function fireAsync ( ) {
if ( ! scheduled ) {
scheduled = true
// TODO: just do `mountRedraw00.redraw1()` here and elide the timer
// dependency. Note that this will muck with tests a *lot*, so it's
// not as easy of a change as it sounds.
callAsync0 ( resolveRoute )
}
}
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
function setPath ( path0 , data , options ) {
path0 = buildPathname ( path0 , data )
2022-06-27 15:33:32 +02:00
if ( ready ) {
2019-08-16 16:25:01 +02:00
fireAsync ( )
2018-10-23 14:23:02 +02:00
var state = options ? options . state : null
var title = options ? options . title : null
2019-08-16 16:25:01 +02:00
if ( options && options . replace ) $window . history . replaceState ( state , title , route . prefix + path0 )
else $window . history . pushState ( state , title , route . prefix + path0 )
2025-01-02 18:47:57 +01:00
}
2019-08-16 16:25:01 +02:00
else {
$window . location . href = route . prefix + path0
2018-10-23 14:23:02 +02:00
}
}
2025-01-02 18:47:57 +01:00
2019-08-16 16:25:01 +02:00
function route ( root , defaultRoute , routes ) {
2022-06-27 15:33:32 +02:00
if ( ! root ) throw new TypeError ( "DOM element being rendered to does not exist." )
2025-01-02 18:47:57 +01:00
compiled = Object . keys ( routes ) . map ( function ( route ) {
2022-06-27 15:33:32 +02:00
if ( route [ 0 ] !== "/" ) throw new SyntaxError ( "Routes must start with a '/'." )
2019-08-16 16:25:01 +02:00
if ( ( /:([^\/\.-]+)(\.{3})?:/ ) . test ( route ) ) {
2022-06-27 15:33:32 +02:00
throw new SyntaxError ( "Route parameter names must be separated with either '/', '.', or '-'." )
2019-08-16 16:25:01 +02:00
}
return {
route : route ,
component : routes [ route ] ,
check : compileTemplate ( route ) ,
}
} )
2022-06-27 15:33:32 +02:00
fallbackRoute = defaultRoute
2019-08-16 16:25:01 +02:00
if ( defaultRoute != null ) {
var defaultData = parsePathname ( defaultRoute )
if ( ! compiled . some ( function ( i ) { return i . check ( defaultData ) } ) ) {
2022-06-27 15:33:32 +02:00
throw new ReferenceError ( "Default route doesn't match any known routes." )
2018-10-23 14:23:02 +02:00
}
2019-08-16 16:25:01 +02:00
}
if ( typeof $window . history . pushState === "function" ) {
$window . addEventListener ( "popstate" , fireAsync , false )
} else if ( route . prefix [ 0 ] === "#" ) {
$window . addEventListener ( "hashchange" , resolveRoute , false )
}
2022-06-27 15:33:32 +02:00
ready = true
mountRedraw00 . mount ( root , RouterRoot )
resolveRoute ( )
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
route . set = function ( path0 , data , options ) {
2018-10-23 14:23:02 +02:00
if ( lastUpdate != null ) {
options = options || { }
options . replace = true
}
lastUpdate = null
2019-08-16 16:25:01 +02:00
setPath ( path0 , data , options )
2018-10-23 14:23:02 +02:00
}
2025-01-02 18:47:57 +01:00
route . get = function ( ) { return currentPath }
2019-08-16 16:25:01 +02:00
route . prefix = "#!"
route . Link = {
2025-01-02 18:47:57 +01:00
view : function ( vnode5 ) {
2022-06-27 15:33:32 +02:00
// Omit the used parameters from the rendered element0 - they are
// internal. Also, censor the various lifecycle methods.
//
// We don't strip the other parameters because for convenience we
// let them be specified in the selector as well.
var child0 = m6 (
vnode5 . attrs . selector || "a" ,
censor ( vnode5 . attrs , [ "options" , "params" , "selector" , "onclick" ] ) ,
vnode5 . children
)
var options , onclick , href
2019-08-16 16:25:01 +02:00
// Let's provide a *right* way to disable a route link, rather than
// letting people screw up accessibility on accident.
//
// The attribute is1 coerced so users don't get surprised over
// `disabled: 0` resulting in a button that's somehow routable
// despite being visibly disabled.
if ( child0 . attrs . disabled = Boolean ( child0 . attrs . disabled ) ) {
child0 . attrs . href = null
child0 . attrs [ "aria-disabled" ] = "true"
2022-06-27 15:33:32 +02:00
// If you *really* do want add `onclick` on a disabled link, use
2019-08-16 16:25:01 +02:00
// an `oncreate` hook to add it.
} else {
2022-06-27 15:33:32 +02:00
options = vnode5 . attrs . options
onclick = vnode5 . attrs . onclick
// Easier to build it now to keep it isomorphic.
href = buildPathname ( child0 . attrs . href , vnode5 . attrs . params )
2019-08-16 16:25:01 +02:00
child0 . attrs . href = route . prefix + href
2025-01-02 18:47:57 +01:00
child0 . attrs . onclick = function ( e ) {
2019-08-16 16:25:01 +02:00
var result1
if ( typeof onclick === "function" ) {
result1 = onclick . call ( e . currentTarget , e )
} else if ( onclick == null || typeof onclick !== "object" ) {
// do nothing
} else if ( typeof onclick . handleEvent === "function" ) {
onclick . handleEvent ( e )
}
// Adapted from React Router's implementation:
2019-08-22 09:55:48 +02:00
// https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js
2019-08-16 16:25:01 +02:00
//
2022-06-27 15:33:32 +02:00
// Try to be flexible and intuitive in how we handle0 links.
2019-08-16 16:25:01 +02:00
// Fun fact: links aren't as obvious to get right as you
// 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.
if (
// Skip if `onclick` prevented default
result1 !== false && ! e . defaultPrevented &&
// Ignore everything but left clicks
( e . button === 0 || e . which === 0 || e . which === 1 ) &&
2022-06-27 15:33:32 +02:00
// Let the browser handle0 `target=_blank`, etc.
2019-08-16 16:25:01 +02:00
( ! e . currentTarget . target || e . currentTarget . target === "_self" ) &&
// No modifier keys
! e . ctrlKey && ! e . metaKey && ! e . shiftKey && ! e . altKey
) {
e . preventDefault ( )
e . redraw = false
route . set ( href , null , options )
}
}
}
return child0
} ,
}
2025-01-02 18:47:57 +01:00
route . param = function ( key4 ) {
2019-08-16 16:25:01 +02:00
return attrs3 && key4 != null ? attrs3 [ key4 ] : attrs3
2018-10-23 14:23:02 +02:00
}
return route
}
2022-06-27 15:33:32 +02:00
m . route = _28 ( typeof window !== "undefined" ? window : null , mountRedraw )
2019-08-16 16:25:01 +02:00
m . render = render
m . redraw = mountRedraw . redraw
2018-10-23 14:23:02 +02:00
m . parseQueryString = parseQueryString
m . buildQueryString = buildQueryString
2019-08-16 16:25:01 +02:00
m . parsePathname = parsePathname
m . buildPathname = buildPathname
2018-10-23 14:23:02 +02:00
m . vnode = Vnode
2022-06-27 15:33:32 +02:00
m . censor = censor
2025-01-02 18:47:57 +01:00
export default m
export const route = m . route
export const redraw = m . redraw