@litejs/ui 25.5.0 → 26.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui.js CHANGED
@@ -1,7 +1,11 @@
1
1
 
2
2
  /* litejs.com/MIT-LICENSE.txt */
3
3
 
4
- /* global escape, navigator, xhr, addEventListener */
4
+ /* global escape, navigator, xhr */
5
+
6
+ // Conditional compilation via toggle comments (processed by build tool):
7
+ // /*** name ***/ code /**/ - `code` active in source; build can strip it
8
+ // /*** name ***/ code1 /*/ code2 /**/ - `code1` active in source, `code2` commented out; build can swap
5
9
 
6
10
  /*** debug ***/
7
11
  console.log("LiteJS is in debug mode, but it's fine for production")
@@ -11,9 +15,9 @@ console.log("LiteJS is in debug mode, but it's fine for production")
11
15
  window.El = El
12
16
  asEmitter(window.LiteJS = LiteJS)
13
17
 
14
- var UNDEF, parser, pushBase, styleNode, canCapture
18
+ var UNDEF, parser, pushBase, styleNode
15
19
  , NUL = null
16
- // THIS will be undefined in strict mode and window in sloppy mode
20
+ // THIS will be `undefined` in strict mode and `window` in sloppy mode
17
21
  , THIS = this
18
22
  , html = document.documentElement
19
23
  , body = document.body
@@ -22,57 +26,143 @@ console.log("LiteJS is in debug mode, but it's fine for production")
22
26
  , plugins = {}
23
27
  , sources = []
24
28
  , assign = Object.assign
29
+ // bind(fn, ctx, ...args)() calls fn.call(ctx, ...args); closureless partial application
25
30
  , bind = El.bind.bind(El.call)
26
31
  , create = Object.create
27
32
  , hasOwn = bind(plugins.hasOwnProperty)
28
33
  , isArr = Array.isArray
29
34
  , slice = emptyArr.slice
35
+ // Closureless utilities via Function() to avoid capturing outer scope
30
36
  , elReplace = Function("a,b,c", "a&&b&&(c=a.parentNode)&&c.replaceChild(b,a)")
31
37
  , elRm = Function("a,b", "a&&(b=a.parentNode)&&b.removeChild(a)")
32
38
  , getAttr = Function("a,b", "return a&&a.getAttribute&&a.getAttribute(b)")
33
- , replace = Function("a,b,c", "return a.replace(b,c)")
39
+ , replace = Function("a,b,c", "return c.replace(a,b)")
40
+ , toCamel = replace.bind(NUL, /\-([a-z])/g, Function("a,b", "return b.toUpperCase()"))
34
41
 
42
+ /*** ie9 ***/
35
43
  // JScript engine in IE8 and below does not recognize vertical tabulation character `\v`.
36
44
  // http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
37
45
  , ie678 = !+"\v1" // jshint ignore:line
46
+ // innerText is implemented in IE4, textContent in IE9, Node.text in Opera 9-10
47
+ // Safari 2.x innerText results an empty string when style.display=="none" or Node is not in DOM
48
+ , txtAttr = El.T = "textContent" in html ? "textContent" : "innerText"
49
+ , elTxt = function(el, txt) {
50
+ if (el[txtAttr] !== txt) el[txtAttr] = txt
51
+ }
52
+ /*/
53
+ , elTxt = function(el, txt) {
54
+ if (el.textContent !== txt) el.textContent = txt
55
+ }
56
+ /**/
38
57
 
39
58
  , elSeq = 0
40
59
  , elCache = {}
60
+ // Parses ";name! args" binding expressions from _b attribute
41
61
  , renderRe = /[;\s]*([-.\w$]+)(?:(!?)[ :]*((?:(["'\/])(?:\\.|[^\\])*?\4|[^;])*))?/g
62
+ // Parses CSS selectors: .class #id [attr=val] :pseudo
42
63
  , selectorRe = /([.#:[])([-\w]+)(?:([~^$*|]?)=(("|')(?:\\.|[^\\])*?\5|[-\w]+))?]?/g
43
64
  , fnCache = {}
44
- , fnRe = /('|")(?:\\.|[^\\])*?\1|\/(?:\\.|[^\\])+?\/[gim]*|\$el\b|\$[aos]\b|\b(?:false|in|if|new|null|this|true|typeof|void|function|var|else|return)\b|\.\w+|\w+:/g
65
+ // Matches tokens to exclude from scope variable extraction: strings, keywords, member access, labels
66
+ , fnRe = /('|")(?:\\.|[^\\])*?\1|\/(?:\\.|[^\\])+?\/[gim]*|\$el\b|\$[aorsS]\b|\b(?:false|in|if|new|null|this|true|typeof|void|function|var|else|return)\b|\.\w+|\w+:/g
45
67
  , wordRe = /[a-z_$][\w$]*/ig
46
- , camelRe = /\-([a-z])/g
47
- // innerText is implemented in IE4, textContent in IE9, Node.text in Opera 9-10
48
- // Safari 2.x innerText results an empty string when style.display=="none" or Node is not in DOM
49
- , txtAttr = "textContent" in html ? "textContent" /* c8 ignore next */ : "innerText"
50
- , bindingsCss = acceptMany(function(el, key, val) {
51
- el.style[replace(key, camelRe, camelFn)] = val
52
- })
53
- , bindingsOn = acceptMany(addEvent, function(el, val, selector, data) {
54
- return isStr(val) ? function(e) {
55
- var target = selector ? closest(e.target, selector) : el
56
- if (target) emit.apply(target, [elScope(el).$ui, val, e, target].concat(data))
57
- } :
58
- selector ? function(e, touchEv, touchEl) {
59
- if (matches(touchEl = e.target, selector)) val(e, touchEv, touchEl, data)
60
- } :
61
- val
68
+ , bindingsCss = acceptMany(function(el, key, val, current) {
69
+ current = el.style[key = toCamel(key)]
70
+ el.style[key] = val
71
+ return current
62
72
  })
73
+ , bindingsOn = acceptMany(addEvent, 1)
63
74
  , bindings = {
64
75
  cls: acceptMany(cls),
65
76
  css: bindingsCss,
66
77
  on: bindingsOn,
78
+ one: acceptMany(function(el, ev, fn) {
79
+ addEvent(el, ev, function remove() {
80
+ rmEvent(el, ev, remove)
81
+ fn.apply(el, arguments)
82
+ })
83
+ }, 1),
67
84
  set: acceptMany(setAttr),
68
85
  txt: elTxt,
69
- val: elVal,
86
+ /*** form ***/
87
+ val: function elVal(el, val, ignoreFocus) {
88
+ if (!el) return
89
+ var input, step, key, value
90
+ , i = 0
91
+ , type = el.type
92
+ , opts = el.options
93
+ , checkbox = type === "checkbox" || type === "radio"
94
+
95
+ if (el.tagName === "FORM") {
96
+ // Disabled controls do not receive focus,
97
+ // are skipped in tabbing navigation, cannot be successfully posted.
98
+ //
99
+ // Read-only elements receive focus but cannot be modified by the user,
100
+ // are included in tabbing navigation, are successfully posted.
101
+ //
102
+ // Read-only checkboxes can be changed by the user
103
+
104
+ for (opts = {}; (input = el.elements[i++]); ) if (!input.disabled && (key = input.name || input.id)) {
105
+ value = elVal(input, val != UNDEF ? val[key] : UNDEF, ignoreFocus)
106
+ if (value !== UNDEF) {
107
+ step = opts
108
+ replace(/\[(.*?)\]/g, replacer, key)
109
+ step[key || step.length] = value
110
+ }
111
+ }
112
+ return opts
113
+ }
114
+
115
+ if (val !== UNDEF) {
116
+ try {
117
+ if (!ignoreFocus && document.activeElement === el) return
118
+ } catch (e) {}
119
+ if (opts) {
120
+ for (value = (isArr(val) ? val : [ val ]).map(String); (input = opts[i++]); ) {
121
+ input.selected = value.indexOf(input.value) > -1
122
+ }
123
+ } else if (el.val) {
124
+ el.val(val)
125
+ } else if (checkbox) {
126
+ el.checked = !!val
127
+ } else {
128
+ el.value = val
129
+ }
130
+ return
131
+ }
132
+
133
+ if (opts) {
134
+ if (type === "select-multiple") {
135
+ for (val = []; (input = opts[i++]); ) {
136
+ if (input.selected && !input.disabled) {
137
+ val.push(input.valObject || input.value)
138
+ }
139
+ }
140
+ return val
141
+ }
142
+ // IE8 throws error when accessing to options[-1]
143
+ value = el.selectedIndex
144
+ el = value > -1 && opts[value] || el
145
+ }
146
+
147
+ return checkbox && !el.checked ?
148
+ (type === "radio" ? UNDEF : NUL) :
149
+ el.valObject !== UNDEF ? el.valObject : el.value
150
+
151
+ function replacer(_, _key, offset) {
152
+ if (step == opts) key = key.slice(0, offset)
153
+ step = step[key] || (step[key] = step[key] === NUL || _key && +_key != _key ? {} : [])
154
+ key = _key
155
+ }
156
+ }
157
+ /**/
70
158
  }
159
+ // Stores "!" once-bindings; index used in compiled fn to strip from _b after first run
71
160
  , bindOnce = []
72
161
  , globalScope = {
73
162
  El: El,
74
163
  $b: bindings
75
164
  }
165
+ // Array-like wrapper methods for multi-element collections (mixed into arrays by ElWrap)
76
166
  , elArr = {
77
167
  append: function(el) {
78
168
  var elWrap = this
@@ -90,6 +180,8 @@ console.log("LiteJS is in debug mode, but it's fine for production")
90
180
  }
91
181
  }
92
182
 
183
+ // fixEv: maps custom event names to native (e.g., touch→"" for non-DOM events)
184
+ // fixFn: transforms event handlers for browser compat (e.g., touch→pointer init)
93
185
  , Event = window.Event || window
94
186
  , fixEv = Event.fixEv || (Event.fixEv = {})
95
187
  , fixFn = Event.fixFn || (Event.fixFn = {})
@@ -119,27 +211,27 @@ console.log("LiteJS is in debug mode, but it's fine for production")
119
211
  (tag == "a" ? " href=\"" + (link || text) + "\"" : op == "@" ? " datetime=\"" + name + "\"" : "") +
120
212
  (attr ? " class=\"" + attr.slice(1) + "\">" : ">") +
121
213
  (
122
- op === ">" ? doc(replace(text, /^> ?/gm, "")) :
214
+ op === ">" ? doc(replace(/^> ?/gm, "", text)) :
123
215
  tag == "ul" ? "<li>" + text.split(/\n - (?=\S)/).map(inline).join("</li>\n<li>") + "</li>" :
124
- inline(tag == "a" ? replace(name, /^\w+:\/{0,2}/, "") : text)
216
+ inline(tag == "a" ? replace(/^\w+:\/{0,2}/, "", name) : text)
125
217
  ) +
126
218
  "</" + tag + ">" :
127
- replace(tag, /\[([-!*+,/:;@^_`~])((.+?)(?: (\S+?))?)\1(\.[.\w]+)?]/g, inline)
219
+ replace(/\[([-!*+,/:;@^_`~])((.+?)(?: (\S+?))?)\1(\.[.\w]+)?]/g, inline, tag)
128
220
  }
129
221
  function block(tag, op, text, media, alt) {
130
222
  return op && !isArr(text) ? inline(tag, op, text) :
131
223
  media ? "<img src=\"" + media + "\" alt=\"" + alt + "\">" :
132
- blockRe.test(tag) ? replace(tag, blockRe, block) :
224
+ blockRe.test(tag) ? replace(blockRe, block, tag) :
133
225
  tag === "---" ? "<hr>" : "<p>" + inline(tag) + "</p>"
134
226
  }
135
227
  function doc(txt) {
136
- return replace(txt.trim(), /^ \b/gm, "<br>").split(/\n\n+/).map(block).join("\n")
228
+ return replace(/^ \b/gm, "<br>", txt.trim()).split(/\n\n+/).map(block).join("\n")
137
229
  }
138
230
  bindings.t = function(el, text) {
139
- el.innerHTML = inline(replace(text, /</g, "&lt;"))
231
+ el.innerHTML = inline(replace(/</g, "&lt;", text))
140
232
  }
141
233
  bindings.d = function(el, text) {
142
- el.innerHTML = doc(replace(text, /</g, "&lt;"))
234
+ el.innerHTML = doc(replace(/</g, "&lt;", text))
143
235
  }
144
236
  /**/
145
237
 
@@ -172,12 +264,22 @@ console.log("LiteJS is in debug mode, but it's fine for production")
172
264
  return fn(this, a, b, c, d, e)
173
265
  }
174
266
  }
267
+ function one(type, fn, scope) {
268
+ var emitter = this
269
+ on(emitter, type, function remove() {
270
+ off.call(emitter, type, remove, scope)
271
+ fn.apply(scope, arguments)
272
+ }, scope)
273
+ return emitter
274
+ }
175
275
  }
176
276
 
277
+ // Events stored as triplets [scope, _origin, fn] in emitter._e[type]
278
+ // _origin tracks the unwrapped fn before fixFn (for rmEvent lookup)
279
+ // emptyArr substitutes window as emitter (can't safely add _e property to window)
177
280
  function on(emitter, type, fn, scope, _origin) {
178
281
  if (emitter && type && fn) {
179
282
  if (emitter === window) emitter = emptyArr
180
- emit(emitter, "newListener", type, fn, scope, _origin)
181
283
  var events = emitter._e || (emitter._e = create(NUL))
182
284
  ;(events[type] || (events[type] = [])).unshift(scope, _origin, fn)
183
285
  }
@@ -185,14 +287,13 @@ console.log("LiteJS is in debug mode, but it's fine for production")
185
287
  }
186
288
 
187
289
  function off(type, fn, scope) {
188
- var i, args
290
+ var i
189
291
  , emitter = this === window ? emptyArr : this
190
292
  , events = emitter._e && emitter._e[type]
191
293
  if (events) {
192
294
  for (i = events.length - 2; i > 0; i -= 3) {
193
295
  if ((events[i + 1] === fn || events[i] === fn) && events[i - 1] == scope) {
194
- args = events.splice(i - 1, 3)
195
- emit(emitter, "removeListener", type, args[2], args[0], args[1])
296
+ events.splice(i - 1, 3)
196
297
  if (fn) break
197
298
  }
198
299
  }
@@ -200,18 +301,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
200
301
  return this
201
302
  }
202
303
 
203
- function one(type, fn, scope) {
204
- var emitter = this === window ? emptyArr : this
205
-
206
- function remove() {
207
- off.call(emitter, type, fn, scope)
208
- off.call(emitter, type, remove, scope)
209
- }
210
- on(emitter, type, remove, scope)
211
- on(emitter, type, fn, scope)
212
- return this
213
- }
214
-
215
304
  function emit(emitter, type) {
216
305
  if (emitter === window) emitter = emptyArr
217
306
  var args, i
@@ -225,10 +314,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
225
314
  return _e / 3
226
315
  }
227
316
 
228
- try {
229
- addEventListener("t", NUL, { get capture() { canCapture = 1 }})
230
- } catch(e){}
231
-
232
317
  function addEvent(el, ev, fn, opts) {
233
318
  var fn2 = fixFn[ev] && fixFn[ev](el, fn, ev) || fn
234
319
  , ev2 = fixEv[ev] || ev
@@ -237,15 +322,13 @@ console.log("LiteJS is in debug mode, but it's fine for production")
237
322
  // polyfilled addEventListener returns patched function
238
323
  // useCapture defaults to false
239
324
  // Chrome56 touchstart/move sets {passive:true} by default; use {passive:false} to enable preventDefault()
240
- fn2 = html.addEventListener.call(
241
- el, ev2, fn2, !canCapture && isObj(opts) ? !!opts.capture : opts || false
242
- ) || fn2
325
+ fn2 = html.addEventListener.call(el, ev2, fn2, opts) || fn2
243
326
  }
244
327
 
245
328
  on(el, ev, fn2, el, fn)
246
329
  }
247
330
 
248
- function rmEvent(el, ev, fn) {
331
+ function rmEvent(el, ev, fn, opts) {
249
332
  var evs = el._e && el._e[ev]
250
333
  , id = evs && evs.indexOf(fn)
251
334
  , ev2 = fixEv[ev] || ev
@@ -254,7 +337,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
254
337
  evs[id + 1]._rm()
255
338
  }
256
339
  if (ev2 !== "" && "on" + ev2 in el) {
257
- html.removeEventListener.call(el, ev2, evs[id + 1])
340
+ html.removeEventListener.call(el, ev2, evs[id + 1], opts)
258
341
  }
259
342
  evs.splice(id - 1, 3)
260
343
  }
@@ -271,16 +354,16 @@ console.log("LiteJS is in debug mode, but it's fine for production")
271
354
  function LiteJS(opts) {
272
355
  opts = assign({
273
356
  /*** breakpoints ***/
274
- breakpoints: {
275
- sm: 0,
276
- md: 601,
277
- lg: 1025
278
- },
357
+ breakpoints: "sm,601=md,1025=lg",
279
358
  /**/
280
359
  home: "home",
281
360
  root: body
282
361
  }, opts)
283
362
 
363
+ // View properties:
364
+ // .r route pattern .e template element .p parent view
365
+ // .c active child .o rendered clone .f file dependencies (csv)
366
+ // .s route sequence# .kb keyboard shortcuts
284
367
  function View(route, el, parent) {
285
368
  var view = views[route]
286
369
  if (view) {
@@ -298,11 +381,11 @@ console.log("LiteJS is in debug mode, but it's fine for production")
298
381
 
299
382
  if (route.charAt(0) !== "#") {
300
383
  fnStr += "m[" + (view.s = routeSeq++) + "]?("
301
- reStr += "|(" + replace(route, routeRe, function(_, expr) {
384
+ reStr += "|(" + replace(routeRe, function(_, expr) {
302
385
  return expr ?
303
386
  (fnStr += "p['" + expr + "']=m[" + (routeSeq++) + "],") && "([^/]+?)" :
304
- replace(_, reEsc, "\\$&")
305
- }) + ")"
387
+ replace(reEsc, "\\$&", _)
388
+ }, route) + ")"
306
389
  fnStr += "'" + route + "'):"
307
390
  viewFn = 0
308
391
  }
@@ -315,11 +398,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
315
398
  params._p = 1 + (params._p | 0) // pending
316
399
  return function() {
317
400
  if (--params._p || lastParams !== params || syncResume) return
318
- if (params._d) {
319
- bubbleDown(params)
320
- } else if (params._v) {
321
- viewPing(lastView, params)
322
- }
401
+ bubbleUp(params)
323
402
  }
324
403
  }
325
404
  })
@@ -365,6 +444,43 @@ console.log("LiteJS is in debug mode, but it's fine for production")
365
444
  }
366
445
  })
367
446
 
447
+ // params._p pending async count; ._v current view in traversal; ._c view to close; ._t navigation timestamp
448
+ function bubbleUp(params) {
449
+ var parent
450
+ , view = lastView
451
+ , tmp = params._v || view // Continue bubbleUp from _v
452
+ params._c = view.o ? view : params._c
453
+ for (View.route = view.r; tmp; tmp = parent) {
454
+ viewEmit(syncResume = params._v = tmp, "ping", params, View)
455
+ syncResume = UNDEF
456
+ if (lastParams !== params) return
457
+ if ((parent = tmp.p)) {
458
+ if (parent.c && parent.c !== tmp) {
459
+ params._c = parent.c
460
+ }
461
+ parent.c = tmp
462
+ }
463
+ if (tmp.f) {
464
+ return xhr.load(
465
+ replace(/^|,/g, "$&" + (View.path || ""), tmp.f).split(","),
466
+ bind(readTemplates, view, view.wait(tmp.f = ""))
467
+ )
468
+ } else if (!tmp.e) {
469
+ if (tmp.r === "404") {
470
+ viewParse("%view 404 #\nh2 Not found")
471
+ }
472
+ return viewShow("404")
473
+ }
474
+ }
475
+
476
+ for (tmp in params) {
477
+ if (tmp.charAt(0) !== "_" && (syncResume = hasOwn(paramCb, tmp) && paramCb[tmp] || paramCb["*"])) {
478
+ syncResume(params[tmp], tmp, view, params)
479
+ syncResume = UNDEF
480
+ }
481
+ }
482
+ bubbleDown(params)
483
+ }
368
484
  function bubbleDown(params) {
369
485
  var view = params._v
370
486
  , close = params._c
@@ -383,27 +499,30 @@ console.log("LiteJS is in debug mode, but it's fine for production")
383
499
  /**/
384
500
  params._c = UNDEF
385
501
  }
386
- if ((params._d = params._v = view.c)) {
502
+ if ((params._v = view.c)) {
387
503
  bubbleDown(params)
388
504
  }
389
505
  if ((lastView === view)) {
390
- viewEmit(view, "show", params)
506
+ for (; view; view = view.p) {
507
+ viewEmit(view, "pong", params, View)
508
+ }
509
+ viewEmit(lastView, "show", params)
391
510
  blur()
392
511
  }
393
- }
394
-
395
- function viewClose(view, open) {
396
- if (view && view.o) {
397
- viewEmit(view.p, "closeChild", view, open)
398
- viewClose(view.c)
399
- elKill(view.o)
400
- view.o = UNDEF
401
- /*** kb ***/
402
- rmKb(view.kb)
403
- /**/
404
- viewEmit(view, "close")
512
+ function viewClose(view, open) {
513
+ if (view && view.o) {
514
+ viewEmit(view.p, "closeChild", view, open)
515
+ viewClose(view.c)
516
+ elKill(view.o)
517
+ view.o = UNDEF
518
+ /*** kb ***/
519
+ rmKb(view.kb)
520
+ /**/
521
+ viewEmit(view, "close")
522
+ }
405
523
  }
406
524
  }
525
+
407
526
  function viewDef(str) {
408
527
  for (var match, re = /(\S+) (\S+)/g; (match = re.exec(str)); ) {
409
528
  each(match[1], def)
@@ -437,24 +556,37 @@ console.log("LiteJS is in debug mode, but it's fine for production")
437
556
  return View(url ? viewFn(url, params || {}, "404") : View.home)
438
557
  }
439
558
  function viewParse(str) {
559
+ if (!str) return
440
560
  var parent = El("div")
441
561
  , stack = [-1]
442
562
  , parentStack = []
443
- , templateRe = /([ \t]*)(%?)((?:("|')(?:\\.|[^\\])*?\4|[-\w:.#[\]~^$*|]=?)*) ?([\/>=@^;]|)(([\])}]?).*?([[({]?))(?=\x1f|$)/gm
563
+ , templateRe = /([ \t]*)(%?)((?:("|')(?:\\.|[^\\])*?\4|[-#:.\w[\]](?:[~^$*|]?=)?)*) ?([\/>=@^;]|)(([\])}]?).*?([[({]?))(?=\x1f|$)/gm
564
+ replace(templateRe, work, str)
565
+ work("", "")
566
+ if (parent.childNodes[0]) {
567
+ append(root, parent.childNodes)
568
+ render(root)
569
+ /*** debug ***/
570
+ console.log("Outside view defined elements are rendered immediately into UI")
571
+ /**/
572
+ }
573
+ if (parent.i) {
574
+ histStart(viewShow)
575
+ }
444
576
 
445
577
  function work(all, indent, plugin, sel, q, op, text, mapEnd, mapStart, offset) {
446
578
  if (offset && all === indent) return
447
579
 
448
580
  for (q = indent.length; q <= stack[0]; ) {
449
- if (parent.p) {
450
- if (parent.p.c && !parent.p.e.childNodes[0]) break
451
- parent.p.d(parent.p)
581
+ if ((offset = parent.p)) {
582
+ if (offset.c && !offset.e.childNodes[0]) break
583
+ offset.d(offset)
452
584
  }
453
585
  parent = parentStack.pop()
454
586
  stack.shift()
455
587
  }
456
588
  if (op === "@") {
457
- text = replace(text, /([\w,.]+):?/, "on!'$1',")
589
+ text = replace(/([\w,.]+)[!:]?/, /^\w+!/.test(text) ? "one!'$1'," : "on!'$1',", text)
458
590
  }
459
591
  if (parent.r) {
460
592
  parent.t += "\n" + all
@@ -476,7 +608,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
476
608
  }
477
609
  if (text && op != "/") {
478
610
  if (op === ">") {
479
- replace(indent + " " + text, templateRe, work)
611
+ replace(templateRe, work, indent + " " + text)
480
612
  } else if (op === "=") {
481
613
  append(parent, text) // + "\n")
482
614
  } else {
@@ -488,56 +620,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
488
620
  }
489
621
  }
490
622
  }
491
- replace(str, templateRe, work)
492
- work("", "")
493
- if (parent.childNodes[0]) {
494
- append(root, parent.childNodes)
495
- render(root)
496
- /*** debug ***/
497
- console.log("Outside view defined elements are rendered immediately into UI")
498
- /**/
499
- }
500
- if (parent.i) {
501
- histStart(viewShow)
502
- }
503
- }
504
- function viewPing(view, params) {
505
- var parent
506
- , tmp = params._v || view // Continue bubbleUp from _v
507
- lastParams = params
508
- lastView = view
509
- params._c = view.o ? view : params._c
510
- for (View.route = view.r; tmp; tmp = parent) {
511
- viewEmit(syncResume = params._v = tmp, "ping", params, View)
512
- syncResume = UNDEF
513
- if (lastParams !== params) return
514
- if ((parent = tmp.p)) {
515
- if (parent.c && parent.c !== tmp) {
516
- params._c = parent.c
517
- }
518
- parent.c = tmp
519
- }
520
- if (tmp.f) {
521
- return xhr.load(
522
- replace(tmp.f, /^|,/g, "$&" + (View.path || "")).split(","),
523
- bind(readTemplates, view, view.wait(tmp.f = ""))
524
- )
525
- } else if (!tmp.e) {
526
- if (tmp.r === "404") {
527
- viewParse("%view 404 #\nh2 Not found")
528
- }
529
- return viewShow("404")
530
- }
531
- }
532
-
533
- for (tmp in params) {
534
- if (tmp.charAt(0) !== "_" && (syncResume = hasOwn(paramCb, tmp) && paramCb[tmp] || paramCb["*"])) {
535
- syncResume(params[tmp], tmp, view, params)
536
- syncResume = UNDEF
537
- }
538
- }
539
- viewEmit(view, "nav")
540
- bubbleDown(params)
541
623
  }
542
624
  function viewShow(url) {
543
625
  if (url === true) {
@@ -549,11 +631,17 @@ console.log("LiteJS is in debug mode, but it's fine for production")
549
631
  , view = viewGet(url, params)
550
632
  if (!view.o || lastUrl !== url) {
551
633
  $d.url = lastUrl = expand(url)
552
- viewPing(view, params)
634
+ viewEmit(lastView = view, "nav", lastParams = params)
635
+ bubbleUp(params)
553
636
  }
554
637
  }
555
638
 
556
- function addPlugin(name, proto) {
639
+ // Plugin properties:
640
+ // .n name .x parent view name .u parent DOM element
641
+ // .e container el .d done callback .c saved elCache (for %el/%view)
642
+ // When proto is a function, plugin accumulates raw text:
643
+ // .r raw handler .t accumulated text .o original op+text .s separator
644
+ function addPlugin(name, proto, expectContent) {
557
645
  plugins[name] = Plugin
558
646
  function Plugin(parent, op, sep) {
559
647
  var plugin = this
@@ -567,15 +655,14 @@ console.log("LiteJS is in debug mode, but it's fine for production")
567
655
  plugin.o = op
568
656
  plugin.s = sep
569
657
  } else {
570
- if (plugin.c) {
658
+ if (expectContent) {
571
659
  elCache = create(plugin.c = elCache)
572
660
  }
573
661
  plugin.e = El(name === "svg" ? name : "div")
574
662
  plugin.e.p = plugin
575
663
  }
576
664
  }
577
- if (proto.r) proto.d = Function("p", "p.r(p.o+p.t)")
578
- assign(Plugin.prototype, proto)
665
+ assign(Plugin.prototype, isFn(proto) ? { d: Function("p", "p.r(p.o+p.t)"), r: proto } : proto)
579
666
  }
580
667
  function usePluginContent(plugin) {
581
668
  var el = plugin.e
@@ -609,69 +696,44 @@ console.log("LiteJS is in debug mode, but it's fine for production")
609
696
  parent._cp = parent.childNodes.length - 1
610
697
  }
611
698
  })
612
- addPlugin("css", { r: injectCss })
613
- addPlugin("def", { r: viewDef })
614
- addPlugin("js", { r: viewEval })
615
- addPlugin("each", {
616
- r: function() {
617
- var txt = this.t
618
- each(this.o, function(param) {
619
- viewParse(replace(txt, /{key}/g, param))
620
- })
621
- }
699
+ addPlugin("css", injectCss)
700
+ addPlugin("def", viewDef)
701
+ addPlugin("js", viewEval)
702
+ addPlugin("each", function() {
703
+ var txt = this.t
704
+ each(this.o, function(param) {
705
+ viewParse(replace(/{key}/g, param, txt))
706
+ })
622
707
  })
623
708
  addPlugin("el", {
624
- c: 1,
625
709
  d: function(plugin, el) {
626
710
  el = usePluginContent(plugin)
627
711
  elCache[plugin.n] = el
628
712
  }
629
- })
713
+ }, 1)
630
714
  plugins.svg = plugins.el
631
- addPlugin("map", {
632
- r: function(txt) {
633
- var plugin = this
634
- appendBind(plugin.u, plugin.s ? txt.slice(1) : txt, plugin.s)
635
- }
715
+ addPlugin("map", function(txt) {
716
+ var plugin = this
717
+ appendBind(plugin.u, plugin.s ? txt.slice(1) : txt, plugin.s)
636
718
  })
637
719
  addPlugin("view", {
638
- c: 1,
639
720
  d: function(plugin) {
640
721
  var expr = getAttr(plugin.e, "_b")
641
722
  , view = View(plugin.n, usePluginContent(plugin), plugin.x)
642
723
  if (expr) {
643
- viewEval(replace(expr, renderRe, function(_, name, op, args) {
724
+ viewEval(replace(renderRe, function(_, name, op, args) {
644
725
  return "($s." + name + (isFn(view[name]) ? "(" + (args || "") + ")" : "=" + args) + "),"
645
- }) + "1", view)
726
+ }, expr) + "1", view)
646
727
  }
647
728
  }
648
- })
729
+ }, 1)
649
730
 
650
731
  /*** breakpoints ***/
651
- var lastSize, lastOrient
652
- , breakpoints = opts.breakpoints
653
- , setBreakpointsRated = rate(function() {
732
+ var breakpoints = opts.breakpoints
733
+ , setBreakpointsRated = rate(function(width) {
654
734
  // document.documentElement.clientWidth is 0 in IE5
655
- var point, next
656
- , width = html.offsetWidth
657
-
658
- for (point in breakpoints) {
659
- if (breakpoints[point] > width) break
660
- next = point
661
- }
662
-
663
- if (next != lastSize) {
664
- cls(html, lastSize, 0)
665
- cls(html, lastSize = next)
666
- }
667
-
668
- next = width > html.offsetHeight ? "land" : "port"
669
-
670
- if (next != lastOrient) {
671
- cls(html, lastOrient, 0)
672
- cls(html, lastOrient = next)
673
- }
674
-
735
+ bindingsIs(html, (width = html.offsetWidth), breakpoints, "")
736
+ bindingsIs(html, +(width > html.offsetHeight), "port,1=land", "")
675
737
  emit(View, "resize")
676
738
  }, 99)
677
739
 
@@ -744,18 +806,15 @@ console.log("LiteJS is in debug mode, but it's fine for production")
744
806
  pattern: function(str, re) {
745
807
  var values = []
746
808
  , t = translations["~"] || {}
747
- , key = replace(str, RegExp(re || t[""] || "[\\d.]+", "g"), function(a) {
809
+ , key = replace(RegExp(re || t[""] || "[\\d.]+", "g"), function(a) {
748
810
  values.push(a)
749
811
  return "#"
750
- })
751
- return str != key ? replace(iGet(t, key, str), /#/g, bind(values.shift, values)) : str
812
+ }, str)
813
+ return str != key ? replace(/#/g, bind(values.shift, values), iGet(t, key, str)) : str
752
814
  },
753
815
  pick: function(val, word) {
754
- for (var t = translations["?"] || {}, arr = replace((t[word] || word), /([^;=,]+?)\?/g, "$1=$1;").split(/[;=,]/), i = 1|arr.length; i > 0; ) {
755
- if ((i-=2) < 0 || arr[i] && (arr[i] == "" + val || +arr[i] <= val)) {
756
- return arr[i + 1] ? replace(arr[i + 1], "#", val) : ""
757
- }
758
- }
816
+ var t = translations["?"] || {}
817
+ return pick(val, t[word] || word)
759
818
  },
760
819
  plural: function(n, word, expr) {
761
820
  var t = translations["*"] || {}
@@ -781,10 +840,10 @@ console.log("LiteJS is in debug mode, but it's fine for production")
781
840
  S: "Milliseconds()"
782
841
  }
783
842
  , setA = "a.setTime(+d+((4-(" + get + dateMap.d + "))*864e5))"
784
- return replace((utc ? mask.slice(4) : mask), dateRe, function(match, MD, single, pad, text, esc) {
843
+ return replace(dateRe, function(match, MD, single, pad, text, esc) {
785
844
  mask = (
786
- esc ? replace(replace(escape(esc), /%u/g, "\\u"), /%/g, "\\x") :
787
- text !== UNDEF ? replace(text, /''/g, "'") :
845
+ esc ? replace(/%/g, "\\x", replace(/%u/g, "\\u", escape(esc))) :
846
+ text !== UNDEF ? replace(/''/g, "'", text) :
788
847
  MD || match == "dd" ? "l[''][" + get + (MD == "M" ? "Month()+" + (match == "MMM" ? 14 : 26) : "Day()" + (pad ? (pad = "") : "+7")) + "]" :
789
848
  match == "u" ? "(d/1000)>>>0" :
790
849
  match == "U" ? "+d" :
@@ -801,7 +860,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
801
860
  pad ? "(t=" + mask + ")>9?t:'0'+t" :
802
861
  mask
803
862
  ) + ")+\""
804
- })
863
+ }, (utc ? mask.slice(4) : mask))
805
864
  }
806
865
 
807
866
  function numStr(format, t) {
@@ -820,7 +879,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
820
879
  , m3 = /([.,\/])(\d*)$/.exec(m2[2])
821
880
  , decimals = m3 && m3[2].length || 0
822
881
  , full = m3 ? m2[2].slice(0, m3.index) : m2[2]
823
- , num = replace(full, /\D+/g, "")
882
+ , num = replace(/\D+/g, "", full)
824
883
  , sLen = num.length
825
884
  , step = decimals ? +(m3[1] === "/" ? 1 / m3[2] : num + "." + m3[2]) : num
826
885
  , decSep = m3 && m3[1]
@@ -864,7 +923,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
864
923
  fn += (
865
924
  (m2[4] ? ",r=" + (post[m2[4]] || "r+o") : "") +
866
925
  // negative format
867
- ",N&&n>0?" + replace(quote(conf[2] || "-#"), "#", "'+r+'") + ":" +
926
+ ",N&&n>0?" + replace("#", "'+r+'", quote(conf[2] || "-#")) + ":" +
868
927
  (conf[3] ? "n===0?" + quote(conf[3]) + ":" : "") +
869
928
  (m2[1] ? quote(m2[1]) + "+r" : "r") +
870
929
  (m2[6] ? "+" + quote(m2[6]) : "")
@@ -889,11 +948,11 @@ console.log("LiteJS is in debug mode, but it's fine for production")
889
948
  return format(iGet(translations, str, str || ""), data, getExt)
890
949
  }
891
950
  function getExt(obj, str) {
892
- var fn = cache[str] || (cache[str] = (replace(replace(str, /;\s*([#*?@~])(.*)/, function(_, op, arg) {
893
- return ";" + iAlias[op] + " " + quote(arg)
894
- }), renderRe, function(_, name, op, args) {
951
+ var fn = cache[str] || (cache[str] = (replace(renderRe, function(_, name, op, args) {
895
952
  fn = (_ === name) ? name : "$el." + name + "(" + fn + (args ? "," + args : "") + ")"
896
- }), fn === str ? str : makeFn(fn, fn)))
953
+ }, replace(/;\s*([#*?@~])(.*)/, function(_, op, arg) {
954
+ return ";" + iAlias[op] + " " + quote(arg)
955
+ }, str)), fn === str ? str : makeFn(fn, fn)))
897
956
  return str == "$" ? obj : isStr(fn) ? iGet(obj, str, "") : isFn(fn) ? fn(iExt, obj, translations) : ""
898
957
  }
899
958
  })
@@ -924,7 +983,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
924
983
  }
925
984
  function iGet(obj, path, fallback, tmp) {
926
985
  return isStr(path) ? (
927
- isStr(obj[path]) ? obj[path] :
986
+ NUL != obj[path] ? obj[path] :
928
987
  isStr(obj[tmp = path.toLowerCase()]) ? (
929
988
  path.slice(1) === tmp.slice(1) ? obj[tmp].charAt(0).toUpperCase() + obj[tmp].slice(1) :
930
989
  path === tmp.toUpperCase() ? obj[tmp].toUpperCase() :
@@ -943,17 +1002,13 @@ console.log("LiteJS is in debug mode, but it's fine for production")
943
1002
  return View
944
1003
  }
945
1004
 
946
- function setUrl(url, rep, checkUrl) {
1005
+ function setUrl(url, rep) {
947
1006
  /*** pushState ***/
948
1007
  if (pushBase) {
949
1008
  history[rep ? "replaceState" : "pushState"](NUL, NUL, pushBase + url)
950
- } else {
1009
+ } else
951
1010
  /**/
952
1011
  location[rep ? "replace" : "assign"]("#" + url)
953
- /*** pushState ***/
954
- }
955
- /**/
956
- if (checkUrl) checkUrl()
957
1012
  }
958
1013
 
959
1014
  LiteJS.go = setUrl
@@ -965,7 +1020,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
965
1020
  , baseEl = find(html, "base")
966
1021
  , url = getUrl()
967
1022
  if (baseEl && history.pushState) {
968
- pushBase = replace(baseEl.href, /.*:\/\/[^/]*|[^\/]*$/g, "")
1023
+ pushBase = replace(/.*:\/\/[^/]*|[^\/]*$/g, "", baseEl.href)
969
1024
 
970
1025
  if (url && !getUrl()) {
971
1026
  setUrl(url, 1)
@@ -987,12 +1042,12 @@ console.log("LiteJS is in debug mode, but it's fine for production")
987
1042
  if (cb && histLast != (histLast = getUrl())) cb(histLast)
988
1043
  }
989
1044
  function getUrl() {
990
- return replace(
1045
+ return replace(/^[#\/\!]+|[\s\/]+$/g, "",
991
1046
  /*** pushState ***/
992
1047
  pushBase ? location.pathname.slice(pushBase.length) :
993
1048
  /**/
994
1049
  // NOTE: in Firefox location.hash is decoded; in Safari location.pathname is decoded
995
- location.href.split("#")[1] || "", /^[#\/\!]+|[\s\/]+$/g, "")
1050
+ location.href.split("#")[1] || "")
996
1051
  }
997
1052
  }
998
1053
 
@@ -1004,7 +1059,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1004
1059
  function El(name) {
1005
1060
  var attr
1006
1061
  , attrs = {}
1007
- , el = replace(name, selectorRe, function(_, op, key, fn, val, quotation) {
1062
+ , el = replace(selectorRe, function(_, op, key, fn, val, quotation) {
1008
1063
  attr = 1
1009
1064
  val = quotation ? val.slice(1, -1) : val || key
1010
1065
  attrs[op =
@@ -1018,7 +1073,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1018
1073
  attrs[op] + (fn === "~" ? " " : "") + val :
1019
1074
  val
1020
1075
  return ""
1021
- }) || "div"
1076
+ }, name) || "div"
1022
1077
 
1023
1078
  // NOTE: IE-s cloneNode consolidates the two text nodes together as one
1024
1079
  // http://brooknovak.wordpress.com/2009/08/23/ies-clonenode-doesnt-actually-clone/
@@ -1042,14 +1097,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1042
1097
  empty: elEmpty,
1043
1098
  kill: elKill,
1044
1099
  off: acceptMany(rmEvent),
1045
- one: acceptMany(function(el, ev, fn) {
1046
- function remove() {
1047
- rmEvent(el, ev, fn)
1048
- rmEvent(el, ev, remove)
1049
- }
1050
- addEvent(el, ev, fn)
1051
- addEvent(el, ev, remove)
1052
- }),
1053
1100
  render: render,
1054
1101
  rm: elRm
1055
1102
  })
@@ -1126,12 +1173,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1126
1173
  return true
1127
1174
  }
1128
1175
  },
1129
- is: function(el, val, opts, prefix) {
1130
- if (!isStr(prefix)) prefix = "is-"
1131
- var match = elScope(el)._.ext.pick(val, opts)
1132
- cls(el, el[prefix + opts], 0)
1133
- cls(el, el[prefix + opts] = match && prefix + match)
1134
- },
1176
+ is: bindingsIs,
1135
1177
  name: function(el, name) {
1136
1178
  setAttr(el, "name", expand(name, 1))
1137
1179
  },
@@ -1164,8 +1206,8 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1164
1206
  rate: rate,
1165
1207
  replace: elReplace,
1166
1208
  scope: elScope,
1167
- scrollLeft: scrollLeft,
1168
- scrollTop: scrollTop,
1209
+ scrollLeft: bind(scrollPos, NUL, "pageXOffset", "scrollLeft"),
1210
+ scrollTop: bind(scrollPos, NUL, "pageYOffset", "scrollTop"),
1169
1211
  step: step,
1170
1212
  stop: eventStop
1171
1213
  })
@@ -1173,12 +1215,12 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1173
1215
  function setAttr(el, key, val) {
1174
1216
  var current = getAttr(el, key)
1175
1217
 
1176
- /*** ie8 ***/
1177
1218
  // NOTE: IE5-7 doesn't set styles and removes events when you try to set them.
1178
1219
  // IE6 label with a for attribute will re-select the first option of SELECT list instead of just giving focus.
1179
1220
  // http://webbugtrack.blogspot.com/2007/09/bug-116-for-attribute-woes-in-ie6.html
1180
1221
  // IE8 and below have a bug where changed 'name' not accepted on form submit
1181
- /* c8 ignore next 3 */
1222
+ /* c8 ignore next 4 */
1223
+ /*** ie9 ***/
1182
1224
  if (ie678 && (key === "id" || key === "name" || key === "checked" || key === "style")) {
1183
1225
  el.mergeAttributes(document.createElement("<INPUT " + key + "='" + val + "'>"), false)
1184
1226
  } else
@@ -1227,7 +1269,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1227
1269
  el = before.parentNode
1228
1270
  }
1229
1271
  el.insertBefore(child, (
1230
- isNum(before) ? el.childNodes[before < 0 ? el.childNodes.length - before - 2 : before] :
1272
+ isNum(before) ? el.childNodes[before < 0 ? el.childNodes.length + before : before] :
1231
1273
  isArr(before) ? before[0] :
1232
1274
  before
1233
1275
  ) || NUL)
@@ -1276,7 +1318,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1276
1318
  name = current.split(SP).indexOf(name) > -1 ? current : current + SP + name
1277
1319
  }
1278
1320
  } else {
1279
- name = current ? replace(SP + current + SP, SP + name + SP, SP).trim() : current
1321
+ name = current ? replace(SP + name + SP, SP, SP + current + SP).trim() : current
1280
1322
  }
1281
1323
 
1282
1324
  if (current != name) {
@@ -1286,6 +1328,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1286
1328
  el.className = name
1287
1329
  }
1288
1330
  }
1331
+ return current
1289
1332
  }
1290
1333
 
1291
1334
  function elEmpty(el) {
@@ -1299,7 +1342,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1299
1342
  if (isObj(tr)) bindingsCss(el, tr)
1300
1343
  tr = "transitionend"
1301
1344
  // transitionend fires for each property transitioned
1302
- if ("on" + tr in el) return addEvent(el, tr, bind(elKill, el, el, el = UNDEF))
1345
+ if ("on" + tr in el) return addEvent(el, tr, bind(elKill, el, el))
1303
1346
  }
1304
1347
  if (el._e) {
1305
1348
  emit(el, "kill")
@@ -1324,76 +1367,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1324
1367
  closestScope(el)
1325
1368
  )
1326
1369
  }
1327
- function elTxt(el, txt) {
1328
- if (el[txtAttr] !== txt) el[txtAttr] = txt
1329
- }
1330
- function elVal(el, val) {
1331
- if (!el) return ""
1332
- var input, step, key, value
1333
- , i = 0
1334
- , type = el.type
1335
- , opts = el.options
1336
- , checkbox = type === "checkbox" || type === "radio"
1337
-
1338
- if (el.tagName === "FORM") {
1339
- // Disabled controls do not receive focus,
1340
- // are skipped in tabbing navigation, cannot be successfully posted.
1341
- //
1342
- // Read-only elements receive focus but cannot be modified by the user,
1343
- // are included in tabbing navigation, are successfully posted.
1344
- //
1345
- // Read-only checkboxes can be changed by the user
1346
-
1347
- for (opts = {}; (input = el.elements[i++]); ) if (!input.disabled && (key = input.name || input.id)) {
1348
- value = elVal(input, val != UNDEF ? val[key] : UNDEF)
1349
- if (value !== UNDEF) {
1350
- step = opts
1351
- replace(key, /\[(.*?)\]/g, replacer)
1352
- step[key || step.length] = value
1353
- }
1354
- }
1355
- return opts
1356
- }
1357
-
1358
- if (val !== UNDEF) {
1359
- if (opts) {
1360
- for (value = (isArr(val) ? val : [ val ]).map(String); (input = opts[i++]); ) {
1361
- input.selected = value.indexOf(input.value) > -1
1362
- }
1363
- } else if (el.val) {
1364
- el.val(val)
1365
- } else if (checkbox) {
1366
- el.checked = !!val
1367
- } else {
1368
- el.value = val
1369
- }
1370
- return
1371
- }
1372
-
1373
- if (opts) {
1374
- if (type === "select-multiple") {
1375
- for (val = []; (input = opts[i++]); ) {
1376
- if (input.selected && !input.disabled) {
1377
- val.push(input.valObject || input.value)
1378
- }
1379
- }
1380
- return val
1381
- }
1382
- // IE8 throws error when accessing to options[-1]
1383
- value = el.selectedIndex
1384
- el = value > -1 && opts[value] || el
1385
- }
1386
-
1387
- return checkbox && !el.checked ?
1388
- (type === "radio" ? UNDEF : NUL) :
1389
- el.valObject !== UNDEF ? el.valObject : el.value
1390
-
1391
- function replacer(_, _key, offset) {
1392
- if (step == opts) key = key.slice(0, offset)
1393
- step = step[key] || (step[key] = step[key] === NUL || _key && +_key != _key ? {} : [])
1394
- key = _key
1395
- }
1396
- }
1397
1370
 
1398
1371
  function closestScope(node) {
1399
1372
  for (; (node = node.parentNode); ) {
@@ -1411,7 +1384,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1411
1384
  var el, next
1412
1385
  , scope = node.$s || $s || closestScope(node)
1413
1386
 
1414
- /*** ie8 ***/
1387
+ /*** ie9 ***/
1415
1388
  if (ie678 && node.tagName === "SELECT") {
1416
1389
  node.parentNode.insertBefore(node, node)
1417
1390
  }
@@ -1425,34 +1398,40 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1425
1398
  hydrate(node, "data-out", scope)
1426
1399
  }
1427
1400
 
1401
+ // Reads binding expression from DOM attr (_b or data-out), compiles via makeFn, executes.
1402
+ // Caches expr on node[attr] to avoid re-reading DOM; true = no bindings (already processed).
1403
+ // Returns truthy if binding replaced the element (if/each), so render() skips children.
1428
1404
  function hydrate(node, attr, scope) {
1429
1405
  var fn
1430
1406
  , expr = node[attr] || (node[attr] = setAttr(node, attr, "") || true)
1431
1407
  if (expr !== true) try {
1432
1408
  fn = fnCache[expr] || (fnCache[expr] = makeFn(expr))
1433
- return fn(node, scope, attr, bindOnce)
1409
+ return fn(node, scope, attr, bindOnce, elScope)
1434
1410
  } catch (e) {
1435
1411
  throw e + "\n" + attr + ": " + expr
1436
1412
  }
1437
1413
  }
1414
+ // Compiles binding expression string (e.g. ";txt foo;cls 'active',bar") into a Function.
1415
+ // Extracts free variable names and aliases them from scope ($s.varName).
1416
+ // raw parameter bypasses the $s guard wrapper (used by i18n getExt).
1438
1417
  function makeFn(fn, raw, i) {
1439
- fn = raw || "$s&&(" + replace(fn, renderRe, function(match, name, op, args) {
1418
+ fn = raw || "$s&&(" + replace(renderRe, function(match, name, op, args) {
1440
1419
  return (
1441
1420
  op ? "($el[$a]=$el[$a].replace($o[" + (i = bindOnce.indexOf(match), i < 0 ? bindOnce.push(match) - 1 : i)+ "],''),0)||" : ""
1442
- ) + "$b['" + (bindings[name] ? name + "'].call($s" + (name == "$s" ? "=El.scope($el,$el)": "") + ",$el" : "set']($el,'" + name + "'") + (args ? "," + args : "") + ")||"
1443
- }) + "$r)"
1444
- var vars = replace(fn, fnRe, "").match(wordRe) || []
1421
+ ) + "$b['" + (bindings[name] ? name + "'].call($s" + (name == "$s" ? "=$S($el,$el)": "") + ",$el" : "set']($el,'" + name + "'") + (args ? "," + args : "") + ")||"
1422
+ }, fn) + "$r)"
1423
+ var vars = replace(fnRe, "", fn).match(wordRe) || []
1445
1424
  for (i = vars.length; i--; ) {
1446
- if (vars.indexOf(vars[i]) !== i) vars.splice(i, 1)
1425
+ if (window[vars[i]] || vars.indexOf(vars[i]) !== i) vars.splice(i, 1)
1447
1426
  else vars[i] += "=$s." + vars[i]
1448
1427
  }
1449
- fn = Function("$el,$s,$a,$o,$r", (vars[0] ? "var " + vars : "") + ";return " + fn)
1428
+ fn = Function("$el,$s,$a,$o,$S,$r", (vars[0] ? "var " + vars : "") + ";return " + fn)
1450
1429
  return fn
1451
1430
  }
1452
1431
 
1453
1432
  /*** kb ***/
1454
1433
  var kbMaps = []
1455
- , kbMod = LiteJS.kbMod = /^(Mac|iP)/.test(navigator.platform) ? "metaKey" : "ctrlKey"
1434
+ , kbMod = LiteJS.kbMod = /\bMac|\biP/.test(navigator.userAgent) ? "metaKey" : "ctrlKey"
1456
1435
  , kbCodes = LiteJS.kbCodes = ",,,,,,,,backspace,tab,,,,enter,,,shift,ctrl,alt,pause,caps,,,,,,,esc,,,,,,pgup,pgdown,end,home,left,up,right,down,,,,,ins,del,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,cmd,,,,,,,,,,,,,,,,,,,,,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12".split(splitRe)
1457
1436
 
1458
1437
  El.addKb = addKb
@@ -1636,6 +1615,19 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1636
1615
  }
1637
1616
  /**/
1638
1617
 
1618
+ function bindingsIs(el, val, opts, prefix) {
1619
+ if (!isStr(prefix)) prefix = "is-"
1620
+ var match = pick(val, opts)
1621
+ cls(el, el[prefix + opts], 0)
1622
+ cls(el, el[prefix + opts] = match && prefix + match)
1623
+ }
1624
+ function pick(val, word) {
1625
+ for (var arr = replace(/([^;=,]+?)\?/g, "$1=$1;", word).split(/[;=,]/), i = 1|arr.length; i > 0; ) {
1626
+ if ((i-=2) < 0 || arr[i] && (arr[i] == "" + val || +arr[i] <= val)) {
1627
+ return arr[i + 1] ? replace("#", val, arr[i + 1]) : ""
1628
+ }
1629
+ }
1630
+ }
1639
1631
  function closest(el, sel) {
1640
1632
  return el && html.closest.call(el.nodeType < 2 ? el : el.parentNode, sel)
1641
1633
  }
@@ -1651,6 +1643,9 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1651
1643
  function nearest(el, sel) {
1652
1644
  return el ? find(el, sel) || nearest(el.parentNode, sel) : NUL
1653
1645
  }
1646
+ // Wraps fn to accept: space-separated names, object maps {name:val}, CSS selectors, delays.
1647
+ // prepareVal=1: wraps val as event delegate (string val→emit on view, fn+selector→delegation)
1648
+ // After arg normalization, selector is reused as element array, delay as loop counter.
1654
1649
  function acceptMany(fn, prepareVal) {
1655
1650
  return function f(el, name, val, selector, delay, data) {
1656
1651
  if (el && name) {
@@ -1672,15 +1667,28 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1672
1667
  }
1673
1668
  return
1674
1669
  }
1675
- if (prepareVal) val = prepareVal(el, val, selector, data)
1670
+ if (prepareVal) val = delegate(el, val, selector, data)
1676
1671
  selector = !prepareVal && selector ? findAll(el, selector) : isArr(el) ? el : [ el ]
1677
1672
  for (delay = 0; (el = selector[delay++]); ) {
1678
- for (var arr = ("" + name).split(splitRe), i = 0, len = arr.length; i < len; ) {
1679
- if (arr[i]) fn(el, arr[i++], isArr(val) ? val[i - 1] : val, data)
1673
+ for (var result, arr = ("" + name).split(splitRe), i = 0, len = arr.length; i < len; i++) {
1674
+ if (arr[i]) {
1675
+ result = fn(el, arr[i], isArr(val) ? val[i] : val, data)
1676
+ if (!prepareVal && data > 0) f(el, name, result, "", data)
1677
+ }
1680
1678
  }
1681
1679
  }
1682
1680
  }
1683
1681
  }
1682
+ function delegate(el, val, selector, data) {
1683
+ return isStr(val) ? function(e) {
1684
+ var target = selector ? closest(e.target, selector) : el
1685
+ if (target) emit.apply(target, [elScope(el).$ui, val, e, target].concat(data))
1686
+ } :
1687
+ selector ? function(e, touchEv, touchEl) {
1688
+ if (matches(touchEl = e.target, selector)) val(e, touchEv, touchEl, data)
1689
+ } :
1690
+ val
1691
+ }
1684
1692
  }
1685
1693
  function assignDeep(target, map) {
1686
1694
  if (map) for (var k in map) if (hasOwn(map, k)) {
@@ -1695,9 +1703,6 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1695
1703
  document.activeElement.blur()
1696
1704
  } catch(e) {}
1697
1705
  }
1698
- function camelFn(_, a) {
1699
- return a.toUpperCase()
1700
- }
1701
1706
  function each(arr, fn, scope, key) {
1702
1707
  if (arr) {
1703
1708
  if (isStr(arr)) arr = arr.split(splitRe)
@@ -1738,7 +1743,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1738
1743
  return typeof str === "string"
1739
1744
  }
1740
1745
  function quote(str) {
1741
- return "'" + replace(replace(str || "", /'/g, "\\'"), /\n/g, "\\n") + "'"
1746
+ return "'" + replace(/\n/g, "\\n", replace(/'/g, "\\'", str || "")) + "'"
1742
1747
  }
1743
1748
  // Maximum call rate for Function with optional leading edge and trailing edge
1744
1749
  function rate(fn, ms, onStart, onEnd) {
@@ -1760,11 +1765,8 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1760
1765
  }
1761
1766
  }
1762
1767
  }
1763
- function scrollLeft() {
1764
- return window.pageXOffset || html.scrollLeft || body.scrollLeft || 0
1765
- }
1766
- function scrollTop() {
1767
- return window.pageYOffset || html.scrollTop || body.scrollTop || 0
1768
+ function scrollPos(page, key) {
1769
+ return window[page] || html[key] || body[key] || 0
1768
1770
  }
1769
1771
  function step(num, factor, mid) {
1770
1772
  var x = ("" + factor).split(".")
@@ -1780,7 +1782,7 @@ console.log("LiteJS is in debug mode, but it's fine for production")
1780
1782
  elKill(el)
1781
1783
  return el.src
1782
1784
  }), function(res) {
1783
- res = res.concat(sources, next && next.src && next.innerHTML).filter(Boolean)
1785
+ res = res.concat(sources, next && next.src && next.innerHTML)
1784
1786
  if (res[sources.length = 0]) {
1785
1787
  if (!parser) LiteJS.ui = LiteJS()
1786
1788
  each(res, parser)