diff --git a/.eslintrc.js b/.eslintrc.js index f41170f..cd42cbc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,7 +4,6 @@ module.exports = { browser: true, es6: true }, - extends: 'eslint:recommended', globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' @@ -14,11 +13,9 @@ module.exports = { sourceType: 'module' }, rules: { - indent: ['error', 2], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single'], semi: ['error', 'never'], 'comma-dangle': ['error', 'never'], - 'arrow-parens': ['error', 'as-needed'] + quotes: ['error', 'single'], + indent: ['error', 2] } } diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..c50384f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/README.md b/README.md index 0d786fa..74a0210 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,21 @@ Goals: - Event system over state machine ## Install + ```bash npm i -S @wallerbuilt/mantle ``` ```javascript -import { DOM, mount, dispatch } from '@wallerbuilt/mantle' +import { DOM, mount, dispatch } from "@wallerbuilt/mantle"; ``` ## Examples Demo card game - - repo: https://github.com/wallerbuilt/mantle-card-game - - preview: https://mantle-card-game.netlify.app/ + +- repo: https://github.com/wallerbuilt/mantle-card-game +- preview: https://mantle-card-game.netlify.app/ ## Documentation @@ -35,144 +37,160 @@ Mantle comes with three main concepts: - Provides html elements as functions with additional features, ie: `div(), a()` - Each element has two chained functions `on` and `when` - `on` handles native events on the element itself - - returns the event callback - - can target the main element with `on` attached using `this` , ie: - ```jsx - a({ href: "/" }, "I am a link") - .on({ - click(e) { - e.preventDefault() - this.style.color = "tomato" // this refers to the actual anchor element - } - }) - ``` + - returns the event callback + - can target the main element with `on` attached using `this` , ie: + + ```jsx + a({ href: "/" }, "I am a link").on({ + click(e) { + e.preventDefault(); + this.style.color = "tomato"; // this refers to the actual anchor element + }, + }); + ``` - `when` encompasses global event listening functions that are triggered using `dispatch` - - returns `self` and the optional `payload` (`self` referring to the main element `when` is attached to) - ```jsx - /** - * if payload is a person object { name: "Dave" } passed in the dispatch payload - */ - when({ - "person:added": (self, payload) => self.appendChild(li({}, payload.name)) - }) - ``` + - returns `self` and the optional `payload` (`self` referring to the main element `when` is attached to) + + ```jsx + /** + * if payload is a person object { name: "Dave" } passed in the dispatch payload + */ + when({ + "person:added": (self, payload) => self.appendChild(li({}, payload.name)), + }); + ``` ### Dispatch - `dispatch` takes two arguments - - First argument is the event key. This is best used in a format of prefixing the subject and then the action, ie: - ```jsx - dispatch("person:added") // without payload as second argument - ``` + - First argument is the event key. This is best used in a format of prefixing the subject and then the action, ie: + + ```jsx + dispatch("person:added"); // without payload as second argument + ``` - - Second argument is the payload for the event for global event listeners (`when`) to receive, ie: + - Second argument is the payload for the event for global event listeners (`when`) to receive, ie: - ```jsx - dispatch("person:added", { name: "Dave", age: 50, city: "Chicago" }) + ```jsx + dispatch("person:added", { name: "Dave", age: 50, city: "Chicago" }); - // an example of an element receiving person:added - const { ul, li } = DOM; + // an example of an element receiving person:added + const { ul, li } = DOM; - const listItem = person => li({}, person.name) + const listItem = (person) => li({}, person.name); - const PeopleList = ul({}, "") - .when({ - "person:added": (self, person) => self.appendChild(listItem(person)) - }) - ``` + const PeopleList = ul({}, "").when({ + "person:added": (self, person) => self.appendChild(listItem(person)), + }); + ``` ### Mount - Is the main function that attaches the app element or the outermost element (container) containing all elements used in the app - `mount` takes two arguments - - existing dom element to attach to in your html, ie: `document.getElementById("app")` - - your app element created - ```jsx - // ...imports + - existing dom element to attach to in your html, ie: `document.getElementById("app")` + - your app element created - const { div, p } = DOM + ```jsx + // ...imports - const Intro = p({}, - "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum" - ) + const { div, p } = DOM; - const App = children => div({ className: "my-app" }, children) + const Intro = p( + {}, + "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum" + ); - // Apply App to existing element in the DOM on load with it's children - mount(document.getElementById("app"), App([Intro])) - ``` + const App = (children) => div({ className: "my-app" }, children); + + // Apply App to existing element in the DOM on load with it's children + mount(document.getElementById("app"), App([Intro])); + ``` ### Helper methods for easier DOM manipulation + - **append** -> append child element to target element + ```javascript - append(li({}, 'List Item'))(el) + append(li({}, "List Item"))(el); ``` - **clear** -> clear all child elements on target element + ```javascript - clear(el) + clear(el); ``` - **compose** -> curried composition to use with most helper functions + ```javascript // get text of first span element in el - compose(getText, qs('span'))(el) + compose(getText, qs("span"))(el); ``` - **getProp** -> get attribute property from target element + ```javascript - getProp('data-title')(el) + getProp("data-title")(el); ``` - **setProp** -> set attribute property on target element + ```javascript - setProp('disabled', true)(el) + setProp("disabled", true)(el); ``` - **remove** -> remove child element from target element + ```javascript - remove(child)(el) + remove(child)(el); ``` - **setStyle** -> Set an elements style properties + ```javascript setStyle([ - ['background', 'orange'], - ['fontSize', '18px'] - ])(el) + ["background", "orange"], + ["fontSize", "18px"], + ])(el); ``` - **getText** -> get `textContent` or `innerText` of element + ```javascript - getText(el) + getText(el); ``` - **setText** -> set `textContent` or `innerText` of element + ```javascript - setText('hello text')(el) + setText("hello text")(el); ``` - **qs** -> query selector from target element + ```javascript - qs('span')(el) + qs("span")(el); ``` - **qsAll** -> query all selectors from target element ```javascript - qsAll('li')(ul) + qsAll("li")(ul); ``` ## Development Setup + - `nvm install && nvm use` - `npm i` - `npm start` ### e2e tests for examples + - `npm run cypress` (will start cypress) ### Unit tests @@ -186,4 +204,5 @@ To run tests once: `npm run test` ### Build + - `npm run build` diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index aa9b217..0000000 --- a/dist/index.js +++ /dev/null @@ -1,45 +0,0 @@ -parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c=o||0===r)){var f=o-(r=e+r>o?o-e:r);for(n=e;n0;)e.queue.shift()(t);e.scheduled=!1,e.queue.length&&e.schedule()})}},i.prototype.setTimeout=function(e){setTimeout(e,0,{timeRemaining:function(){return 1}})},module.exports=n; -},{"assert":"Yjno"}],"XNjw":[function(require,module,exports) { -var r,e=require("nanoscheduler")(),a=require("assert");n.disabled=!0;try{r=window.performance,n.disabled="true"===window.localStorage.DISABLE_NANOTIMING||!r.mark}catch(i){}function n(u){if(a.equal(typeof u,"string","nanotiming: name should be type string"),n.disabled)return t;var o=(1e4*r.now()).toFixed()%Number.MAX_SAFE_INTEGER,s="start-"+o+"-"+u;function c(a){var n="end-"+o+"-"+u;r.mark(n),e.push(function(){var e=null;try{var t=u+" ["+o+"]";r.measure(t,s,n),r.clearMarks(s),r.clearMarks(n)}catch(i){e=i}a&&a(e,u)})}return r.mark(s),c.uuid=o,c}function t(r){r&&e.push(function(){r(new Error("nanotiming: performance API unavailable"))})}module.exports=n; -},{"nanoscheduler":"WJwS","assert":"Yjno"}],"d8gF":[function(require,module,exports) { -var e=require("remove-array-items"),t=require("nanotiming"),s=require("assert");function n(e){if(!(this instanceof n))return new n(e);this._name=e||"nanobus",this._starListeners=[],this._listeners={}}module.exports=n,n.prototype.emit=function(e){s.ok("string"==typeof e||"symbol"==typeof e,"nanobus.emit: eventName should be type string or symbol");for(var n=[],i=1,r=arguments.length;i0&&this._emit(this._listeners[e],n),this._starListeners.length>0&&this._emit(this._starListeners,e,n,o.uuid),o(),this},n.prototype.on=n.prototype.addListener=function(e,t){return s.ok("string"==typeof e||"symbol"==typeof e,"nanobus.on: eventName should be type string or symbol"),s.equal(typeof t,"function","nanobus.on: listener should be type function"),"*"===e?this._starListeners.push(t):(this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t)),this},n.prototype.prependListener=function(e,t){return s.ok("string"==typeof e||"symbol"==typeof e,"nanobus.prependListener: eventName should be type string or symbol"),s.equal(typeof t,"function","nanobus.prependListener: listener should be type function"),"*"===e?this._starListeners.unshift(t):(this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].unshift(t)),this},n.prototype.once=function(e,t){s.ok("string"==typeof e||"symbol"==typeof e,"nanobus.once: eventName should be type string or symbol"),s.equal(typeof t,"function","nanobus.once: listener should be type function");var n=this;return this.on(e,function s(){t.apply(n,arguments);n.removeListener(e,s)}),this},n.prototype.prependOnceListener=function(e,t){s.ok("string"==typeof e||"symbol"==typeof e,"nanobus.prependOnceListener: eventName should be type string or symbol"),s.equal(typeof t,"function","nanobus.prependOnceListener: listener should be type function");var n=this;return this.prependListener(e,function s(){t.apply(n,arguments);n.removeListener(e,s)}),this},n.prototype.removeListener=function(t,n){return s.ok("string"==typeof t||"symbol"==typeof t,"nanobus.removeListener: eventName should be type string or symbol"),s.equal(typeof n,"function","nanobus.removeListener: listener should be type function"),"*"===t?(this._starListeners=this._starListeners.slice(),i(this._starListeners,n)):(void 0!==this._listeners[t]&&(this._listeners[t]=this._listeners[t].slice()),i(this._listeners[t],n));function i(t,s){if(t){var n=t.indexOf(s);return-1!==n?(e(t,n,1),!0):void 0}}},n.prototype.removeAllListeners=function(e){return e?"*"===e?this._starListeners=[]:this._listeners[e]=[]:(this._starListeners=[],this._listeners={}),this},n.prototype.listeners=function(e){var t="*"!==e?this._listeners[e]:this._starListeners,s=[];if(t)for(var n=t.length,i=0;i0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1?arguments[1]:void 0,i=Object.assign(document.createElement(t),u({},r)),a=document.createDocumentFragment();if(Array.isArray(o))o.flatMap(function(e){return a.appendChild(e)});else if("string"==typeof o){var c=document.createTextNode(o);a.appendChild(c)}else"object"===(0,e.default)(o)&&a.appendChild(o);return i.appendChild(a),i.on=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};for(var t in e)t in e&&i.addEventListener(t,e[t]);return i},i.when=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=function(t){t in e&&(0,n.on)(t,function(r){return e[t](i,r)})};for(var r in e)t(r);return i},i}});var c=a;exports.default=c; -},{"@babel/runtime/helpers/typeof":"b9XL","@babel/runtime/helpers/defineProperty":"IxO8","./elements.json":"FAj7","./emitter":"H0u9"}],"NVR6":[function(require,module,exports) { -function n(n,r){(null==r||r>n.length)&&(r=n.length);for(var e=0,l=new Array(r);e0;)e.firstElementChild.remove();return e};exports.clear=c;var x=function(e){return function(t){return"textContent"in t?t.textContent=e:t.innerText=e,t}};exports.setText=x;var l=function(e){return"textContent"in e?e.textContent:e.innerText};exports.getText=l;var a=function(e){return function(t){return t.querySelector(e)}};exports.qs=a;var f=function(t){return function(r){return(0,e.default)(r.querySelectorAll(t))}};exports.qsAll=f; -},{"@babel/runtime/helpers/toConsumableArray":"Fhqp","@babel/runtime/helpers/slicedToArray":"HETk"}],"Focm":[function(require,module,exports) { -"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"DOM",{enumerable:!0,get:function(){return e.default}}),Object.defineProperty(exports,"dispatch",{enumerable:!0,get:function(){return t.dispatch}}),Object.defineProperty(exports,"setProp",{enumerable:!0,get:function(){return r.setProp}}),Object.defineProperty(exports,"setStyle",{enumerable:!0,get:function(){return r.setStyle}}),Object.defineProperty(exports,"getProp",{enumerable:!0,get:function(){return r.getProp}}),Object.defineProperty(exports,"compose",{enumerable:!0,get:function(){return r.compose}}),Object.defineProperty(exports,"append",{enumerable:!0,get:function(){return r.append}}),Object.defineProperty(exports,"remove",{enumerable:!0,get:function(){return r.remove}}),Object.defineProperty(exports,"clear",{enumerable:!0,get:function(){return r.clear}}),Object.defineProperty(exports,"setText",{enumerable:!0,get:function(){return r.setText}}),Object.defineProperty(exports,"getText",{enumerable:!0,get:function(){return r.getText}}),Object.defineProperty(exports,"qs",{enumerable:!0,get:function(){return r.qs}}),Object.defineProperty(exports,"qsAll",{enumerable:!0,get:function(){return r.qsAll}}),exports.mount=void 0;var e=n(require("./dom")),t=require("./emitter"),r=require("./helpers");function n(e){return e&&e.__esModule?e:{default:e}}var o=function(e,t){e.appendChild(t)};exports.mount=o; -},{"./dom":"fRxd","./emitter":"H0u9","./helpers":"lTk1"}]},{},["Focm"], "@wallerbuilt/mantle") \ No newline at end of file diff --git a/examples/people/NewPersonForm.js b/examples/people/NewPersonForm.js index 13ffc24..f7d796b 100644 --- a/examples/people/NewPersonForm.js +++ b/examples/people/NewPersonForm.js @@ -1,4 +1,4 @@ -import { DOM, dispatch } from '../../src' +import { DOM, dispatch, debug } from '../../src' const { input, div, button } = DOM @@ -25,6 +25,8 @@ const AddPersonButton = button( click: () => dispatch('person:added', PersonInput.value) }) +debug('AddPersonButton', AddPersonButton) + const NewPersonForm = div({ className: 'new-person-form' }, [ PersonInput, AddPersonButton diff --git a/examples/people/index.js b/examples/people/index.js index 7223a40..dc93015 100644 --- a/examples/people/index.js +++ b/examples/people/index.js @@ -1,13 +1,13 @@ -import { DOM, mount, dispatch, append, remove } from '../../src' +import { DOM, mount, dispatch, append, remove, debug } from '../../src' import { notify } from './notifications' import NewPersonForm from './NewPersonForm' // Pull the needed DOM elements const { main, div, button, ul, li, span } = DOM -const PersonListItem = val => +const PersonListItem = (val) => li({}, [ - span({ className: 'mr2 f3'}, val), + span({ className: 'mr2 f3' }, val), button({ className: 'pa1 bg-black white bn' }, 'Delete?').on({ click() { dispatch('person:removed', this.parentNode) @@ -19,14 +19,11 @@ const PersonList = ul({ className: 'list pl0 mt4' }, []).when({ ['person:added']: (self, val) => append(PersonListItem(val))(self), ['person:removed']: (self, child) => remove(child)(self) }) +debug('PersonList', PersonList) const App = main( { id: 'app-root' }, - div({ className: 'container tc' }, [ - notify, - NewPersonForm, - PersonList - ]) + div({ className: 'container tc' }, [notify, NewPersonForm, PersonList]) ) mount(document.getElementById('app'), App) diff --git a/package-lock.json b/package-lock.json index 1d3dd99..23a2abf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4771,6 +4771,24 @@ } } }, + "eslint-config-prettier": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", @@ -5432,6 +5450,12 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -9310,6 +9334,21 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", diff --git a/package.json b/package.json index 498a766..ab7e7bf 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,13 @@ "browser-env": "^3.3.0", "cypress": "^4.5.0", "eslint": "^7.0.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-prettier": "^3.1.4", "esm": "^3.2.25", "husky": "^4.2.5", "nyc": "^15.1.0", - "parcel-bundler": "^1.12.4" + "parcel-bundler": "^1.12.4", + "prettier": "^2.0.5" }, "dependencies": { "@babel/runtime": "^7.10.3", diff --git a/src/debug/NewEntry/index.js b/src/debug/NewEntry/index.js new file mode 100644 index 0000000..540a715 --- /dev/null +++ b/src/debug/NewEntry/index.js @@ -0,0 +1,40 @@ +import DOM from '../../dom' +import { append, setStyle, qs, getProp } from '../../helpers' +import { withFlashStyle } from '../styles' +import newEntryStyles from './styles' + +const { ul, li } = DOM + +const withLiStyle = setStyle([['padding', '5px 8px']]) +const withUlStyle = setStyle(newEntryStyles) + +const handleClickEl = (mid) => { + const activeDebugEl = qs(`[data-mid="${mid}"]`)(document) + withFlashStyle(activeDebugEl) + + setTimeout(() => { + setStyle([['box-shadow', 'none']])(activeDebugEl) + }, 1000) +} + +const NewEntryValue = (key, val) => withLiStyle(li({}, `${key}: ${val}`)) + +const NewEntry = (type, evtStr, el) => + withUlStyle( + ul({ onclick: () => handleClickEl(getProp('data-mid')(el)) }, [ + NewEntryValue('On', type), + NewEntryValue('Event', evtStr), + NewEntryValue('Element Changed', el.nodeName.toLowerCase()), + NewEntryValue('ID', getProp('data-mid')(el)), + NewEntryValue('Timestamp', new Date().toLocaleTimeString()) + ]).on({ + mouseover() { + setStyle([['background', '#ddd']])(this) + }, + mouseleave() { + setStyle(newEntryStyles)(this) + } + }) + ) + +export default NewEntry diff --git a/src/debug/NewEntry/styles.js b/src/debug/NewEntry/styles.js new file mode 100644 index 0000000..5514347 --- /dev/null +++ b/src/debug/NewEntry/styles.js @@ -0,0 +1,16 @@ +const newEntryStyles = [ + ['background', '#eee'], + ['border-radius', '0'], + ['color', '#222'], + ['cursor', 'pointer'], + ['display', 'flex'], + ['flex-direction', 'column'], + ['font-size', '14px'], + ['padding', '5px'], + ['list-style', 'none'], + ['margin', '0 10px'], + ['transition', 'all 200ms ease-in'], + ['min-width', '200px'] +] + +export default newEntryStyles diff --git a/src/debug/index.js b/src/debug/index.js new file mode 100644 index 0000000..3321630 --- /dev/null +++ b/src/debug/index.js @@ -0,0 +1,64 @@ +import { on } from '../emitter' +import DOM from '../dom' +import { prepend, setStyle, clear } from '../helpers' +import NewEntry from './NewEntry' +import { + withBtnStyle, + withFlashStyle, + withWidgetStyle, + withListStyle, + onClearBtnEnter, + onClearBtnLeave +} from './styles' + +const { div, button } = DOM + +const DebugList = withListStyle(div({ id: 'debug-list' }, [])) + +const ClearBtn = withBtnStyle( + button({}, 'clear').on({ + click() { + clear(DebugList) + hideClearBtn() + }, + mouseenter() { + onClearBtnEnter(this) + }, + mouseleave() { + onClearBtnLeave(this) + } + }) +) + +const hideClearBtn = () => setStyle([['display', 'none']])(ClearBtn) + +const showClearBtn = () => setStyle([['display', 'block']])(ClearBtn) + +const Widget = () => withWidgetStyle(div({}, [ClearBtn, DebugList])) + +const flashElement = (element) => { + withFlashStyle(element) + + setTimeout(() => { + setStyle([['box-shadow', 'none']])(element) + }, 1000) +} + +export const debug = (name = '', element) => { + element.when({ + ['*']: (el, evtName) => { + flashElement(el) + prepend(NewEntry(event.type, evtName, el))(DebugList) + showClearBtn() + } + }) +} + +const globalDebug = () => + on('*', (evtName) => + prepend(NewEntry('global', evtName, event.target))(Widget()) + ) + +document.body.appendChild(Widget()) + +export default globalDebug diff --git a/src/debug/styles.js b/src/debug/styles.js new file mode 100644 index 0000000..e71ad7a --- /dev/null +++ b/src/debug/styles.js @@ -0,0 +1,55 @@ +import { setStyle } from '../' + +export const withWidgetStyle = setStyle([ + ['align-items', 'flex-start'], + ['background', '#f7f7f7'], + ['bottom', 0], + ['box-shadow', '0 1px 4px #444'], + ['color', '#222'], + ['display', 'flex'], + ['font-size', '20px'], + ['left', 0], + ['overflow-x', 'scroll'], + ['padding', '10px'], + ['position', 'absolute'], + ['width', '100vw'] +]) + +export const withListStyle = setStyle([ + ['align-items', 'flex-start'], + ['display', 'flex'], + ['flex-direction', 'row'], + ['flex-wrap', 'no-wrap'] +]) + +export const onClearBtnEnter = setStyle([ + ['background', '#f45'], + ['color', '#f7f7f7'], + ['box-shadow', '0 0 4px #666'] +]) + +export const onClearBtnLeave = setStyle([ + ['background', '#f78'], + ['color', '#fff'] +]) + +export const withBtnStyle = setStyle([ + ['align-self', 'flex-start'], + ['background', '#f78'], + ['border', 'none'], + ['box-shadow', '0 0 2px #666'], + ['color', '#fff'], + ['cursor', 'pointer'], + ['display', 'none'], + ['font-size', '14px'], + ['margin', '0 0 0 10px'], + ['padding', '2px 5px 5px'], + ['transition', 'all 200ms ease-in-out'] +]) + +export const withH4Style = setStyle([['margin', '0 10px 0 0']]) + +export const withFlashStyle = setStyle([ + ['box-shadow', '0 0 5px tomato'], + ['transition', 'box-shadow 200ms ease'] +]) diff --git a/src/dom.js b/src/dom.js index 7e27b62..e322d41 100644 --- a/src/dom.js +++ b/src/dom.js @@ -3,13 +3,21 @@ import { on } from './emitter' let DOM = {} -elementTypes.map(element => { +const uuid = () => + ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + ( + c ^ + (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) + ).toString(16) + ) + +elementTypes.map((element) => { DOM[element] = (props = {}, children) => { let el = Object.assign(document.createElement(element), { ...props }) let fragment = document.createDocumentFragment() if (Array.isArray(children)) { - children.flatMap(c => fragment.appendChild(c)) + children.flatMap((c) => fragment.appendChild(c)) } else if (typeof children === 'string') { let textNode = document.createTextNode(children) fragment.appendChild(textNode) @@ -17,6 +25,8 @@ elementTypes.map(element => { fragment.appendChild(children) } + el.setAttribute('data-mid', uuid()) + el.appendChild(fragment) // apply native listeners @@ -35,7 +45,7 @@ elementTypes.map(element => { el.when = (events = {}) => { for (const key in events) { if (key in events) { - on(key, data => events[key](el, data)) + on(key, (data) => events[key](el, data)) } } return el diff --git a/src/helpers.js b/src/helpers.js index 51a6bf9..7d52b7e 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -71,6 +71,20 @@ export const append = element => target => { return target } +/** + * @name prepend + * @param {HTMLElement} element + * @param {HTMLElement} target + * @description Appends node to parent target + * @example + * append(li({}, 'child'))(ul({}, [])) + * @return {HTMLElement} target + */ +export const prepend = element => target => { + target.prepend(element) + return target +} + /** * @name remove * @param {HTMLElement} element diff --git a/src/index.js b/src/index.js index 34f4504..67e6b17 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import { getProp, compose, append, + prepend, remove, clear, setText, @@ -13,6 +14,7 @@ import { qs, qsAll } from './helpers' +import globalDebug, { debug } from './debug/index' const mount = (el, container) => { el.appendChild(container) @@ -27,10 +29,13 @@ export { getProp, compose, append, + prepend, remove, clear, setText, getText, qs, - qsAll + qsAll, + globalDebug, + debug } diff --git a/tests/debug.spec.js b/tests/debug.spec.js new file mode 100644 index 0000000..48eb12a --- /dev/null +++ b/tests/debug.spec.js @@ -0,0 +1,4 @@ + +/* eslint-disable no-undef */ +import test from 'ava' +import { debug } from '../src'