luci-base: add markup, JS and CSS for new dropdown
authorJo-Philipp Wich <jo@mein.io>
Fri, 8 Jun 2018 05:29:44 +0000 (07:29 +0200)
committerJo-Philipp Wich <jo@mein.io>
Fri, 8 Jun 2018 06:14:36 +0000 (08:14 +0200)
This commit introduces the required code for a new, markup based dropdown
widget which can be used as a styleable alternative to select boxes or
radio/checkbox button groups.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/luasrc/cbi.lua
modules/luci-base/luasrc/view/cbi/dropdown.htm [new file with mode: 0644]
themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css
themes/luci-theme-material/htdocs/luci-static/material/css/style.css
themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css

index 0a1961916cb1388da46393b2abb73573d456682d..a7f999d8764dc7ab592b7d68e590a1d7367040ff 100644 (file)
@@ -465,31 +465,16 @@ function cbi_d_add(field, dep, index) {
 }
 
 function cbi_d_checkvalue(target, ref) {
-       var t = document.getElementById(target);
-       var value;
+       var value = null,
+           query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
+                   'select[id="'+target+'"], select[name="'+target+'"]';
 
-       if (!t) {
-               var tl = document.getElementsByName(target);
-
-               if( tl.length > 0 && (tl[0].type == 'radio' || tl[0].type == 'checkbox'))
-                       for( var i = 0; i < tl.length; i++ )
-                               if( tl[i].checked ) {
-                                       value = tl[i].value;
-                                       break;
-                               }
-
-               value = value ? value : "";
-       } else if (!t.value) {
-               value = "";
-       } else {
-               value = t.value;
-
-               if (t.type == "checkbox") {
-                       value = t.checked ? value : "";
-               }
-       }
+       document.querySelectorAll(query).forEach(function(i) {
+               if (value === null && ((i.type !== 'radio' && i.type !== 'checkbox') || i.checked === true))
+                       value = i.value;
+       });
 
-       return (value == ref)
+       return (((value !== null) ? value : "") == ref);
 }
 
 function cbi_d_check(deps) {
@@ -634,6 +619,10 @@ function cbi_init() {
                                   node.getAttribute('data-type'));
        }
 
+       document.querySelectorAll('.cbi-dropdown').forEach(function(s) {
+                cbi_dropdown_init(s);
+       });
+
        cbi_d_update();
 }
 
@@ -1574,3 +1563,484 @@ function E()
 
        return elem;
 }
+
+if (typeof(window.CustomEvent) !== 'function') {
+       function CustomEvent(event, params) {
+               params = params || { bubbles: false, cancelable: false, detail: undefined };
+               var evt = document.createEvent('CustomEvent');
+                   evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+               return evt;
+       }
+
+       CustomEvent.prototype = window.Event.prototype;
+       window.CustomEvent = CustomEvent;
+}
+
+CBIDropdown = {
+       openDropdown: function(sb) {
+               var st = window.getComputedStyle(sb, null),
+                   ul = sb.querySelector('ul'),
+                   li = ul.querySelectorAll('li'),
+                   sel = ul.querySelector('[selected]'),
+                   rect = sb.getBoundingClientRect(),
+                   h = sb.clientHeight - parseFloat(st.paddingTop) - parseFloat(st.paddingBottom),
+                   mh = this.dropdown_items * h,
+                   eh = Math.min(mh, li.length * h);
+
+               document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
+                       s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
+               });
+
+               ul.style.maxHeight = mh + 'px';
+               sb.setAttribute('open', '');
+
+               ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0;
+               ul.querySelectorAll('[selected] input[type="checkbox"]').forEach(function(c) {
+                       c.checked = true;
+               });
+
+               ul.style.top = ul.style.bottom = '';
+               ul.style[((sb.getBoundingClientRect().top + eh) > window.innerHeight) ? 'bottom' : 'top'] = rect.height + 'px';
+               ul.classList.add('dropdown');
+
+               var pv = ul.cloneNode(true);
+                   pv.classList.remove('dropdown');
+                   pv.classList.add('preview');
+
+               sb.insertBefore(pv, ul.nextElementSibling);
+
+               li.forEach(function(l) {
+                       l.setAttribute('tabindex', 0);
+               });
+
+               sb.lastElementChild.setAttribute('tabindex', 0);
+
+               this.setFocus(sb, sel || li[0], true);
+       },
+
+       closeDropdown: function(sb, no_focus) {
+               if (!sb.hasAttribute('open'))
+                       return;
+
+               var pv = sb.querySelector('ul.preview'),
+                   ul = sb.querySelector('ul.dropdown'),
+                   li = ul.querySelectorAll('li');
+
+               li.forEach(function(l) { l.removeAttribute('tabindex'); });
+               sb.lastElementChild.removeAttribute('tabindex');
+
+               sb.removeChild(pv);
+               sb.removeAttribute('open');
+               sb.style.width = sb.style.height = '';
+
+               ul.classList.remove('dropdown');
+
+               if (!no_focus)
+                       this.setFocus(sb, sb);
+
+               this.saveValues(sb, ul);
+       },
+
+       toggleItem: function(sb, li, force_state) {
+               if (li.hasAttribute('unselectable'))
+                       return;
+
+               if (this.multi) {
+                       var cbox = li.querySelector('input[type="checkbox"]'),
+                           items = li.parentNode.querySelectorAll('li'),
+                           label = sb.querySelector('ul.preview'),
+                           sel = li.parentNode.querySelectorAll('[selected]').length,
+                           more = sb.querySelector('.more'),
+                           ndisplay = this.display_items,
+                           n = 0;
+
+                       if (li.hasAttribute('selected')) {
+                               if (force_state !== true) {
+                                       if (sel > 1 || this.optional) {
+                                               li.removeAttribute('selected');
+                                               cbox.checked = cbox.disabled = false;
+                                               sel--;
+                                       }
+                                       else {
+                                               cbox.disabled = true;
+                                       }
+                               }
+                       }
+                       else {
+                               if (force_state !== false) {
+                                       li.setAttribute('selected', '');
+                                       cbox.checked = true;
+                                       cbox.disabled = false;
+                                       sel++;
+                               }
+                       }
+
+                       while (label.firstElementChild)
+                               label.removeChild(label.firstElementChild);
+
+                       for (var i = 0; i < items.length; i++) {
+                               items[i].removeAttribute('display');
+                               if (items[i].hasAttribute('selected')) {
+                                       if (ndisplay-- > 0) {
+                                               items[i].setAttribute('display', n++);
+                                               label.appendChild(items[i].cloneNode(true));
+                                       }
+                                       var c = items[i].querySelector('input[type="checkbox"]');
+                                       if (c)
+                                               c.disabled = (sel == 1 && !this.optional);
+                               }
+                       }
+
+                       if (ndisplay < 0)
+                               sb.setAttribute('more', '');
+                       else
+                               sb.removeAttribute('more');
+
+                       if (ndisplay === this.display_items)
+                               sb.setAttribute('empty', '');
+                       else
+                               sb.removeAttribute('empty');
+
+                       more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : 'ยทยทยท';
+               }
+               else {
+                       var sel = li.parentNode.querySelector('[selected]');
+                       if (sel) {
+                               sel.removeAttribute('display');
+                               sel.removeAttribute('selected');
+                       }
+
+                       li.setAttribute('display', 0);
+                       li.setAttribute('selected', '');
+
+                       this.closeDropdown(sb, true);
+               }
+
+               this.saveValues(sb, li.parentNode);
+       },
+
+       transformItem: function(sb, li) {
+               var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })),
+                   label = E('label');
+
+               while (li.firstChild)
+                       label.appendChild(li.firstChild);
+
+               li.appendChild(cbox);
+               li.appendChild(label);
+       },
+
+       saveValues: function(sb, ul) {
+               var sel = ul.querySelectorAll('[selected]'),
+                   div = sb.lastElementChild;
+
+               while (div.lastElementChild)
+                       div.removeChild(div.lastElementChild);
+
+               sel.forEach(function (s) {
+                       div.appendChild(E('input', {
+                               type: 'hidden',
+                               name: s.hasAttribute('name') ? s.getAttribute('name') : (sb.getAttribute('name') || ''),
+                               value: s.hasAttribute('value') ? s.getAttribute('value') : s.innerText
+                       }));
+               });
+
+               cbi_d_update();
+       },
+
+       setFocus: function(sb, elem, scroll) {
+               if (sb && sb.hasAttribute && sb.hasAttribute('locked-in'))
+                       return;
+
+               document.querySelectorAll('.focus').forEach(function(e) {
+                       if (e.nodeName.toLowerCase() !== 'input') {
+                               e.classList.remove('focus');
+                               e.blur();
+                       }
+               });
+
+               if (elem) {
+                       elem.focus();
+                       elem.classList.add('focus');
+
+                       if (scroll)
+                               elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop;
+               }
+       },
+
+       createItems: function(sb, value) {
+               var sbox = this,
+                   val = (value || '').trim().split(/\s+/),
+                   ul = sb.querySelector('ul');
+
+               if (!sbox.multi)
+                       val.length = Math.min(val.length, 1);
+
+               val.forEach(function(item) {
+                       var new_item = null;
+
+                       ul.childNodes.forEach(function(li) {
+                               if (li.getAttribute && li.getAttribute('value') === item)
+                                       new_item = li;
+                       });
+
+                       if (!new_item) {
+                               var markup,
+                                   tpl = sb.querySelector(sbox.template);
+
+                               if (tpl)
+                                       markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim();
+                               else
+                                       markup = '<li value="{{value}}">{{value}}</li>';
+
+                               new_item = E(markup.replace(/{{value}}/g, item));
+
+                               if (sbox.multi) {
+                                       sbox.transformItem(sb, new_item);
+                               }
+                               else {
+                                       var old = ul.querySelector('li[created]');
+                                       if (old)
+                                               ul.removeChild(old);
+
+                                       new_item.setAttribute('created', '');
+                               }
+
+                               new_item = ul.insertBefore(new_item, ul.lastElementChild);
+                       }
+
+                       sbox.toggleItem(sb, new_item, true);
+                       sbox.setFocus(sb, new_item, true);
+               });
+       },
+
+       closeAllDropdowns: function() {
+               document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
+                       s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
+               });
+       },
+
+       findParent: function(node, selector) {
+               while (node)
+                       if (node.msMatchesSelector && node.msMatchesSelector(selector))
+                               return node;
+                       else if (node.matches && node.matches(selector))
+                               return node;
+                       else
+                               node = node.parentNode;
+
+               return null;
+       }
+};
+
+function cbi_dropdown_init(sb) {
+       if (!(this instanceof cbi_dropdown_init))
+               return new cbi_dropdown_init(sb);
+
+       this.multi = sb.hasAttribute('multiple');
+       this.optional = sb.hasAttribute('optional');
+       this.placeholder = sb.getAttribute('placeholder') || '---';
+       this.display_items = parseInt(sb.getAttribute('display-items') || 3);
+       this.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || 5);
+       this.create = sb.getAttribute('item-create') || '.create-item-input';
+       this.template = sb.getAttribute('item-template') || 'script[type="item-template"]';
+
+       var sbox = this,
+           ul = sb.querySelector('ul'),
+           items = ul.querySelectorAll('li'),
+           more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, 'ยทยทยท')),
+           open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, 'โ–พ')),
+           canary = sb.appendChild(E('div')),
+           create = sb.querySelector(this.create),
+           ndisplay = this.display_items,
+           n = 0;
+
+       if (this.multi) {
+               for (var i = 0; i < items.length; i++) {
+                       sbox.transformItem(sb, items[i]);
+
+                       if (items[i].hasAttribute('selected') && ndisplay-- > 0)
+                               items[i].setAttribute('display', n++);
+               }
+       }
+       else {
+               var sel = sb.querySelectorAll('[selected]');
+
+               sel.forEach(function(s) {
+                       s.removeAttribute('selected');
+               });
+
+               var s = sel[0] || items[0];
+               if (s) {
+                       s.setAttribute('selected', '');
+                       s.setAttribute('display', n++);
+               }
+
+               ndisplay--;
+
+               if (this.optional && !ul.querySelector('li[value=""]')) {
+                       var placeholder = E('li', { placeholder: '' }, this.placeholder);
+                       ul.firstChild ? ul.insertBefore(placeholder, ul.firstChild) : ul.appendChild(placeholder);
+               }
+       }
+
+       sbox.saveValues(sb, ul);
+
+       ul.setAttribute('tabindex', -1);
+       sb.setAttribute('tabindex', 0);
+
+       if (ndisplay < 0)
+               sb.setAttribute('more', '')
+       else
+               sb.removeAttribute('more');
+
+       if (ndisplay === this.display_items)
+               sb.setAttribute('empty', '')
+       else
+               sb.removeAttribute('empty');
+
+       more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : 'ยทยทยท';
+
+
+       sb.addEventListener('click', function(ev) {
+               if (!this.hasAttribute('open')) {
+                       if (ev.target.nodeName.toLowerCase() !== 'input')
+                               sbox.openDropdown(this);
+               }
+               else {
+                       var li = sbox.findParent(ev.target, 'li');
+                       if (li && li.parentNode.classList.contains('dropdown'))
+                               sbox.toggleItem(this, li);
+               }
+
+               ev.preventDefault();
+               ev.stopPropagation();
+       });
+
+       sb.addEventListener('keydown', function(ev) {
+               if (ev.target.nodeName.toLowerCase() === 'input')
+                       return;
+
+               if (!this.hasAttribute('open')) {
+                       switch (ev.keyCode) {
+                       case 37:
+                       case 38:
+                       case 39:
+                       case 40:
+                               sbox.openDropdown(this);
+                               ev.preventDefault();
+                       }
+               }
+               else
+               {
+                       var active = sbox.findParent(document.activeElement, 'li');
+
+                       switch (ev.keyCode) {
+                       case 27:
+                               sbox.closeDropdown(this);
+                               break;
+
+                       case 13:
+                               if (active) {
+                                       if (!active.hasAttribute('selected'))
+                                               sbox.toggleItem(this, active);
+                                       sbox.closeDropdown(this);
+                                       ev.preventDefault();
+                               }
+                               break;
+
+                       case 32:
+                               if (active) {
+                                       sbox.toggleItem(this, active);
+                                       ev.preventDefault();
+                               }
+                               break;
+
+                       case 38:
+                               if (active && active.previousElementSibling) {
+                                       sbox.setFocus(this, active.previousElementSibling);
+                                       ev.preventDefault();
+                               }
+                               break;
+
+                       case 40:
+                               if (active && active.nextElementSibling) {
+                                       sbox.setFocus(this, active.nextElementSibling);
+                                       ev.preventDefault();
+                               }
+                               break;
+                       }
+               }
+       });
+
+       sb.addEventListener('cbi-dropdown-close', function(ev) {
+               sbox.closeDropdown(this, true);
+       });
+
+       if ('ontouchstart' in window) {
+               sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); });
+               window.addEventListener('touchstart', sbox.closeAllDropdowns);
+       }
+       else {
+               sb.addEventListener('mouseover', function(ev) {
+                       if (!this.hasAttribute('open'))
+                               return;
+
+                       var li = sbox.findParent(ev.target, 'li');
+                       if (li) {
+                               if (li.parentNode.classList.contains('dropdown'))
+                                       sbox.setFocus(this, li);
+
+                               ev.stopPropagation();
+                       }
+               });
+
+               sb.addEventListener('focus', function(ev) {
+                       document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
+                               if (s !== this || this.hasAttribute('open'))
+                                       s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
+                       });
+               });
+
+               canary.addEventListener('focus', function(ev) {
+                       sbox.closeDropdown(this.parentNode);
+               });
+
+               window.addEventListener('mouseover', sbox.setFocus);
+               window.addEventListener('click', sbox.closeAllDropdowns);
+       }
+
+       if (create) {
+               create.addEventListener('keydown', function(ev) {
+                       switch (ev.keyCode) {
+                       case 13:
+                               sbox.createItems(sb, this.value);
+                               ev.preventDefault();
+                               this.value = '';
+                               this.blur();
+                               break;
+                       }
+               });
+
+               create.addEventListener('focus', function(ev) {
+                       var cbox = sbox.findParent(this, 'li').querySelector('input[type="checkbox"]');
+                       if (cbox) cbox.checked = true;
+                       sb.setAttribute('locked-in', '');
+               });
+
+               create.addEventListener('blur', function(ev) {
+                       var cbox = sbox.findParent(this, 'li').querySelector('input[type="checkbox"]');
+                       if (cbox) cbox.checked = false;
+                       sb.removeAttribute('locked-in');
+               });
+
+               var li = sbox.findParent(create, 'li');
+
+               li.setAttribute('unselectable', '');
+               li.addEventListener('click', function(ev) {
+                       this.querySelector(sbox.create).focus();
+               });
+       }
+}
+
+cbi_dropdown_init.prototype = CBIDropdown;
index 3a55eb23c853a33331b2279565fceb9dbad71c65..d275c5b27564366041a56fc3010b07d4a938e8dc 100644 (file)
@@ -1846,6 +1846,15 @@ function DynamicList.formvalue(self, section)
 end
 
 
+DropDown = class(MultiValue)
+
+function DropDown.__init__(self, ...)
+       ListValue.__init__(self, ...)
+       self.template = "cbi/dropdown"
+       self.delimiter = " "
+end
+
+
 --[[
 TextValue - A multi-line value
        rows:   Rows
diff --git a/modules/luci-base/luasrc/view/cbi/dropdown.htm b/modules/luci-base/luasrc/view/cbi/dropdown.htm
new file mode 100644 (file)
index 0000000..bdf7248
--- /dev/null
@@ -0,0 +1,42 @@
+<%+cbi/valueheader%>
+
+<%-
+       local selected = { }
+
+       if self.multiple then
+               local val
+               for val in luci.util.imatch(self:cfgvalue(section)) do
+                       selected[val] = true
+               end
+       else
+               selected[self:cfgvalue(section)] = true
+       end
+
+       if not next(selected) and self.default then
+               selected[self.default] = true
+       end
+-%>
+
+<div class="cbi-dropdown"<%=
+       attr("name", cbid) ..
+       attr("display-items", self.display or self.size or 3) ..
+       attr("dropdown-items", self.dropdown or self.display or self.size or 5) ..
+       attr("placeholder", self.placeholder or translate("-- please select --")) ..
+       ifattr(self.multiple, "multiple", "multiple") ..
+       ifattr(self.optional or self.rmempty, "optional", "optional")
+%>>
+       <ul>
+               <% local i, key; for i, key in pairs(self.keylist) do %>
+                       <li<%=
+                               attr("data-index", i) ..
+                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
+                               attr("value", key) ..
+                               ifattr(selected[key], "selected", "selected")
+                       %>>
+                               <%=pcdata(self.vallist[i])%>
+                       </li>
+               <% end %>
+       </ul>
+</div>
+
+<%+cbi/valuefooter%>
index 5aff0914858c69e2e8f03ad24c68d3c4c0b41e61..4a40f6d680f1fe24492fcd82235f4ce14325e380 100644 (file)
@@ -430,7 +430,6 @@ form .clearfix,
 form .cbi-value {
        margin-bottom: 18px;
        zoom: 1;
-       overflow: hidden;
 }
 
 form .clearfix:before, form .clearfix:after,
@@ -477,6 +476,7 @@ input[type=checkbox], input[type=radio] {
 input,
 textarea,
 select,
+.cbi-dropdown,
 .uneditable-input {
        display: inline-block;
        width: 210px;
@@ -487,6 +487,13 @@ select,
        color: #808080;
        border: 1px solid #ccc;
        border-radius: 3px;
+       box-sizing: border-box;
+}
+
+.cbi-dropdown {
+       min-width: 210px;
+       max-width: 400px;
+       width: auto;
 }
 
 select {
@@ -1332,6 +1339,159 @@ footer {
        background-image: url('../resources/cbi/download.gif'), linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
 }
 
+.cbi-dropdown {
+       border: 1px solid #ccc;
+       border-radius: 3px;
+       display: inline-flex;
+       padding: 0;
+       cursor: pointer;
+       height: auto;
+       background: linear-gradient(#fff 0%, #e9e8e6 100%);
+       position: relative;
+       color: #404040;
+}
+
+.cbi-dropdown:focus {
+       outline: 2px solid #4b6e9b;
+}
+
+.cbi-dropdown > ul {
+       margin: 0 !important;
+       padding: 0;
+       list-style: none;
+       overflow-x: hidden;
+       overflow-y: auto;
+       display: flex;
+       width: 100%;
+}
+
+.cbi-dropdown > ul.preview {
+       display: none;
+}
+
+.cbi-dropdown > .open,
+.cbi-dropdown > .more {
+       flex-grow: 0;
+       flex-shrink: 0;
+       display: flex;
+       flex-direction: column;
+       justify-content: center;
+       text-align: center;
+       line-height: 2em;
+       padding: 0 .25em;
+}
+
+.cbi-dropdown > .more,
+.cbi-dropdown > ul > li[placeholder] {
+       color: #777;
+       font-weight: bold;
+       text-shadow: 1px 1px 0px #fff;
+       display: none;
+}
+
+.cbi-dropdown > ul > li {
+       display: none;
+       padding: .25em;
+       white-space: nowrap;
+       overflow: hidden;
+       text-overflow: ellipsis;
+       flex-shrink: 1;
+       flex-grow: 1;
+       align-items: center;
+       align-self: center;
+       color: #404040;
+       min-height: 20px;
+}
+
+.cbi-dropdown > ul > li .hide-open { display: initial; }
+.cbi-dropdown > ul > li .hide-close { display: none; }
+
+.cbi-dropdown > ul > li[display]:not([display="0"]) {
+       border-left: 1px solid #ccc;
+}
+
+.cbi-dropdown[empty] > ul {
+       max-width: 1px;
+}
+
+.cbi-dropdown > ul > li > form {
+       display: none;
+       margin: 0;
+       padding: 0;
+       pointer-events: none;
+}
+
+.cbi-dropdown > ul > li img {
+       vertical-align: middle;
+       margin-right: .25em;
+}
+
+.cbi-dropdown > ul > li > form > input[type="checkbox"] {
+       margin: 0;
+}
+
+.cbi-dropdown > ul > li input[type="text"] {
+       height: 20px;
+}
+
+.cbi-dropdown[open] {
+       position: relative;
+}
+
+.cbi-dropdown[open] > ul.dropdown {
+       display: block;
+       background: #f6f6f5;
+       border: 1px solid #918e8c;
+       box-shadow: 0 0 4px #918e8c;
+       position: absolute;
+       z-index: 1000;
+       max-width: none;
+       min-width: 100%;
+       width: auto;
+}
+
+.cbi-dropdown > ul > li[display],
+.cbi-dropdown[open] > ul.preview,
+.cbi-dropdown[open] > ul.dropdown > li,
+.cbi-dropdown[multiple] > ul > li > label,
+.cbi-dropdown[multiple][open] > ul.dropdown > li,
+.cbi-dropdown[multiple][more] > .more,
+.cbi-dropdown[multiple][empty] > .more {
+       flex-grow: 1;
+       display: flex;
+}
+
+.cbi-dropdown[empty] > ul > li,
+.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],
+.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
+       display: block;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }
+.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; }
+
+.cbi-dropdown[open] > ul.dropdown > li {
+       border-bottom: 1px solid #ccc;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+       background: #b0d0f0;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+       background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%);
+}
+
+.cbi-dropdown[open] > ul.dropdown > li:last-child {
+       margin-bottom: 0;
+       border-bottom: none;
+}
+
+.cbi-dropdown[disabled] {
+       pointer-events: none;
+       opacity: .6;
+}
+
 .btn.active, .btn:active {
        box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
 }
@@ -1423,7 +1583,7 @@ button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner {
        opacity: 0.4;
 }
 
-.alert-message, .errorbox {
+.alert-message {
        position: relative;
        padding: 7px 15px;
        margin-bottom: 18px;
@@ -1646,7 +1806,6 @@ table table td,
        background-color: #FFFFFF;
        border: 1px solid #CCCCCC;
        padding: 2px;
-       margin-left: 2px;
        background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
        text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
        border-radius: 4px;
@@ -1712,7 +1871,7 @@ table table td,
 
 .zonebadge > em,
 .zonebadge > strong {
-       margin: 5px;
+       margin: 0 2px;
        display: inline-block;
 }
 
@@ -1720,6 +1879,10 @@ table table td,
        width: 6em;
 }
 
+.zonebadge > .ifacebadge {
+       margin-left: 2px;
+}
+
 .zonebadge-empty {
        border: 1px dashed #AAAAAA;
        color: #AAAAAA;
index a3b22ccf811d78761c431903bb95cf00a06d562e..a9585cf40ec2b1a4fcb6410d3c235c10cf501fdd 100755 (executable)
@@ -107,7 +107,8 @@ select {
 }
 
 select,
-input {
+input,
+.cbi-dropdown {
     background-color: transparent;
     color: rgba(0, 0, 0, .87);
     border: none;
@@ -157,7 +158,7 @@ header {
     transition: box-shadow .2s;
     float: left;
     position: fixed;
-    z-index: 101;
+    z-index: 2000;
 }
 
 footer {
@@ -452,11 +453,7 @@ fieldset {
     font-style: normal;
     line-height: 1;
     font-family: inherit;
-
     min-width: inherit;
-    overflow-x: auto;
-    overflow-y: hidden;
-
     border-radius: 0;
     background-color: #FFF;
     box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
@@ -626,6 +623,7 @@ td > table > tbody > tr > td,
     box-shadow: none;
 }
 
+.btn + .btn,
 form.inline + form.inline,
 .cbi-button + .cbi-button {
     margin-left: 0.6rem;
@@ -804,6 +802,172 @@ form.inline + form.inline,
     font-size: small;
 }
 
+
+.cbi-dropdown {
+    display: inline-flex;
+    cursor: pointer;
+    position: relative;
+    padding: 0;
+    height: auto;
+}
+
+.cbi-dropdown:focus {
+    outline: 2px solid #4b6e9b;
+}
+
+.cbi-dropdown > ul {
+    margin: 0 !important;
+    padding: 0;
+    list-style: none;
+    overflow-x: hidden;
+    overflow-y: auto;
+    display: flex;
+    width: 100%;
+}
+
+.cbi-dropdown > ul.preview {
+    display: none;
+}
+
+.cbi-dropdown > .open {
+    border: 2px outset #eee;
+    flex-basis: 15px;
+    background: #eee;
+}
+
+.cbi-dropdown > .open,
+.cbi-dropdown > .more {
+    flex-grow: 0;
+    flex-shrink: 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    text-align: center;
+    line-height: 2em;
+    padding: 0 .25em;
+}
+
+.cbi-dropdown > .more,
+.cbi-dropdown > ul > li[placeholder] {
+    color: #777;
+    font-weight: bold;
+    text-shadow: 1px 1px 0px #fff;
+    display: none;
+}
+
+.cbi-dropdown > ul > li {
+    display: none;
+    padding: .25em;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    flex-shrink: 1;
+    flex-grow: 1;
+    align-items: center;
+    align-self: center;
+    min-height: 20px;
+}
+
+.cbi-dropdown > ul > li .hide-open { display: initial; }
+.cbi-dropdown > ul > li .hide-close { display: none; }
+
+.cbi-dropdown > ul > li[display]:not([display="0"]) {
+    border-left: 1px solid #ccc;
+}
+
+.cbi-dropdown[empty] > ul {
+    max-width: 1px;
+}
+
+.cbi-dropdown > ul > li > form {
+    display: none;
+    margin: 0;
+    padding: 0;
+    pointer-events: none;
+}
+
+.cbi-dropdown > ul > li img {
+    vertical-align: middle;
+    margin-right: .25em;
+}
+
+.cbi-dropdown > ul > li > form > input[type="checkbox"] {
+    margin: 0;
+    height: auto;
+}
+
+.cbi-dropdown > ul > li input[type="text"] {
+    height: 20px;
+}
+
+.cbi-dropdown[open] {
+    position: relative;
+}
+
+.cbi-dropdown[open] > ul.dropdown {
+    display: block;
+    background: #f6f6f5;
+    border: 1px solid #918e8c;
+    box-shadow: 0 0 4px #918e8c;
+    position: absolute;
+    z-index: 1000;
+    max-width: none;
+    min-width: 100%;
+    width: auto;
+}
+
+.cbi-dropdown > ul > li[display],
+.cbi-dropdown[open] > ul.preview,
+.cbi-dropdown[open] > ul.dropdown > li,
+.cbi-dropdown[multiple] > ul > li > label,
+.cbi-dropdown[multiple][open] > ul.dropdown > li,
+.cbi-dropdown[multiple][more] > .more,
+.cbi-dropdown[multiple][empty] > .more {
+    flex-grow: 1;
+    display: flex;
+    align-items: center;
+}
+
+.cbi-dropdown[empty] > ul > li,
+.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],
+.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
+    display: block;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }
+.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; }
+
+.cbi-dropdown[open] > ul.dropdown > li {
+    border-bottom: 1px solid #ccc;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+    background: #b0d0f0;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+    background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%);
+}
+
+.cbi-dropdown[open] > ul.dropdown > li:last-child {
+    margin-bottom: 0;
+    border-bottom: none;
+}
+
+.cbi-dropdown[disabled] {
+    pointer-events: none;
+    opacity: .6;
+}
+
+.cbi-dropdown .zonebadge {
+    width: 100%;
+}
+
+.cbi-dropdown[open] .zonebadge {
+    width: auto;
+}
+
+
 /* luci */
 
 .hidden {
@@ -839,17 +1003,20 @@ form.inline + form.inline,
 }
 
 /* select */
-.cbi-value-field .cbi-input-select {
+.cbi-value-field .cbi-dropdown {
     min-width: 15rem;
 }
 
+.cbi-value-field .cbi-input-select {
+    width: 15rem;
+}
+
 .ifacebadge {
     display: inline-flex;
     border-bottom: 1px solid #CCCCCC;
     padding: 0.5rem 1rem;
-    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
     box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    background: #fff;
 }
 
 td > .ifacebadge,
@@ -858,12 +1025,17 @@ td > .ifacebadge,
     font-size: 0.9rem;
 }
 
+.ifacebadge > em,
 .ifacebadge > img {
-    float: right;
-    margin: 0 0.3rem;
+    display: inline-block;
+    margin: 0 .2rem;
     align-self: start;
 }
 
+.ifacebadge > img + img {
+    margin: 0 .2rem 0 0;
+}
+
 .network-status-table {
     display: flex;
     flex-wrap: wrap;
@@ -1038,8 +1210,8 @@ td > .ifacebadge,
 }
 
 .zonebadge > .ifacebadge {
-    padding: 0.2rem 1rem;
-    margin: 0.3rem;
+    padding: .2rem .3rem;
+    margin: 0.1rem 0.2rem;
     border: 1px solid #6C6C6C;
 }
 
@@ -1049,6 +1221,12 @@ td > .ifacebadge,
     margin-top: 0.3rem;
 }
 
+.zonebadge > em,
+.zonebadge > strong {
+    margin: 0 0.2rem;
+    display: inline-block;
+}
+
 .cbi-value-field .cbi-input-checkbox,
 .cbi-value-field .cbi-input-radio {
     margin-top: 0.5rem;
@@ -1073,10 +1251,14 @@ td > .ifacebadge,
     margin-top: -0.5rem;
 }
 
-.cbi-section-table-row > .cbi-value-field .cbi-input-select {
+.cbi-section-table-row > .cbi-value-field .cbi-dropdown {
     min-width: 7rem;
 }
 
+.cbi-section-table-row > .cbi-value-field .cbi-input-select {
+    width: 7rem;
+}
+
 .cbi-section-create > .cbi-button-add {
     margin: 0.5rem;
 }
@@ -1607,8 +1789,6 @@ body.lang_pl.node-main-login .cbi-value-title {
         line-height: 1;
         font-family: inherit;
         min-width: inherit;
-        overflow-x: auto;
-        overflow-y: hidden;
         border-radius: 0;
         background-color: #FFF;
         box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
@@ -1627,33 +1807,36 @@ body.lang_pl.node-main-login .cbi-value-title {
 
 @media screen and (min-width: 992px) {
     .cbi-value input[type="password"],
-    .cbi-value input[type="text"] {
-        min-width: 20rem;
+    .cbi-value input[type="text"],
+    .cbi-value-field .cbi-input-select {
+        width: 20rem;
     }
 
-    .cbi-value-field .cbi-input-select {
+    .cbi-value-field .cbi-dropdown {
         min-width: 20rem;
     }
 }
 
 @media screen and (min-width: 1280px) {
     .cbi-value input[type="password"],
-    .cbi-value input[type="text"] {
-        min-width: 22rem;
+    .cbi-value input[type="text"],
+    .cbi-value-field .cbi-input-select {
+        width: 22rem;
     }
 
-    .cbi-value-field .cbi-input-select {
+    .cbi-value-field .cbi-dropdown {
         min-width: 22rem;
     }
 }
 
 @media screen and (min-width: 1600px) {
     .cbi-value input[type="password"],
-    .cbi-value input[type="text"] {
-        min-width: 25rem;
+    .cbi-value input[type="text"],
+    .cbi-value-field .cbi-input-select {
+        width: 25rem;
     }
 
-    .cbi-value-field .cbi-input-select {
+    .cbi-value-field .cbi-dropdown {
         min-width: 25rem;
     }
 }
index 8b359d475ab74cf8ecd6f3393703c430cd50f5ca..1d5ac8ab242c1de3ce06c5bffb520d62c6e78238 100644 (file)
@@ -27,6 +27,7 @@ body {
 * {
        margin: 0;
        padding: 0;
+       box-sizing: border-box;
 }
 
 .table { display: table; }
@@ -456,13 +457,20 @@ select:hover {
        color: #000000;
 }
 
+input[type=text],
+input[type=password] {
+       padding: 1px 3px;
+}
+
 select,
 input[type=text],
 input[type=password] {
+       font-size: inherit;
        width: 20em;
 }
 
 .td select,
+.td .cbi-dropdown,
 .td input[type=text],
 .td input[type=password] {
        width: 99%;
@@ -773,6 +781,7 @@ div.cbi-section-create {
        vertical-align: top;
 }
 
+div.cbi-section-create .btn,
 div.cbi-section-create .cbi-button {
        margin: 0.25em;
 }
@@ -844,13 +853,6 @@ div.cbi-section-remove {
        vertical-align: bottom;
 }
 
-div.cbi-error {
-       font-size: 95%;
-       font-weight: bold;
-       color: #ff0000;
-       background-color: #ffffff;
-}
-
 .td.cbi-value-error {
        border-color: red;
 }
@@ -926,6 +928,169 @@ div.cbi-tab-descr {
        padding: 0.5em 0.5em 0.5em 2em;
 }
 
+
+.cbi-dropdown {
+       border: 1px inset #000;
+       display: inline-flex;
+       cursor: pointer;
+       background: #eee;
+       position: relative;
+       padding: 0;
+       color: #000;
+       min-width: 20em;
+}
+
+.cbi-dropdown:hover {
+       background: #fff;
+}
+
+.cbi-dropdown:focus {
+       outline: 2px solid #4b6e9b;
+}
+
+.cbi-dropdown > ul {
+       margin: 0 !important;
+       padding: 0;
+       list-style: none;
+       overflow-x: hidden;
+       overflow-y: auto;
+       display: flex;
+       width: 100%;
+}
+
+.cbi-dropdown > ul.preview {
+       display: none;
+}
+
+.cbi-dropdown > .open {
+       border: 2px outset #eee;
+       flex-basis: 15px;
+}
+
+.cbi-dropdown > .open,
+.cbi-dropdown > .more {
+       flex-grow: 0;
+       flex-shrink: 0;
+       display: flex;
+       flex-direction: column;
+       justify-content: center;
+       text-align: center;
+       line-height: 2em;
+       padding: 0 .25em;
+}
+
+.cbi-dropdown > .more,
+.cbi-dropdown > ul > li[placeholder] {
+       color: #777;
+       font-weight: bold;
+       text-shadow: 1px 1px 0px #fff;
+       display: none;
+}
+
+.cbi-dropdown > ul > li {
+       display: none;
+       padding: .25em;
+       white-space: nowrap;
+       overflow: hidden;
+       text-overflow: ellipsis;
+       flex-shrink: 1;
+       flex-grow: 1;
+       align-items: center;
+       align-self: center;
+       min-height: 20px;
+}
+
+.cbi-dropdown > ul > li .hide-open { display: initial; }
+.cbi-dropdown > ul > li .hide-close { display: none; }
+
+.cbi-dropdown > ul > li[display]:not([display="0"]) {
+       border-left: 1px solid #ccc;
+}
+
+.cbi-dropdown[empty] > ul {
+       max-width: 1px;
+}
+
+.cbi-dropdown > ul > li > form {
+       display: none;
+       margin: 0;
+       padding: 0;
+       pointer-events: none;
+}
+
+.cbi-dropdown > ul > li img {
+       vertical-align: middle;
+       margin-right: .25em;
+}
+
+.cbi-dropdown > ul > li > form > input[type="checkbox"] {
+       margin: 0;
+}
+
+.cbi-dropdown > ul > li input[type="text"] {
+       height: 20px;
+}
+
+.cbi-dropdown[open] {
+       position: relative;
+}
+
+.cbi-dropdown[open] > ul.dropdown {
+       display: block;
+       background: #f6f6f5;
+       border: 1px solid #918e8c;
+       box-shadow: 0 0 4px #918e8c;
+       position: absolute;
+       z-index: 1000;
+       max-width: none;
+       min-width: 100%;
+       width: auto;
+}
+
+.cbi-dropdown > ul > li[display],
+.cbi-dropdown[open] > ul.preview,
+.cbi-dropdown[open] > ul.dropdown > li,
+.cbi-dropdown[multiple] > ul > li > label,
+.cbi-dropdown[multiple][open] > ul.dropdown > li,
+.cbi-dropdown[multiple][more] > .more,
+.cbi-dropdown[multiple][empty] > .more {
+       flex-grow: 1;
+       display: flex;
+       align-items: center;
+}
+
+.cbi-dropdown[empty] > ul > li,
+.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],
+.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
+       display: block;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }
+.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; }
+
+.cbi-dropdown[open] > ul.dropdown > li {
+       border-bottom: 1px solid #ccc;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+       background: #b0d0f0;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+       background: linear-gradient(90deg, #a3c2e8 0%, #84aad9 100%);
+}
+
+.cbi-dropdown[open] > ul.dropdown > li:last-child {
+       margin-bottom: 0;
+       border-bottom: none;
+}
+
+.cbi-dropdown[disabled] {
+       pointer-events: none;
+       opacity: .6;
+}
+
+
 .left {
        text-align: left !important;
 }
@@ -985,9 +1150,16 @@ div.cbi-tab-descr {
        background: #90c0e0;
 }
 
-.ifacebadge > img {
+.ifacebadge, .zonebadge {
+       align-items: center;
+}
+
+.ifacebadge > img,
+.ifacebadge > em {
        margin-right: 5px;
        align-self: start;
+       display: inline-block;
+       height: 16px;
 }
 
 .ifacebadge-active {
@@ -1021,7 +1193,7 @@ div.cbi-tab-descr {
 .network-status-table .ifacebox-body {
        display: flex;
        flex-direction: column;
-       height: 100%;
+       flex: 1 0;
 }
 
 .network-status-table .ifacebox-body > span {
@@ -1047,12 +1219,12 @@ div.cbi-tab-descr {
        display: inline-block;
        white-space: nowrap;
        cursor: pointer;
+       border-radius: 3px;
 }
 
-.zonebadge em,
-.zonebadge strong {
+.zonebadge em,
+.zonebadge strong {
        margin: 3px;
-       display: inline-block;
 }
 
 .zonebadge input {
@@ -1060,6 +1232,18 @@ div.cbi-tab-descr {
        height: 1.5em;
 }
 
+.zonebadge .ifacebadge,
+.cbi-dropdown .ifacebadge {
+       margin: 0 .125em;
+}
+
+.zonebadge .ifacebadge img,
+.zonebadge .ifacebadge em,
+.cbi-dropdown .ifacebadge img,
+.cbi-dropdown .ifacebadge em {
+       margin: 0 1px;
+}
+
 .zonebadge-empty {
        border: 1px dashed #AAAAAA;
        color: #AAAAAA;