@litejs/ui 23.3.0 → 23.3.2

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/css/base.css CHANGED
@@ -231,13 +231,24 @@ button,
231
231
  float: right !important;
232
232
  }
233
233
 
234
+ .sm .sm-t-left,
235
+ .md .md-t-left,
236
+ .lg .md-t-left,
237
+ .lg .lg-t-left,
234
238
  .t-left {
235
239
  text-align: left;
236
240
  }
237
-
241
+ .sm .sm-t-center,
242
+ .md .md-t-center,
243
+ .lg .md-t-center,
244
+ .lg .lg-t-center,
238
245
  .t-center {
239
246
  text-align: center;
240
247
  }
248
+ .sm .sm-t-right,
249
+ .md .md-t-right,
250
+ .lg .md-t-right,
251
+ .lg .lg-t-right,
241
252
  .t-right {
242
253
  text-align: right;
243
254
  }
package/index.js CHANGED
@@ -98,7 +98,6 @@
98
98
  }(this) // jshint ignore:line
99
99
 
100
100
 
101
- /*! litejs.com/MIT-LICENSE.txt */
102
101
 
103
102
  /* litejs.com/MIT-LICENSE.txt */
104
103
 
@@ -227,7 +226,6 @@
227
226
 
228
227
 
229
228
 
230
- /*! litejs.com/MIT-LICENSE.txt */
231
229
 
232
230
  /* litejs.com/MIT-LICENSE.txt */
233
231
 
@@ -344,7 +342,7 @@
344
342
  if (view !== close) emit(view, "change", close)
345
343
 
346
344
  for (tmp in params) if (tmp.charAt(0) !== "_") {
347
- if ((syncResume = hasOwn.call(paramCb, tmp)) && paramCb[tmp] || paramCb["*"]) {
345
+ if ((syncResume = hasOwn.call(paramCb, tmp) && paramCb[tmp] || paramCb["*"])) {
348
346
  syncResume.call(view, params[tmp], tmp, params)
349
347
  syncResume = null
350
348
  }
@@ -488,7 +486,6 @@
488
486
  }(this) // jshint ignore:line
489
487
 
490
488
 
491
- /*! litejs.com/MIT-LICENSE.txt */
492
489
 
493
490
  /* litejs.com/MIT-LICENSE.txt */
494
491
 
@@ -509,6 +506,8 @@
509
506
  , templateRe = /([ \t]*)(%?)((?:("|')(?:\\\4|.)*?\4|[-\w:.#[\]]=?)*)[ \t]*([>^;@|\\\/]|!?=|)(([\])}]?).*?([[({]?))(?=\x1f|\n|$)+/g
510
507
  , renderRe = /[;\s]*(\w+)(?:(::?| )((?:(["'\/])(?:\\\3|.)*?\3|[^;])*))?/g
511
508
  , selectorRe = /([.#:[])([-\w]+)(?:\(((?:[^()]|\([^)]+\))+?)\)|([~^$*|]?)=(("|')(?:\\.|[^\\])*?\6|[-\w]+))?]?/g
509
+ , fnRe = /('|")(?:\\\1|.)*?\1|\/(?:\\?.)+?\/[gim]*|\b(?:n|data|b|s|B|r|false|in|new|null|this|true|typeof|void|function|var|if|else|return)\b|\.\w+|\w+:/g
510
+ , wordRe = /\b[a-z_$][\w$]*/ig
512
511
  , splitRe = /[,\s]+/
513
512
  , camelRe = /\-([a-z])/g
514
513
  , bindings = El.bindings = {
@@ -619,7 +618,8 @@
619
618
 
620
619
  // NOTE: IE-s cloneNode consolidates the two text nodes together as one
621
620
  // http://brooknovak.wordpress.com/2009/08/23/ies-clonenode-doesnt-actually-clone/
622
- el = (elCache[name] || (elCache[name] = document.createElement(name))).cloneNode(true)
621
+ el = (name = elCache[name] || (elCache[name] = document.createElement(name))).cloneNode(true)
622
+ el._s = name._s
623
623
 
624
624
  if (pres) {
625
625
  setAttr(el, pre)
@@ -726,7 +726,7 @@
726
726
  // Read-only checkboxes can be changed by the user
727
727
 
728
728
  for (opts = {}; (input = el.elements[i++]); ) if (!input.disabled && (key = input.name || input.id)) {
729
- value = valFn(input)
729
+ value = valFn(input, val != UNDEF ? val[key] : UNDEF)
730
730
  if (value !== UNDEF) {
731
731
  step = opts
732
732
  key.replace(/\[(.*?)\]/g, replacer)
@@ -737,7 +737,7 @@
737
737
  return opts
738
738
  }
739
739
 
740
- if (arguments.length > 1) {
740
+ if (val !== UNDEF) {
741
741
  if (opts) {
742
742
  value = (isArray(val) ? val : [ val ]).map(String)
743
743
  for (; (input = opts[i++]); ) {
@@ -798,8 +798,12 @@
798
798
 
799
799
  if (child.nodeType) {
800
800
  tmp = el.insertBefore ? el : el[el.length - 1]
801
- if ((i = getAttr(tmp, "data-child"))) {
802
- before = findCom(tmp, i) || tmp
801
+ if ((i = getAttr(child, "slot"))) {
802
+ child.removeAttribute("slot")
803
+ before = findCom(tmp, "%slot-" + i) || tmp
804
+ tmp = before.parentNode
805
+ } else if ((i = getAttr(tmp, "data-slot"))) {
806
+ before = findCom(tmp, "%slot-" + i) || tmp
803
807
  tmp = before.parentNode
804
808
  // TODO:2016-07-05:lauri:handle numeric befores
805
809
  }
@@ -1071,14 +1075,30 @@
1071
1075
  if (!node) return
1072
1076
  var bind, fn
1073
1077
  , scope = elScope(node, 0, _scope)
1074
- , i = 0
1075
1078
 
1076
1079
  if (node.nodeType != 1) {
1077
1080
  if (node.render) node.render(scope)
1078
1081
  return
1079
1082
  }
1080
1083
 
1081
- if ((bind = getAttr(node, BIND_ATTR))) {
1084
+ hydrate(node, BIND_ATTR, scope)
1085
+ for (bind = node.firstChild; bind; bind = fn) {
1086
+ fn = bind.nextSibling
1087
+ render(bind, scope)
1088
+ }
1089
+ hydrate(node, "data-out", scope)
1090
+
1091
+ /*** ie8 ***/
1092
+ if (ie678 && node.tagName === "SELECT") {
1093
+ node.parentNode.insertBefore(node, node)
1094
+ }
1095
+ /**/
1096
+ }
1097
+
1098
+ function hydrate(node, attr, scope) {
1099
+ var bind, fn
1100
+ , i = 0
1101
+ if ((bind = getAttr(node, attr))) {
1082
1102
  scope._m = bindMatch
1083
1103
  scope._t = bind
1084
1104
  // i18n(bind, lang).format(scope)
@@ -1100,7 +1120,13 @@
1100
1120
  }) + "r)"
1101
1121
 
1102
1122
  try {
1103
- if (Function("n,data,b,s,B,r", "with(data||{})return " + fn).call(node, node, scope, bindings, setAttr, BIND_ATTR)) {
1123
+ var vars = fn.replace(fnRe, "").match(wordRe) || []
1124
+ for (i = vars.length; i--; ) if (vars.indexOf(vars[i]) !== i) vars.splice(i, 1)
1125
+ if (Function(
1126
+ "n,data,b,s,B,r",
1127
+ (vars[0] ? "var " + vars.join("='',") + "='';" : "") +
1128
+ "with(data||{})return " + fn
1129
+ ).call(node, node, scope, bindings, setAttr, attr)) {
1104
1130
  return
1105
1131
  }
1106
1132
  } catch (e) {
@@ -1113,21 +1139,17 @@
1113
1139
  }
1114
1140
  }
1115
1141
  }
1116
-
1117
- for (bind = node.firstChild; bind; bind = fn) {
1118
- fn = bind.nextSibling
1119
- render(bind, scope)
1120
- }
1121
- /*** ie8 ***/
1122
- if (ie678 && node.tagName === "SELECT") {
1123
- node.parentNode.insertBefore(node, node)
1124
- }
1125
- /**/
1126
1142
  }
1127
1143
 
1144
+
1128
1145
  El.empty = empty
1129
1146
  El.kill = kill
1130
1147
  El.render = render
1148
+ El.replace = replace
1149
+ function replace(oldEl, newEl) {
1150
+ var parent = oldEl && oldEl.parentNode
1151
+ if (parent && newEl) return parent.replaceChild(oldEl, newEl)
1152
+ }
1131
1153
 
1132
1154
  for (var key in El) wrap(key)
1133
1155
 
@@ -1148,10 +1170,8 @@
1148
1170
 
1149
1171
  wrapProto.append = function(el) {
1150
1172
  var elWrap = this
1151
- if (elWrap._ca > -1) {
1152
- append(elWrap[elWrap._ca], el)
1153
- // } else if (elWrap._cb > -1) {
1154
- // elWrap.splice(elWrap._cb, 0, el)
1173
+ if (elWrap._s) {
1174
+ append(elWrap[elWrap._s[getAttr(el, "slot") || elWrap._s._] || 0], el)
1155
1175
  } else {
1156
1176
  elWrap.push(el)
1157
1177
  }
@@ -1160,8 +1180,7 @@
1160
1180
 
1161
1181
  wrapProto.cloneNode = function(deep) {
1162
1182
  deep = new ElWrap(this, deep)
1163
- deep._ca = this._ca
1164
- //deep._cb = this._cb
1183
+ deep._s = this._s
1165
1184
  return deep
1166
1185
  }
1167
1186
 
@@ -1213,8 +1232,8 @@
1213
1232
  text = text.replace(/(\w+):?/, "on:'$1',")
1214
1233
  } else if (op != ";" && op != "^") {
1215
1234
  text = (parent.tagName === "INPUT" ? "val" : "txt") + (
1216
- op === "=" ? ":" + text.replace(/\\|'/g, "\\$&") :
1217
- ":_('" + text.replace(/\\|'/g, "\\$&") + "',data)"
1235
+ op === "=" ? ":" + text.replace(/'/g, "\\'") :
1236
+ ":_('" + text.replace(/'/g, "\\'") + "',data)"
1218
1237
  )
1219
1238
  }
1220
1239
  appendBind(parent, text, ";", op)
@@ -1251,8 +1270,10 @@
1251
1270
  , el = childNodes[1] ? new ElWrap(childNodes) : childNodes[0]
1252
1271
 
1253
1272
  if (i > -1) {
1254
- if (childNodes[i].nodeType == 1) setAttr(childNodes[el._ca = i], "data-child", t.el._ck)
1255
- // else el._cb = i
1273
+ if (childNodes[i].nodeType == 1 && t.el._sk) {
1274
+ setAttr(childNodes[i], "data-slot", t.el._sk)
1275
+ }
1276
+ el._s = t.el._s
1256
1277
  }
1257
1278
 
1258
1279
  t.el.plugin = t.el = t.parent = null
@@ -1284,12 +1305,14 @@
1284
1305
  Object.assign(bindings, Function("return({" + this.txt + "})")())
1285
1306
  }
1286
1307
  }),
1287
- child: extend(plugin, {
1308
+ slot: extend(plugin, {
1288
1309
  done: function() {
1289
- var key = "@child-" + (++seq)
1310
+ var name = this.name || ++seq
1311
+ var key = "%slot-" + name
1290
1312
  , root = append(this.parent, document.createComment(key))
1291
1313
  for (; root.parentNode; root = root.parentNode);
1292
- root._ck = key
1314
+ ;(root._s || (root._s = {}))[name] = root.childNodes.length - 1
1315
+ if (!this.name) root._s._ = root._sk = name
1293
1316
  root._cp = root.childNodes.length - 1
1294
1317
  }
1295
1318
  }),
@@ -1355,6 +1378,7 @@
1355
1378
  }
1356
1379
  })
1357
1380
  }
1381
+ El.plugins.child = El.plugins.slot
1358
1382
 
1359
1383
  xhr.view = xhr.tpl = El.tpl = parseTemplate
1360
1384
  xhr.css = function(str) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litejs/ui",
3
- "version": "23.3.0",
3
+ "version": "23.3.2",
4
4
  "description": "UI engine for LiteJS full-stack framework",
5
5
  "license": "MIT",
6
6
  "author": "Lauri Rooden <lauri@rooden.ee>",
@@ -25,7 +25,7 @@
25
25
  "test": "TZ='Europe/Tallinn' lj test test/index.js --brief --coverage && jshint --verbose *.js src/*.js binding/*.js polyfill/*.js"
26
26
  },
27
27
  "devDependencies": {
28
- "@litejs/cli": "23.3.0",
28
+ "@litejs/cli": "23.3.2",
29
29
  "jshint": "2.13.6"
30
30
  },
31
31
  "litejs": {
@@ -1 +0,0 @@
1
- El.tpl('%css .Segment7 { position: relative; height: 1em; width: .5em; margin-right: 8px; } .Seg-V, .Seg-H { position: absolute; top: 0; left: .105em; height: .098em; width: .28em; background-color: currentColor; } .Seg-V + .Seg-V { top: .392em; } .Seg-V + .Seg-V + .Seg-V { top: .784em; } .Seg:before, .Seg:after { position: absolute; content: ""; border: .049em solid transparent; } .Seg-V:before { left: -.098em; border-right-color: currentColor; } .Seg-V:after { right: -.098em; border-left-color: currentColor; } .Seg-H:before { top: -.098em; border-bottom-color: currentColor; } .Seg-H:after { bottom: -.098em; border-top-color: currentColor; } .Seg-H { top: .105em; left: 0; height: .28em; width: .098em; } .Seg-H + .Seg-H { left: .392em; } .Seg-H + .Seg-H + .Seg-H { top: .497em; } .Seg-H + .Seg-H + .Seg-H + .Seg-H { left: 0; } [data-value="0"]>[off~="0"], [data-value="1"]>[off~="1"], [data-value="2"]>[off~="2"], [data-value="3"]>[off~="3"], [data-value="4"]>[off~="4"], [data-value="5"]>[off~="5"], [data-value="6"]>[off~="6"], [data-value="7"]>[off~="7"], [data-value="8"]>[off~="8"], [data-value="9"]>[off~="9"], [data-value="a"]>[off~="a"], [data-value="b"]>[off~="b"], [data-value="c"]>[off~="c"], [data-value="d"]>[off~="d"], [data-value="e"]>[off~="e"], [data-value="f"]>[off~="f"] { color: rgba(0,0,0,0.07); }%el Segment7 .Segment7.left .Seg.Seg-V.Seg-A[off="1 4 b d"] .Seg.Seg-V.Seg-G[off="0 1 7 c"] .Seg.Seg-V.Seg-D[off="1 4 7 a f"] .Seg.Seg-H.Seg-F[off="1 2 3 7 d"] .Seg.Seg-H.Seg-B[off="5 6 b c e f"] .Seg.Seg-H.Seg-C[off="2 c e f"] .Seg.Seg-H.Seg-E[off="1 3 4 5 7 9"]')
package/el/confirm.tpl.js DELETED
@@ -1 +0,0 @@
1
- El.tpl('%css .Confirm { z-index: 9; } .Confirm-bg { backdrop-filter: blur(5px); background-color: rgba(0, 0, 0, .6); } .Confirm-content { position: absolute; left: 0; right: 0; margin: 0 auto; top: 4%; width: 600px; background-color: #fff; box-shadow: 0 2px 10px 2px rgba(255,255,255,.5); } .sm .Confirm-content { width: 94%; } .Confirm--blur { transform: scale(.85); transform-origin: 50% 100vh; }// reverse animation: x1, y1, x2, y2 -> (1 - x2), (1 - y2), (1 - x1), (1 - y1)// https://developer.mozilla.org/en-US/docs/Web/API/Notification// https://developers.google.com/web/fundamentals/push-notifications/display-a-notification// var n = new Notification(title, options);// {// "//": "Visual Options",// "body": "Did you make a $1,000,000 purchase at Dr. Evil...",// "icon": "images/ccard.png", // 192px or more is a safe bet// "image": "<URL String>", // width 1350px or more, ratio of 4:3 for desktop and Android will crop the image// "badge": "<URL String>", // 72px or more should be good// "vibrate": "<Array of Integers>",// "sound": "<URL String>",// "dir": "<String of \'auto\' | \'ltr\' | \'rtl\'>",// "//": "Behavioural Options",// "tag": "<String>", // group messages so that any old notifications that are currently displayed will be closed if they have the same tag as a new notification.// "data": "<Anything>",// "requireInteraction": "<boolean>", // for Chrome on desktop// "renotify": "<Boolean>",// "silent": "<Boolean>",// "//": "Both Visual & Behavioural Options",// "actions": "<Array of Strings>",// "//": "Information Option. No visual affect.",// "timestamp": "<Long>" // ms// }// Star Wars shamelessly taken from the awesome Peter Beverloo// https://tests.peter.sh/notification-generator/// "vibrate": [500,110,500,110,450,110,200,110,170,40,450,110,200,110,170,40,500]// "actions": [// { "action": "yes", "title": "Yes", "icon": "images/yes.png" },// { "action": "no", "title": "No", "icon": "images/no.png" }// ]%js View.on("confirm", function(title, opts, next) { View.blur() if (!next && typeof opts === "function") { next = opts opts = null } var sound, vibrate , code = "" , el = El("Confirm") , scope = El.scope(el, El.data) , kbMap = {} , body = document.body , blurEl = body.lastChild Object.assign(scope, opts) scope.title = title || "Confirm?" if (!scope.actions) scope.actions = [ { action: "close", title: "Close", key: "esc" } ] for (var a, i = 0; a = scope.actions[i++]; ) { if (typeof a == "string") a = scope.actions[i-1] = {title:a,action:a} if (a.key) kbMap[a.key] = resolve.bind(el, el, a.action) } El.cls(blurEl, "Confirm--blur") El.cls(el.lastChild, "Confirm--blur", 0, 1) El.append(body, el) El.render(el, scope) if (scope.code) { El.findAll(el, ".js-numpad").on("click", numpad) kbMap.backspace = kbMap.del = kbMap.num = numpad } El.addKb(kbMap, el) El.findAll(el, ".js-btn").on("click", resolve) View.one("navigation", resolve) if (scope.bgClose) El.on(el, "click", resolve) El.on(el, "wheel", Event.stop) El.on(el.lastChild, "click", Event.stop) if (scope.vibrate && navigator.vibrate) { vibrate = navigator.vibrate(scope.vibrate) } if (scope.sound && window.Audio) { sound = new Audio(scope.sound) sound.play() } function numpad(e, _num) { // Enter pressed on focused element if (_num == void 0 && e.clientX == 0) return var num = _num == void 0 ? e.target[El.T] : _num code += num if (num == "CLEAR" || num == "del" || num == "backspace") code = "" El.md(El.find(el, ".js-body"), code.replace(/./g, "•") || opts.body) if (typeof scope.code == "number" && code.length == scope.code && id && !sent) next(sent = code, id, resolve, reject) } function resolve(e, key) { if (el) { var action = key || El.attr(this, "data-action") , result = { code: code, input: El.val(El.find(el, ".js-input")), inputMd: El.val(El.find(el, ".js-inputMd")), select: El.val(El.find(el, ".js-select")) } El.kill(el, "transparent") El.cls(blurEl, "Confirm--blur", el = 0) if (action && next) { if (typeof next === "function") next(action, result) else if (typeof next[action] === "function") next[action](result) else if (next[action]) View.emit(next[action], result) } if (vibrate) navigator.vibrate(0) if (sound) sound.pause() } } scope.resolve = resolve View.emit("confirm:open", scope) })%el Confirm .Confirm.max.fix.anim .Confirm-bg.max.abs .Confirm-content.Confirm--blur.grid.p2.anim .col.ts3 ;txt:: _(title, map) .col.js-body ;md:: _(body, map) .row.js-numpad ;if: code ;each: num in [1,2,3,4,5,6,7,8,9,"CLEAR",0] .col.w4>.btn {num} .row ;if: input .col>input.field.js-input .row ;if: data.inputMd!=null .col textarea.field.js-inputMd @keyup [this.parentNode.nextSibling.nextSibling], "renderMd" ;val: inputMd .col.ts3 Preview .p4 ;md: inputMd .row ;if: select .col select.field.js-select ;list: select, [""] option ;val:: item.id ;txt:: _(item.name) .col .group ;each: action in actions .btn.js-btn ;txt:: _(action.title) ;class:: "w" + (12/actions.length) ;nop: this.focus() ;data: "action", action.action ;class:: "is-" + action.action, action.action')
package/el/form1.tpl.js DELETED
@@ -1 +0,0 @@
1
- El.tpl('%css ::-moz-focus-inner { border: 0; padding: 0; } .Form1-del.right { display: block; margin: -10px -10px 0 0; opacity: .2; } .Form1-del { font-size: 20px; font-weight: 700; border: 1px solid transparent; line-height: 16px; width: 20px; height: 20px; text-align: center; border-radius: 4px; } .Form1-del:hover { opacity: 1; border: 1px solid #aaa; background-image: linear-gradient(to bottom, #ddd, #888); } /** * 1. avoid ios styling the submit button */ .input { display: block; border-radius: 4px; border: 1px solid #aaa; } .field { width: 100%; } .btn, input, select, textarea { display: block; border-radius: 4px; border: 1px solid #aaa; font-size: 14px; font-weight: 400; line-height: 30px; height: 32px; padding: 0 8px; margin: 0; } input[type=checkbox] { height: auto; } input[type=time] { padding: 0 0 0 8px; } textarea { height: 64px; padding: 8px; margin: 0; line-height: 1.1; } select { padding-right: 0; } select[multiple] { height: auto; padding: 0; } input[type=radio], input[type=checkbox] { width: auto; display: inline; margin-top: -2px; } .btn, input[type=submit] { /* 1 */ -webkit-appearance: none; /* 1 */ position: relative; padding: 0px 14px; text-align: center; text-decoration: none; /* default look */ background-color: #ddd; color: #444; cursor: pointer; } option[disabled], .btn.disabled, .btn[disabled] { box-shadow: none; cursor: not-allowed; font-style: italic; opacity: .6; pointer-events: none; } .group { overflow: auto; } .group > .btn { border-radius: 0; float: left; } .group > .btn:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .group > .btn:last-child { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .btn--narrow { line-height: 1.6; margin: .7em 0; } .btn__spacer { height: 33px; } .md .input__label, .lg .input__label { padding-right: 8px; text-align: right; line-height: 28px; } .input__hint { text-align: right; color: #444; } input[type=checkbox]+.input__hint { display: inline-block; margin-left: 8px; } .btn:active, .btn:focus, input:active, input:focus, select:active, select:focus, textarea:active, textarea:focus { border-color: #257; outline: 0 none; box-shadow: 0 2px 5px rgba(0, 0, 0, .5) inset, 0 0 2px 2px #6ae; z-index: 1; } .btn:hover, .btn:focus { filter: brightness(1.3) saturate(1.2); text-decoration: none; } .btn:active, .btn.is-active { box-shadow: inset 0 0 8px rgba(0, 0, 0, .5); }%el form1-row label.row .col.md-w4.input__label = _(title||name) .col.md-w8 %child .input__hint = _(description) ;if: description%el form1-subheader .col = _(title)%el form1-fieldset fieldset.grid.b2 legend = _(schema.title || _link.title || "")%el form1 form1-row input.field%el form1-ro form1-row>span ;txt: value%el form1-hidden div>input.field[type=hidden]%el form1-boolean form1-row input.field[type=checkbox] ;value: value%el form1-boolean-ro form1-row>span = _(!!value)%el form1-password form1-row input.field[type=password]%el form1-new-password form1-row input.field[type=password][autocomplete=new-password]%el form1-text form1-row textarea.field%el form1-text-ro form1-ro%el form1-enum form1-row select.field ;each:val in data["enum"] option ;val:: val = _("" + val)%el form1-enum-ro form1-ro%el form1-list form1-row select.field ;list: api(resourceCollection.format(data.params, data)), required ? 0 : [""], value option ;val:: item.id ;txt:: _(item.name)%el form1-list-ro form1-row>span = _(item.name)%el form1-array .col .input.p13.cf .left = _(title||name) .input__hint = _(description) .js-items.cf a.btn.right ;if: !data.noAdd ;txt: _(data.name + ".Add") @click: data.add%el form1-array-item .input.p3.m2b.cf.js-del a.right.Form1-del.hand × ;if: !data.noAdd ;data:: "tooltip", _("Delete") @click: data.del b ;if: title ;txt: title .grid.b2.js-item')