Index: .eslintrc.json
===================================================================
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,11 +1,13 @@
{
"extends": "eslint-config-eyeo",
"root": true,
"env": {
"browser": true,
"webextensions": true
},
"globals": {
- "ext": true
+ "ext": true,
+ "require": true,
+ "module": true
}
}
Index: .gitignore
===================================================================
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
node_modules/
smoke/
-^desktop-options.js
-skin/desktop-options.css
\ No newline at end of file
+desktop-options.js
+skin/desktop-options.css
+skin/io-toggle.css
Index: .hgignore
===================================================================
--- a/.hgignore
+++ b/.hgignore
@@ -1,4 +1,5 @@
node_modules/
smoke/
^desktop-options.js
-skin/desktop-options.css
\ No newline at end of file
+skin/desktop-options.css
+skin/io-toggle.css
Index: css/io-toggle.scss
===================================================================
new file mode 100644
--- /dev/null
+++ b/css/io-toggle.scss
@@ -0,0 +1,93 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+io-toggle
+{
+ --width: 30px;
+ --height: 8px;
+ --translateY: -4px;
+ --translateX: 14px;
+ display: inline-block;
+ width: var(--width);
+ height: var(--height);
+ border-radius: 4px;
+ background-color: #9b9b9b;
+ transition: background .2s ease-out;
+ will-change: background;
+ cursor: pointer;
+}
+
+io-toggle[checked]
+{
+ background-color: #92d3ea;
+}
+
+io-toggle[disabled]
+{
+ opacity: 0.5;
+ cursor: default;
+}
+
+io-toggle button
+{
+ width: calc(var(--height) * 2);
+ height: calc(var(--height) * 2);
+ padding: 0;
+ border: 2px solid #e1e0e1;
+ border-radius: var(--height);
+ transition: border .2s ease-out, box-shadow .2s ease-out,
+ transform .2s ease-out, width .2s ease-out;
+ will-change: border, box-shadow, transform, width;
+ transform: translateY(var(--translateY));
+ outline: none;
+ cursor: pointer;
+}
+
+io-toggle button[aria-checked="false"]
+{
+ background-color: #f1f1f1;
+ box-shadow: 0 1px 2px 0 #e5d1d1;
+}
+
+io-toggle button[aria-checked="false"]:hover
+{
+ box-shadow: 0 2px 4px 0 #d3b0b0;
+}
+
+io-toggle button[aria-checked="true"]
+{
+ background-color: #059cd0;
+ border: 2px solid #059cd0;
+ box-shadow: 0 1px 2px 0 #a6cede;
+ transform: translateY(var(--translateY)) translateX(var(--translateX));
+}
+
+io-toggle button[aria-checked="true"]:hover
+{
+ box-shadow: 0 2px 4px 0 #a6cede;
+}
+
+io-toggle button:focus,
+io-toggle button[aria-checked="true"]:focus
+{
+ border: 2px solid #87bffe;
+}
+
+body[dir="rtl"] io-toggle button[aria-checked="true"]
+{
+ transform: translateY(var(--translateY)) translateX(calc(var(--translateX) * -1));
+}
Index: js/io-element.js
===================================================================
--- a/js/io-element.js
+++ b/js/io-element.js
@@ -1,26 +1,94 @@
-/* globals module, require */
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
"use strict";
// Custom Elements ponyfill (a polyfill triggered on demand)
const customElementsPonyfill = require("document-register-element/pony");
if (typeof customElements !== "object")
customElementsPonyfill(window);
// external dependencies
const {default: HyperHTMLElement} = require("hyperhtml-element/cjs");
+// common DOM utilities exposed as IOElement.utils
+const DOMUtils = {
+
+ // boolean related operations/helpers
+ boolean: {
+ // utils.boolean.attribute(node, name, setAsTrue):void
+ // set a generic node attribute name as "true"
+ // if value is a boolean one or it removes the attribute
+ attribute(node, name, setAsTrue)
+ {
+ // don't use `this.value(value)` with `this` as context
+ // to make destructuring of helpers always work.
+ // @example
+ // const {attribute: setBoolAttr} = IOElement.utils.boolean;
+ // setBoolAttr(node, 'test', true);
+ if (DOMUtils.boolean.value(setAsTrue))
+ {
+ node.setAttribute(name, "true");
+ }
+ else
+ {
+ node.removeAttribute(name);
+ }
+ },
+
+ // utils.boolean.value(any):boolean
+ // it returns either true or false
+ // via truthy or falsy values, but also via strings
+ // representing "true", "false" as well as "0" or "1"
+ value(value)
+ {
+ if (typeof value === "string" && value.length)
+ {
+ try
+ {
+ value = JSON.parse(value);
+ }
+ catch (error)
+ {
+ // Ignore invalid JSON to continue using value as string
+ }
+ }
+ return !!value;
+ }
+ }
+};
+
// provides a unique-id suffix per each component
let counter = 0;
// common Custom Element class to extend
class IOElement extends HyperHTMLElement
{
+ // exposes DOM helpers as read only utils
+ static get utils()
+ {
+ return DOMUtils;
+ }
+
// get a unique ID or, if null, set one and returns it
static getID(element)
{
return element.getAttribute("id") || IOElement.setID(element);
}
// set a unique ID to a generic element and returns the ID
static setID(element)
@@ -36,16 +104,33 @@
return IOElement.getID(this);
}
// whenever an element is created, render its content once
created() { this.render(); }
// by default, render is a no-op
render() {}
+
+ // usually a template would contain a main element such
+ // input, button, div, section, etc.
+ // having a simple way to retrieve such element can be
+ // both semantic and handy, as opposite of using
+ // this.children[0] each time
+ get child()
+ {
+ let element = this.firstElementChild;
+ // if accessed too early, will render automatically
+ if (!element)
+ {
+ this.render();
+ element = this.firstElementChild;
+ }
+ return element;
+ }
}
// whenever an interpolation with ${{i18n: 'string-id'}} is found
// transform such value into the expected content
// example:
// render() {
// return this.html`
${{i18n:'about-abp'}}
`;
// }
Index: js/io-toggle.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/js/io-toggle.js
@@ -0,0 +1,89 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+"use strict";
+
+const IOElement = require("./io-element");
+const {boolean} = IOElement.utils;
+
+class IOToggle extends IOElement
+{
+ // action, checked, and disabled should be reflected down the button
+ static get observedAttributes()
+ {
+ return ["action", "checked", "disabled"];
+ }
+
+ created()
+ {
+ this.addEventListener("click", this);
+ this.render();
+ }
+
+ get checked()
+ {
+ return this.hasAttribute("checked");
+ }
+
+ set checked(value)
+ {
+ boolean.attribute(this, "checked", value);
+ this.render();
+ }
+
+ get disabled()
+ {
+ return this.hasAttribute("disabled");
+ }
+
+ set disabled(value)
+ {
+ boolean.attribute(this, "disabled", value);
+ this.render();
+ }
+
+ onclick(event)
+ {
+ if (!this.disabled)
+ {
+ this.checked = !this.checked;
+ if (this.ownerDocument.activeElement !== this.child)
+ {
+ this.child.focus();
+ }
+ this.dispatchEvent(new CustomEvent("change", {
+ bubbles: true,
+ cancelable: true,
+ detail: this.checked
+ }));
+ }
+ }
+
+ render()
+ {
+ this.html`
+ `;
+ }
+}
+
+IOToggle.define("io-toggle");
Index: package.json
===================================================================
--- a/package.json
+++ b/package.json
@@ -1,29 +1,30 @@
{
"name": "adblockplusui",
"private": true,
"description": "Adblock Plus UI",
"scripts": {
"bash:js": "eslint ./js/**/*.js && bash -c 'echo \"/* eslint-disable */$(browserify --node --no-bundle-external $0)\">$1'",
- "bash:css": "node-sass --output $1 $0",
+ "bash:css": "bash -c 'node-sass ./css/$0.scss ./skin/$0.css'",
"bashForTest:js": "mkdir -p smoke && bash -c 'cp -R ./tests/{locale,background.html,$0.*} ./smoke'",
"bundleTest:js": "bash -c 'echo \"/* eslint-disable */$(browserify --node ./tests/$0.js)\">./smoke/$0.js'",
"lint": "npm run lint:js",
"lint:js": "eslint ./*.js ./lib/*.js ./ext/*.js",
"prepare": "python2 ensure_dependencies.py",
"start": "http-server & npm run watch",
"bundle": "npm run bundle:desktop-options.js && npm run bundle:desktop-options.css",
- "bundle:desktop-options.css": "npm run bash:css ./css/desktop-options.scss ./skin/desktop-options.css",
+ "bundle:desktop-options.css": "npm run bash:css desktop-options",
"bundle:desktop-options.js": "npm run bash:js ./js/desktop-options.js ./desktop-options.js",
"watch": "npm run watch:desktop-options.js & npm run watch:desktop-options.css",
"watch:desktop-options.css": "watch 'npm run bundle:desktop-options.css' ./css",
"watch:desktop-options.js": "watch 'npm run bundle:desktop-options.js' ./js",
- "test": "npm run test:io-element.js && http-server",
- "test:io-element.js": "npm run bashForTest:js io-element && npm run bundleTest:js io-element",
+ "test": "npm run test:io-element && npm run test:io-toggle && http-server",
+ "test:io-element": "npm run bashForTest:js io-element && npm run bundleTest:js io-element",
+ "test:io-toggle": "npm run bashForTest:js io-toggle && npm run bundleTest:js io-toggle && npm run bash:css io-toggle",
"postinstall": "npm run bundle"
},
"dependencies": {
"browserify": "^16.1.0",
"document-register-element": "^1.7.2",
"eslint": "^4.16.0",
"eslint-config-eyeo": "^2.0.0",
"http-server": "^0.11.1",
Index: tests/io-element.js
===================================================================
--- a/tests/io-element.js
+++ b/tests/io-element.js
@@ -1,10 +1,8 @@
-/* globals module, require */
-
"use strict";
const IOElement = require("../js/io-element");
class IOClock extends IOElement
{
connectedCallback()
{
Index: tests/io-toggle.html
===================================================================
new file mode 100644
--- /dev/null
+++ b/tests/io-toggle.html
@@ -0,0 +1,32 @@
+
+
+
+
+ Test io-toggle.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: tests/io-toggle.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/tests/io-toggle.js
@@ -0,0 +1,3 @@
+"use strict";
+
+require("../js/io-toggle");