From faf3409c7168b3f95db10dabff26d5ca1b8a0fa2 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 10:53:38 +0100 Subject: [PATCH 01/84] starting with-react func --- src/demos/demo.react.jsx | 11 ++++++++--- src/js/with-react.jsx | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/js/with-react.jsx diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index 42e43efd95..7ffa973dbd 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -1,5 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import withReact from '../js/with-react.jsx'; +import AXAButton from '../components/m-button'; + +const AXAButtonReact = withReact(React)(AXAButton); // components are loaded already in the body cause this demo is a the end of the body @@ -19,11 +23,12 @@ class MyEventDemoReact extends React.Component { } render() { - return ( + return ([ {this.state.isToggleOn ? 'ON' : 'OFF'} - - ); + , + {this.state.isToggleOn ? 'ON' : 'OFF'}, + ]); } } diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx new file mode 100644 index 0000000000..eb3aa5f7f6 --- /dev/null +++ b/src/js/with-react.jsx @@ -0,0 +1,18 @@ +const withReact = React => (WebComponent) => { + console.log(WebComponent); + + const WCTagName = ''; + + return class WebComponentWrapper extends React.PureComponent { + render() { + const { props } = this; + const { children } = props; + + return ( + { this.wcNode = wcNode; }}>{children} + ); + } + }; +}; + +export default withReact; From 616a057a6260bf3ee95e6ee997d6590349aea9cd Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:06:49 +0100 Subject: [PATCH 02/84] added sass to demo build --- stack/tasks/bundle-demos.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stack/tasks/bundle-demos.js b/stack/tasks/bundle-demos.js index c1b8e9df87..22051fef2a 100644 --- a/stack/tasks/bundle-demos.js +++ b/stack/tasks/bundle-demos.js @@ -5,6 +5,9 @@ const commonjs = require('rollup-plugin-commonjs'); const babel = require('rollup-plugin-babel'); const uglify = require('rollup-plugin-uglify'); const replace = require('rollup-plugin-replace'); +const sass = require('rollup-plugin-sass'); +const autoprefixer = require('autoprefixer'); +const postcss = require('postcss'); const constants = require('../constants'); @@ -30,6 +33,15 @@ async function buildComponents() { include: 'node_modules/**', exclude: ['node_modules/@webcomponents/webcomponentsjs/**'], }), + sass({ + insert: false, + options: { + outputStyle: ENV === constants.ENV.PROD ? undefined : 'expanded', + }, + processor: css => postcss([autoprefixer]) + .process(css) + .then(result => result.css), + }), babel({ runtimeHelpers: true, presets: ['react'], From 2531dc19d998cb96629cc35d429dc5d56bab3cdc Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:09:00 +0100 Subject: [PATCH 03/84] watch jsx to --- stack/tasks/watch-js.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack/tasks/watch-js.js b/stack/tasks/watch-js.js index f5b9889e3c..4c678092e5 100644 --- a/stack/tasks/watch-js.js +++ b/stack/tasks/watch-js.js @@ -9,7 +9,7 @@ const SRC_COMPONENTS = 'src/components/'; let started = false; nodemon({ - ext: 'js,scss', + ext: 'js,jsx,scss', watch: './src', script: './stack/server', args: ['DEV'], From ec220c6a1b14451d783db6936bcf55e78cf77a51 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:28:27 +0100 Subject: [PATCH 04/84] added dasherize function --- src/js/dasherize.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/js/dasherize.js diff --git a/src/js/dasherize.js b/src/js/dasherize.js new file mode 100644 index 0000000000..fa4b3a37ae --- /dev/null +++ b/src/js/dasherize.js @@ -0,0 +1,24 @@ +const regexUpperCaseLetter = /[A-Z]/g; + +/** + * Dasherize any given string. + * + * @param {String} string - The string to be dash-cased. + * @returns {String} - Returns dash-cased string. + */ +function dasherize(string) { + return string.replace(regexUpperCaseLetter, replace); +} + +function replace(match, offset, string) { + const lower = match.toLowerCase(); + const next = string.charAt(offset + 1); + + if (offset === 0 || next.toUpperCase() === next) { + return lower; + } + + return `-${lower}`; +} + +export default dasherize; From ead4cfa7736274a90e3f2d538939830d0573965d Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:35:38 +0100 Subject: [PATCH 05/84] also resolve jsx --- stack/tasks/bundle-demos.js | 1 + 1 file changed, 1 insertion(+) diff --git a/stack/tasks/bundle-demos.js b/stack/tasks/bundle-demos.js index 22051fef2a..cab4643304 100644 --- a/stack/tasks/bundle-demos.js +++ b/stack/tasks/bundle-demos.js @@ -27,6 +27,7 @@ async function buildComponents() { main: true, browser: true, preferBuiltins: false, + extensions: ['.js', '.jsx'], }), ENV === constants.ENV.PROD ? uglify() : () => {}, commonjs({ From 7236b1e44f9b106f75bd41a3ba9b9d7f0c51615b Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:38:36 +0100 Subject: [PATCH 06/84] added replace for ENV --- stack/tasks/bundle-demos.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stack/tasks/bundle-demos.js b/stack/tasks/bundle-demos.js index cab4643304..0847213851 100644 --- a/stack/tasks/bundle-demos.js +++ b/stack/tasks/bundle-demos.js @@ -34,6 +34,10 @@ async function buildComponents() { include: 'node_modules/**', exclude: ['node_modules/@webcomponents/webcomponentsjs/**'], }), + replace({ + exclude: 'node_modules/**', + ENV: JSON.stringify(process.env.NODE_ENV || 'development'), + }), sass({ insert: false, options: { From a11caced84c173340d047ebc033c7f8e4488b70c Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:40:24 +0100 Subject: [PATCH 07/84] imported dasherize --- src/js/with-react.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index eb3aa5f7f6..886b49bfa1 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -1,7 +1,11 @@ +import dasherize from './dasherize'; + const withReact = React => (WebComponent) => { console.log(WebComponent); + console.log(WebComponent.name); + console.log(dasherize(WebComponent.name)); - const WCTagName = ''; + const WCTagName = dasherize(WebComponent.name); return class WebComponentWrapper extends React.PureComponent { render() { From 27bed766294a36e0d97c328f7b6fff9e3f2dfd73 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 11:40:42 +0100 Subject: [PATCH 08/84] fixed with react import --- src/demos/demo.react.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index 7ffa973dbd..133b8a353f 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import withReact from '../js/with-react.jsx'; +import withReact from '../js/with-react'; import AXAButton from '../components/m-button'; const AXAButtonReact = withReact(React)(AXAButton); From a8209cc221794af5b6c9980bc8fadb858fa9ba5a Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 14:12:29 +0100 Subject: [PATCH 09/84] added displayName --- src/js/with-react.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 886b49bfa1..1ed145b61d 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -5,9 +5,15 @@ const withReact = React => (WebComponent) => { console.log(WebComponent.name); console.log(dasherize(WebComponent.name)); + const { name } = WebComponent; + const displayName = `${name}React`; const WCTagName = dasherize(WebComponent.name); return class WebComponentWrapper extends React.PureComponent { + static get displayName() { + return displayName; + } + render() { const { props } = this; const { children } = props; From 7e08ca044dbfac8f8448f9f762460c5cc6d6a9a2 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 14:36:09 +0100 Subject: [PATCH 10/84] mostly finalized this func --- src/js/with-react.jsx | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 1ed145b61d..00dca83b1b 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -1,25 +1,70 @@ import dasherize from './dasherize'; +import on from './on'; const withReact = React => (WebComponent) => { - console.log(WebComponent); - console.log(WebComponent.name); - console.log(dasherize(WebComponent.name)); - const { name } = WebComponent; const displayName = `${name}React`; const WCTagName = dasherize(WebComponent.name); + const eventCache = {}; return class WebComponentWrapper extends React.PureComponent { static get displayName() { return displayName; } + constructor(props) { + super(props); + + this.handleRef = this.handleRef.bind(this); + } + + componentDidMount() { + this.componentWillReceiveProps(this.props); + } + + componentWillReceiveProps(props) { + const wcNode = this; + + Object.keys(props).forEach((key) => { + if (key === 'children' || key === 'style') { + return; + } + + const keyFrom2 = key.charAt(2); + + // bind event handlers + if (key.indexOf('on') === 0 && keyFrom2 === keyFrom2.toUpperCase()) { + if (eventCache[key]) { + eventCache[key](); + } + + eventCache[key] = on(wcNode, key.substring(2).toLowerCase(), props[key]); + } else { + // set properties by DOM property API's - not HTML setAttribute -> first class props + wcNode[key] = props[key]; + } + }); + } + + componentWillUnmount() { + // clean up bound custom events + Object.keys(eventCache).forEach((key) => { + if (eventCache[key]) { + eventCache[key](); + } + }); + } + + handleRef(wcNode) { + this.wcNode = wcNode; + } + render() { const { props } = this; const { children } = props; return ( - { this.wcNode = wcNode; }}>{children} + {children} ); } }; From 43ae22ae45210048a165eb764baba982bfe2fbc2 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 14:44:59 +0100 Subject: [PATCH 11/84] added doclet --- src/js/with-react.jsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 00dca83b1b..541ef47282 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -1,6 +1,26 @@ import dasherize from './dasherize'; import on from './on'; +/** + * Provides a partially applied function which let's you wrap any WebComponent with react. + * - it supports first-class props for web components + * - it handles custom events + * + * @link https://github.com/webcomponents/react-integration - inspired by react-integration + * @param React + * @returns {function(*)} + * + * @example How to use + * import React from 'react'; + * import withReact from '@axa-ch/patterns-library/src/js/with-react'; + * import AXAButton from '@axa-ch/patterns-library/dist/components/m-button' + * + * const AXAButtonReact = withReact(React)(AXAButton); + * + * const MyApp = ({ color, onClick }) => ( + * Hello World + * ); + */ const withReact = React => (WebComponent) => { const { name } = WebComponent; const displayName = `${name}React`; From 7eca5b0b06242a4a13505253502b20193bea7441 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 15:02:49 +0100 Subject: [PATCH 12/84] fixed missing braces --- src/js/with-react.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 541ef47282..9252f96b76 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -43,7 +43,7 @@ const withReact = React => (WebComponent) => { } componentWillReceiveProps(props) { - const wcNode = this; + const { wcNode } = this; Object.keys(props).forEach((key) => { if (key === 'children' || key === 'style') { From 84ffbd64a073cbbb38948d179ece073489d4f205 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 21 Mar 2018 15:04:32 +0100 Subject: [PATCH 13/84] added click event --- src/demos/demo.react.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index 133b8a353f..06f6e5de72 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -24,10 +24,10 @@ class MyEventDemoReact extends React.Component { render() { return ([ - + {this.state.isToggleOn ? 'ON' : 'OFF'} , - {this.state.isToggleOn ? 'ON' : 'OFF'}, + {this.state.isToggleOn ? 'ON' : 'OFF'}, ]); } } From fed07489bece8c923f6029e90a5c57db78062fe9 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Mon, 26 Mar 2018 10:34:55 +0200 Subject: [PATCH 14/84] added pure option --- src/js/with-react.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 9252f96b76..49a8a6da00 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -8,6 +8,7 @@ import on from './on'; * * @link https://github.com/webcomponents/react-integration - inspired by react-integration * @param React + * @param {Boolean} [options.pure=true] - Is this a pure component? * @returns {function(*)} * * @example How to use @@ -21,13 +22,14 @@ import on from './on'; * Hello World * ); */ -const withReact = React => (WebComponent) => { +const withReact = (React, { pure = true } = {}) => (WebComponent) => { const { name } = WebComponent; const displayName = `${name}React`; const WCTagName = dasherize(WebComponent.name); const eventCache = {}; + const Component = pure ? React.PureComponent : React.Component; - return class WebComponentWrapper extends React.PureComponent { + return class WebComponentWrapper extends Component { static get displayName() { return displayName; } From a6ac0bec076afb4018612c0f749c72d22382ecaf Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Mon, 26 Mar 2018 11:23:53 +0200 Subject: [PATCH 15/84] fixed typo --- src/js/with-react.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 49a8a6da00..ddb7c3a182 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -2,7 +2,7 @@ import dasherize from './dasherize'; import on from './on'; /** - * Provides a partially applied function which let's you wrap any WebComponent with react. + * Provides a partially applied function which let's you wrap any WebComponent with React. * - it supports first-class props for web components * - it handles custom events * From ae98adc87c433510a2d4ee150bc2e6101088a914 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 28 Mar 2018 16:33:35 +0200 Subject: [PATCH 16/84] start adding prop blacklist --- src/js/with-react.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index ddb7c3a182..1c286a334b 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -1,6 +1,11 @@ import dasherize from './dasherize'; import on from './on'; +const PROP_BLACKLIST = [ + 'children', // children are never passed as props, instead as real DOM children + 'style', // @todo: discuss if we need style, cause we normally use BEM +]; + /** * Provides a partially applied function which let's you wrap any WebComponent with React. * - it supports first-class props for web components @@ -48,7 +53,7 @@ const withReact = (React, { pure = true } = {}) => (WebComponent) => { const { wcNode } = this; Object.keys(props).forEach((key) => { - if (key === 'children' || key === 'style') { + if (PROP_BLACKLIST.indexOf(key) !== -1) { return; } From 11c3318f6d6ed257b5ff0b0db8406ace6fbbe318 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Wed, 28 Mar 2018 16:46:21 +0200 Subject: [PATCH 17/84] fixed env replacement broken for demos --- stack/tasks/bundle-demos.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stack/tasks/bundle-demos.js b/stack/tasks/bundle-demos.js index 0847213851..279cdd585a 100644 --- a/stack/tasks/bundle-demos.js +++ b/stack/tasks/bundle-demos.js @@ -36,7 +36,9 @@ async function buildComponents() { }), replace({ exclude: 'node_modules/**', - ENV: JSON.stringify(process.env.NODE_ENV || 'development'), + ENV: JSON.stringify(ENV), + DEV: JSON.stringify(constants.ENV.DEV), + PROD: JSON.stringify(constants.ENV.PROD), }), sass({ insert: false, From d95a71ef2446dc83b859b2a3485a9f918e58a37a Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 08:43:16 +0200 Subject: [PATCH 18/84] fixed rollup can't build react exports --- stack/tasks/bundle-demos.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stack/tasks/bundle-demos.js b/stack/tasks/bundle-demos.js index 279cdd585a..b1f3380ce7 100644 --- a/stack/tasks/bundle-demos.js +++ b/stack/tasks/bundle-demos.js @@ -33,6 +33,10 @@ async function buildComponents() { commonjs({ include: 'node_modules/**', exclude: ['node_modules/@webcomponents/webcomponentsjs/**'], + namedExports: { + 'node_modules/react/index.js': ['Children', 'Component', 'PureComponent', 'createElement'], + 'node_modules/react-dom/index.js': ['render'], + }, }), replace({ exclude: 'node_modules/**', From 9ca4d1abe4e31acf5ef3d63a619818e2c4a11bcb Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 08:49:01 +0200 Subject: [PATCH 19/84] start adding todo sample app --- src/demos/demo.react.html | 3 + src/demos/demo.react.jsx | 2 + src/demos/todomvc/app.jsx | 29 +++++++ src/demos/todomvc/todo-item.jsx | 142 +++++++++++++++++++++++++++++++ src/demos/todomvc/todo-model.js | 79 +++++++++++++++++ src/demos/todomvc/todos-list.jsx | 16 ++++ src/demos/todomvc/todos.jsx | 106 +++++++++++++++++++++++ src/demos/todomvc/utils.js | 35 ++++++++ 8 files changed, 412 insertions(+) create mode 100644 src/demos/todomvc/app.jsx create mode 100644 src/demos/todomvc/todo-item.jsx create mode 100644 src/demos/todomvc/todo-model.js create mode 100644 src/demos/todomvc/todos-list.jsx create mode 100644 src/demos/todomvc/todos.jsx create mode 100644 src/demos/todomvc/utils.js diff --git a/src/demos/demo.react.html b/src/demos/demo.react.html index bb1f9917e6..99cbee13bc 100644 --- a/src/demos/demo.react.html +++ b/src/demos/demo.react.html @@ -1,4 +1,7 @@

Demo on how to pass a callback function:

+ +

Demo TodoMVC:

+
diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index 06f6e5de72..f51ecbf4a0 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -3,6 +3,8 @@ import ReactDOM from 'react-dom'; import withReact from '../js/with-react'; import AXAButton from '../components/m-button'; +import './todomvc/app'; + const AXAButtonReact = withReact(React)(AXAButton); // components are loaded already in the body cause this demo is a the end of the body diff --git a/src/demos/todomvc/app.jsx b/src/demos/todomvc/app.jsx new file mode 100644 index 0000000000..ce8f48fbde --- /dev/null +++ b/src/demos/todomvc/app.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import Todos from './todos'; +import TodoModel from './todo-model'; + +const todoModel = new TodoModel('react-todos'); + +const TodoApp = ({ + model, +}) => ( +
+ +
+); + +function render() { + ReactDOM.render( + , + document.getElementsByClassName('my-todo-demo-react')[0], + ); +} + +todoModel.subscribe(render); + +document.addEventListener('DOMContentLoaded', () => { + render(); +}); + diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx new file mode 100644 index 0000000000..15cd9080bd --- /dev/null +++ b/src/demos/todomvc/todo-item.jsx @@ -0,0 +1,142 @@ +import React, { Component } from 'react'; +import withReact from '../../js/with-react'; +import AXAButton from '../../components/m-button'; +import AXAIcon from '../../components/a-icon'; + +const withReactBound = withReact(React); +const AXAButtonReact = withReactBound(AXAButton); +const AXAIconReact = withReactBound(AXAIcon); + +const ESCAPE_KEY = 27; +const ENTER_KEY = 13; + +class TodoItem extends Component { + constructor(props, context) { + super(props, context); + + this.onClick = this.onClick.bind(this); + this.handleRef = this.handleRef.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleEdit = this.handleEdit.bind(this); + this.handleChange = this.handleChange.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + + this.state = { + editText: props.todo.title, + }; + } + + /** + * This is a completely optional performance enhancement that you can + * implement on any React component. If you were to delete this method + * the app would still work correctly (and still be very performant!), we + * just use it as an example of how little code it takes to get an order + * of magnitude performance improvement. + */ + shouldComponentUpdate(nextProps, nextState) { + const { props } = this; + + return ( + nextProps.todo !== props.todo || + nextProps.editing !== props.editing || + nextState.editText !== this.state.editText + ); + } + + /** + * Safely manipulate the DOM after updating the state when invoking + * `this.props.onEdit()` in the `handleEdit` method above. + * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate + * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate + */ + componentDidUpdate(prevProps) { + if (!prevProps.editing && this.props.editing) { + const { editField } = this; + + editField.focus(); + editField.setSelectionRange(editField.value.length, editField.value.length); + } + } + + handleRef(ref) { + this.editField = ref; + } + + handleSubmit() { + const val = this.state.editText.trim(); + const { props } = this; + const { todo } = props; + + if (val) { + props.onSave(todo, val); + this.setState({ editText: val }); + } else { + props.onDestroy(todo); + } + } + + handleEdit() { + const { props } = this; + + props.onEdit(); + this.setState({ editText: props.todo.title }); + } + + handleChange(event) { + if (this.props.editing) { + this.setState({ editText: event.target.value }); + } + } + + handleKeyDown(event) { + const { which } = event; + + if (which === ESCAPE_KEY) { + const { props } = this; + + this.setState({ editText: props.todo.title }); + props.onCancel(event); + } else if (which === ENTER_KEY) { + this.handleSubmit(event); + } + } + + render() { + const { props, state } = this; + const { todo, onToggle, onDestroy, id } = props; + const htmlFor = `m-todo-${id}`; + + return ( +
  • +
    + onToggle(todo)} + /> + + + + onDestroy(todo)}> + + +
    + + +
  • + ); + } +} + +export default TodoItem; diff --git a/src/demos/todomvc/todo-model.js b/src/demos/todomvc/todo-model.js new file mode 100644 index 0000000000..6aec4a492f --- /dev/null +++ b/src/demos/todomvc/todo-model.js @@ -0,0 +1,79 @@ +import { store, uuid } from './utils'; + +class TodoModel { + constructor(key) { + this.key = key; + this.todos = store(key); + this.onChanges = []; + } + + subscribe(onChange) { + this.onChanges.push(onChange); + } + + inform() { + store(this.key, this.todos); + this.onChanges.forEach((cb) => { + cb(); + }); + } + + addTodo(title) { + this.todos = this.todos.concat({ + id: uuid(), + title, + completed: false, + }); + + this.inform(); + } + + toggleAll(checked) { + // Note: it's usually better to use immutable data structures since they're + // easier to reason about and React works very well with them. That's why + // we use map() and filter() everywhere instead of mutating the array or + // todo items themselves. + this.todos = this.todos.map(todo => ({ + ...todo, + completed: checked, + })); + + this.inform(); + } + + toggle(todoToToggle) { + this.todos = this.todos.map(todo => todo !== todoToToggle ? + todo : + { + ...todo, + completed: !todo.completed, + }); + + this.inform(); + } + + destroy(todo) { + this.todos = this.todos.filter(candidate => candidate !== todo); + + this.inform(); + } + + save(todoToSave, text) { + this.todos = this.todos.map(todo => todo !== todoToSave ? + todo : + { + ...todo, + title: text, + }); + + this.inform(); + } + + clearCompleted() { + this.todos = this.todos.filter(todo => !todo.completed); + + this.inform(); + } +} + +export default TodoModel; diff --git a/src/demos/todomvc/todos-list.jsx b/src/demos/todomvc/todos-list.jsx new file mode 100644 index 0000000000..be2c424125 --- /dev/null +++ b/src/demos/todomvc/todos-list.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import TodoItem from './todo-item'; + +const TodosList = ({ + shownTodos, + editing, + ...props, +}) => Array.isArray(shownTodos) && shownTodos.length ? ( +
      + {shownTodos.map(({ id, ...todo }) => ( + + ))} +
    +) : null; + +export default TodosList; diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx new file mode 100644 index 0000000000..fd26c897ae --- /dev/null +++ b/src/demos/todomvc/todos.jsx @@ -0,0 +1,106 @@ +import React, { Component } from 'react'; +import TodosList from './todos-list'; + +const ENTER_KEY = 13; +const app = app || {}; + +app.ALL_TODOS = 'all'; +app.ACTIVE_TODOS = 'active'; +app.COMPLETED_TODOS = 'completed'; + +class Todos extends Component { + constructor(props, context) { + super(props, context); + + this.toggle = this.toggle.bind(this); + this.destroy = this.destroy.bind(this); + this.edit = this.edit.bind(this); + this.save = this.save.bind(this); + this.cancel = this.cancel.bind(this); + + this.state = { + nowShowing: app.ALL_TODOS, + editing: null, + newTodo: '', + }; + } + + handleChange(event) { + this.setState({ newTodo: event.target.value }); + } + + handleNewTodoKeyDown(event) { + if (event.keyCode !== ENTER_KEY) { + return; + } + + event.preventDefault(); + + const val = this.state.newTodo.trim(); + + if (val) { + this.props.model.addTodo(val); + this.setState({ newTodo: '' }); + } + } + + toggleAll(event) { + const { target: { checked } } = event; + + this.props.model.toggleAll(checked); + } + + toggle(todoToToggle) { + this.props.model.toggle(todoToToggle); + } + + destroy(todo) { + this.props.model.destroy(todo); + } + + edit(todo) { + this.setState({ editing: todo.id }); + } + + save(todoToSave, text) { + this.props.model.save(todoToSave, text); + this.setState({ editing: null }); + } + + cancel() { + this.setState({ editing: null }); + } + + clearCompleted() { + this.props.model.clearCompleted(); + } + + render() { + const { props: { model: { todos } } } = this; + + const shownTodos = todos.filter((todo) => { + switch (this.state.nowShowing) { + case app.ACTIVE_TODOS: + return !todo.completed; + case app.COMPLETED_TODOS: + return todo.completed; + default: + return true; + } + }); + + return ( + + ); + } +} + +export default Todos; diff --git a/src/demos/todomvc/utils.js b/src/demos/todomvc/utils.js new file mode 100644 index 0000000000..72f0660695 --- /dev/null +++ b/src/demos/todomvc/utils.js @@ -0,0 +1,35 @@ +/* global localStorage */ + +export function uuid() { + let i; + let random; + // eslint-disable-next-line no-shadow + let uuid = ''; + + for (i = 0; i < 32; i++) { + random = Math.random() * 16 | 0; + if (i === 8 || i === 12 || i === 16 || i === 20) { + uuid += '-'; + } + // eslint-disable-next-line no-mixed-operators, no-nested-ternary + uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) + .toString(16); + } + + return uuid; +} + +export function pluralize(count, word) { + return count === 1 ? word : `${word}s`; +} + +export function store(namespace, data) { + if (data) { + return localStorage.setItem(namespace, JSON.stringify(data)); + } + + // eslint-disable-next-line no-shadow + const store = localStorage.getItem(namespace); + + return (store && JSON.parse(store)) || []; +} From c6663aeee17931f6689d6797583b1ca679cd7fae Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 09:36:09 +0200 Subject: [PATCH 20/84] start adding footer --- src/demos/todomvc/todo-footer.jsx | 61 +++++++++++++++++++++++++++++++ src/demos/todomvc/todos.jsx | 31 +++++++++------- src/demos/todomvc/utils.js | 4 ++ 3 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/demos/todomvc/todo-footer.jsx diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx new file mode 100644 index 0000000000..26d21acdd7 --- /dev/null +++ b/src/demos/todomvc/todo-footer.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import withReact from '../../js/with-react'; +import { pluralize, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS } from './utils'; +import AXAButton from '../../components/m-button'; +import AXAFooter from '../../components/o-footer'; +import AXAFooterMain from '../../components/m-footer-main'; +import AXAFooterLinks from '../../components/m-footer-links'; +import AXAFooterSub from '../../components/m-footer-sub'; +import AXAFooterLegals from '../../components/m-footer-legals'; + +const withReactBound = withReact(React); + +const AXAButtonReact = withReactBound(AXAButton); +const AXAFooterReact = withReactBound(AXAFooter); +const AXAFooterMainReact = withReactBound(AXAFooterMain); +const AXAFooterLinksReact = withReactBound(AXAFooterLinks); +const AXAFooterSubReact = withReactBound(AXAFooterSub); +const AXAFooterLegalsReact = withReactBound(AXAFooterLegals); + +const footerItems = [ + { name: 'All', url: '#', state: ALL_TODOS }, + { name: 'Active', url: '#active', state: ACTIVE_TODOS }, + { name: 'completed', url: '#completed', state: COMPLETED_TODOS }, +]; + +const TodoFooter = ({ + title = 'Visible Todos', + items = footerItems, + count, + completedCount, + onClearCompleted, + nowShowing, +}) => { + const activeTodoWord = pluralize(this.props.count, 'item'); + const selected = item => ({ + ...item, + selected: item.state === nowShowing, + }); + + return ( + + + + + + + + + {count} {activeTodoWord} left + + + {completedCount > 0 && + Clear Completed + } + + + + ); +}; + +export default TodoFooter; diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index fd26c897ae..a9a6ff8192 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -1,12 +1,9 @@ import React, { Component } from 'react'; import TodosList from './todos-list'; +import TodoFooter from './todo-footer'; +import { ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS } from './utils'; const ENTER_KEY = 13; -const app = app || {}; - -app.ALL_TODOS = 'all'; -app.ACTIVE_TODOS = 'active'; -app.COMPLETED_TODOS = 'completed'; class Todos extends Component { constructor(props, context) { @@ -17,9 +14,10 @@ class Todos extends Component { this.edit = this.edit.bind(this); this.save = this.save.bind(this); this.cancel = this.cancel.bind(this); + this.clearCompleted = this.clearCompleted.bind(this); this.state = { - nowShowing: app.ALL_TODOS, + nowShowing: ALL_TODOS, editing: null, newTodo: '', }; @@ -77,29 +75,36 @@ class Todos extends Component { render() { const { props: { model: { todos } } } = this; + const { state } = this; const shownTodos = todos.filter((todo) => { - switch (this.state.nowShowing) { - case app.ACTIVE_TODOS: + switch (state.nowShowing) { + case ACTIVE_TODOS: return !todo.completed; - case app.COMPLETED_TODOS: + case COMPLETED_TODOS: return todo.completed; default: return true; } }); - return ( + const activeTodoCount = todos.reduce((accum, todo) => todo.completed ? accum : accum + 1, 0); + const completedCount = todos.length - activeTodoCount; + + return [ - ); + />, + ((activeTodoCount || completedCount) && ( + + )), + ]; } } diff --git a/src/demos/todomvc/utils.js b/src/demos/todomvc/utils.js index 72f0660695..932973b7f0 100644 --- a/src/demos/todomvc/utils.js +++ b/src/demos/todomvc/utils.js @@ -33,3 +33,7 @@ export function store(namespace, data) { return (store && JSON.parse(store)) || []; } + +export const ALL_TODOS = 'all'; +export const ACTIVE_TODOS = 'active'; +export const COMPLETED_TODOS = 'completed'; From ed232dc8b0ea55b63bd4528796b5cd254e5264b1 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 09:38:06 +0200 Subject: [PATCH 21/84] fixed 0 output --- src/demos/todomvc/todos.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index a9a6ff8192..e665ce458c 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -101,9 +101,9 @@ class Todos extends Component { onSave={this.save} onCancel={this.cancel} />, - ((activeTodoCount || completedCount) && ( + ((activeTodoCount || completedCount) ? ( - )), + ) : null), ]; } } From 740303c006feff4f3d5f23a4042fe651564fafde Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 10:03:52 +0200 Subject: [PATCH 22/84] start adding header --- src/demos/todomvc/todo-footer.jsx | 2 +- src/demos/todomvc/todo-header.jsx | 33 +++++++++++++++++++++++++++++++ src/demos/todomvc/todo-item.jsx | 1 - src/demos/todomvc/todos-list.jsx | 4 ++-- src/demos/todomvc/todos.jsx | 9 ++++++++- 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 src/demos/todomvc/todo-header.jsx diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index 26d21acdd7..f53d208376 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -31,7 +31,7 @@ const TodoFooter = ({ onClearCompleted, nowShowing, }) => { - const activeTodoWord = pluralize(this.props.count, 'item'); + const activeTodoWord = pluralize(count, 'item'); const selected = item => ({ ...item, selected: item.state === nowShowing, diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx new file mode 100644 index 0000000000..7d7699423c --- /dev/null +++ b/src/demos/todomvc/todo-header.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import withReact from '../../js/with-react'; +import AXAHeader from '../../components/o-header'; +import AXAHeaderMain from '../../components/m-header-main'; +import AXAHeaderLogo from '../../components/m-header-logo'; + +const withReactBound = withReact(React); +const AXAHeaderReact = withReactBound(AXAHeader); +const AXAHeaderMainReact = withReactBound(AXAHeaderMain); +const AXAHeaderLogoReact = withReactBound(AXAHeaderLogo); + +const TodoHeader = ({ + newTodo, + handleNewTodoKeyDown, + handleChange, +}) => ( + + + +

    Todos

    + +
    +
    +); + +export default TodoHeader; diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index 15cd9080bd..67511b7a93 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -14,7 +14,6 @@ class TodoItem extends Component { constructor(props, context) { super(props, context); - this.onClick = this.onClick.bind(this); this.handleRef = this.handleRef.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.handleEdit = this.handleEdit.bind(this); diff --git a/src/demos/todomvc/todos-list.jsx b/src/demos/todomvc/todos-list.jsx index be2c424125..df8fd1dd12 100644 --- a/src/demos/todomvc/todos-list.jsx +++ b/src/demos/todomvc/todos-list.jsx @@ -7,8 +7,8 @@ const TodosList = ({ ...props, }) => Array.isArray(shownTodos) && shownTodos.length ? (
      - {shownTodos.map(({ id, ...todo }) => ( - + {shownTodos.map(todo => ( + ))}
    ) : null; diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index e665ce458c..48da9d3eeb 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import TodoHeader from './todo-header'; import TodosList from './todos-list'; import TodoFooter from './todo-footer'; import { ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS } from './utils'; @@ -15,6 +16,8 @@ class Todos extends Component { this.save = this.save.bind(this); this.cancel = this.cancel.bind(this); this.clearCompleted = this.clearCompleted.bind(this); + this.handleNewTodoKeyDown = this.handleNewTodoKeyDown.bind(this); + this.handleChange = this.handleChange.bind(this); this.state = { nowShowing: ALL_TODOS, @@ -32,6 +35,8 @@ class Todos extends Component { return; } + console.log('handleNewTodoKeyDown', event); + event.preventDefault(); const val = this.state.newTodo.trim(); @@ -92,6 +97,7 @@ class Todos extends Component { const completedCount = todos.length - activeTodoCount; return [ + , , ((activeTodoCount || completedCount) ? ( - + ) : null), ]; } From 110d27f1472a0100d1cae9e57dca3fce8107a7fa Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 10:33:14 +0200 Subject: [PATCH 23/84] all-demos.js:8502 Uncaught TypeError: Cannot read property 'id' of undefined --- src/demos/todomvc/todo-item.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index 67511b7a93..b41c22a5d1 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -76,9 +76,10 @@ class TodoItem extends Component { handleEdit() { const { props } = this; + const { todo } = props; - props.onEdit(); - this.setState({ editText: props.todo.title }); + props.onEdit(todo); + this.setState({ editText: todo.title }); } handleChange(event) { From 28f70c68dd16a93bd3dd8b7e2ffca457a982531e Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 10:38:59 +0200 Subject: [PATCH 24/84] improving autofocus on edit --- src/demos/todomvc/todo-header.jsx | 4 +++- src/demos/todomvc/todos.jsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 7d7699423c..6d34d028d1 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -10,6 +10,7 @@ const AXAHeaderMainReact = withReactBound(AXAHeaderMain); const AXAHeaderLogoReact = withReactBound(AXAHeaderLogo); const TodoHeader = ({ + editing, newTodo, handleNewTodoKeyDown, handleChange, @@ -19,12 +20,13 @@ const TodoHeader = ({

    Todos

    input && !editing && input.focus()} className="m-todo-header__new" placeholder="What needs to be done?" value={newTodo} onKeyDown={handleNewTodoKeyDown} onChange={handleChange} - autoFocus={true} + autoFocus={!editing} /> diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index 48da9d3eeb..fd852cc7fb 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -97,7 +97,7 @@ class Todos extends Component { const completedCount = todos.length - activeTodoCount; return [ - , + , Date: Thu, 29 Mar 2018 10:41:58 +0200 Subject: [PATCH 25/84] just added some margin to make the footer completely visible --- src/demos/demo.react.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/demo.react.html b/src/demos/demo.react.html index 99cbee13bc..9e73c47c95 100644 --- a/src/demos/demo.react.html +++ b/src/demos/demo.react.html @@ -1,4 +1,4 @@ -
    +

    Demo on how to pass a callback function:

    From b8ee7d06bcd138305b550f2095d63e93cfd9bebb Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 11:19:47 +0200 Subject: [PATCH 26/84] quickfixed input losing focus within ce --- src/demos/todomvc/todo-header.jsx | 77 +++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 6d34d028d1..f3ef12eafb 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import withReact from '../../js/with-react'; import AXAHeader from '../../components/o-header'; import AXAHeaderMain from '../../components/m-header-main'; @@ -9,27 +9,68 @@ const AXAHeaderReact = withReactBound(AXAHeader); const AXAHeaderMainReact = withReactBound(AXAHeaderMain); const AXAHeaderLogoReact = withReactBound(AXAHeaderLogo); +// @todo: super hacky way to keep the input's focus within custom elements +class Input extends Component { + constructor(props, context) { + super(props, context); + + this.handleRef = this.handleRef.bind(this); + } + + componentDidUpdate() { + const { props: { editing }, input } = this; + + if (input && !editing) { + input.autofocus = !editing; + + setTimeout(() => { + input.focus(); + }, 200); + } + } + + handleRef(input) { + this.input = input; + } + + render() { + const { props, handleRef } = this; + + return ( + + ); + } +} + const TodoHeader = ({ editing, newTodo, handleNewTodoKeyDown, handleChange, -}) => ( - - - -

    Todos

    - input && !editing && input.focus()} - className="m-todo-header__new" - placeholder="What needs to be done?" - value={newTodo} - onKeyDown={handleNewTodoKeyDown} - onChange={handleChange} - autoFocus={!editing} - /> -
    -
    -); +}) => { + console.log('render todo header'); + + return ( + + + +

    Todos

    + + +
    +
    + ); +} export default TodoHeader; From 9974270c34f8170bb4560decfe55491bbc270abc Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 11:55:45 +0200 Subject: [PATCH 27/84] start documentation --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dec708e1f5..da48cac6cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -221,6 +221,45 @@ Invoked when the custom element is disconnected from the document's DOM. The render loop makes sure that upon each [`attributeChangedCallback()`](#attributechangedcallbackname-oldvalue-newvalue) invocation or any observed [property `setter()`](#property-setter) invocation that the flattened DOM is recomputed and that [`willRenderCallback()`](#willrendercallbackinitial) and [`didRenderCallback()`](#didrendercallbackinitial) lifecycle hooks are called respectively. +## Integration + +The goal is that custom elements can be shared across frameworks and libraries like Angular, React, Vue, you name it. To ease this process we provide generic wrapper functions. + +### `withReact()` + +To turn any custom element into a working React Component, you just need to follow these steps: + +1. `import` React +2. `import` withReact +3. `import` any web components you need +4. bind `withReact` to you `import`ed React version + - and may pass optional options +5. wrap all your needed web components +6. use them like regular React components in your app + + **Note:** events work similiar to React's standard events, but each web components could trigger custom events. Make sure to check the out the web-components documentation itself! + +```js +// import your dependencies - 1, 2, and 3 +import React from 'react'; +import withReact from '@axa-ch/patterns-library/src/js/with-react'; +import AXAButton from '@axa-ch/patterns-library/dist/components/m-button'; + +// 4. bind withReact to your imported Reat version +// and optionally pass options +const withReactBound = withReact(React, { + pure: true, +}) + +// 5. wrap your need web components +const AXAButtonReact = withReactBound(AXAButton); + +// 6. use them in your app like regular React components +const MyApp = ({ color, onClick }) => ( + Hello World +); +``` + # How do we release a new version Please run `npm run release` and follow the steps in the wizard. From 8668f72a1ee095c68d759bf520572fde1c23e26a Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 12:05:23 +0200 Subject: [PATCH 28/84] improved options --- CONTRIBUTING.md | 14 +++++++------- src/js/with-react.jsx | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da48cac6cf..91abb879b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -233,11 +233,11 @@ To turn any custom element into a working React Component, you just need to foll 2. `import` withReact 3. `import` any web components you need 4. bind `withReact` to you `import`ed React version - - and may pass optional options 5. wrap all your needed web components + - and may pass optional options 6. use them like regular React components in your app - **Note:** events work similiar to React's standard events, but each web components could trigger custom events. Make sure to check the out the web-components documentation itself! + **Note:** events work similiar to React's standard events, but each web components could trigger custom events. Make sure to check them out at the web-components documentation itself! ```js // import your dependencies - 1, 2, and 3 @@ -246,13 +246,13 @@ import withReact from '@axa-ch/patterns-library/src/js/with-react'; import AXAButton from '@axa-ch/patterns-library/dist/components/m-button'; // 4. bind withReact to your imported Reat version -// and optionally pass options -const withReactBound = withReact(React, { - pure: true, -}) +const withReactBound = withReact(React) // 5. wrap your need web components -const AXAButtonReact = withReactBound(AXAButton); +// and optionally pass options +const AXAButtonReact = withReactBound(AXAButton, { + pure: true, +}); // 6. use them in your app like regular React components const MyApp = ({ color, onClick }) => ( diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 1c286a334b..98ada63d2f 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -13,7 +13,6 @@ const PROP_BLACKLIST = [ * * @link https://github.com/webcomponents/react-integration - inspired by react-integration * @param React - * @param {Boolean} [options.pure=true] - Is this a pure component? * @returns {function(*)} * * @example How to use @@ -21,13 +20,15 @@ const PROP_BLACKLIST = [ * import withReact from '@axa-ch/patterns-library/src/js/with-react'; * import AXAButton from '@axa-ch/patterns-library/dist/components/m-button' * - * const AXAButtonReact = withReact(React)(AXAButton); + * const AXAButtonReact = withReact(React)(AXAButton, { + * pure: true, + * }); * * const MyApp = ({ color, onClick }) => ( * Hello World * ); */ -const withReact = (React, { pure = true } = {}) => (WebComponent) => { +const withReact = React => (WebComponent, { pure = true } = {}) => { const { name } = WebComponent; const displayName = `${name}React`; const WCTagName = dasherize(WebComponent.name); From ada3123396a2ab77de71e0d478a199a3bcbd5699 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 12:13:09 +0200 Subject: [PATCH 29/84] removed log --- src/demos/todomvc/todo-header.jsx | 38 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index f3ef12eafb..11f41a59a8 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -50,27 +50,23 @@ const TodoHeader = ({ newTodo, handleNewTodoKeyDown, handleChange, -}) => { - console.log('render todo header'); +}) => ( + + + +

    Todos

    - return ( - - - -

    Todos

    - - -
    -
    - ); -} + +
    +
    +); export default TodoHeader; From 68f38b00867e9aad0b73144be224467bf22bfbb6 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 12:15:13 +0200 Subject: [PATCH 30/84] removed yet another log --- src/demos/todomvc/todos.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index fd852cc7fb..af0d6dd80b 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -35,8 +35,6 @@ class Todos extends Component { return; } - console.log('handleNewTodoKeyDown', event); - event.preventDefault(); const val = this.state.newTodo.trim(); From d770bcbfd1d54f2ccb435d49df131966c2979a3e Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 13:07:26 +0200 Subject: [PATCH 31/84] improved focus hack --- src/demos/todomvc/todo-header.jsx | 37 ++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 11f41a59a8..37f8fed3ec 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -14,32 +14,53 @@ class Input extends Component { constructor(props, context) { super(props, context); + this.onBlur = this.onBlur.bind(this); + this.onChange = this.onChange.bind(this); this.handleRef = this.handleRef.bind(this); + + this.state = { + reFocus: false, + }; } - componentDidUpdate() { - const { props: { editing }, input } = this; + onBlur() { + const { state: { reFocus } } = this; - if (input && !editing) { - input.autofocus = !editing; + if (reFocus === true) { + this.setState({ + reFocus: false, + }); setTimeout(() => { - input.focus(); - }, 200); + this.input.focus(); + }, 10); } } + onChange(event) { + const { props: { onChange } } = this; + + this.setState({ + reFocus: true, + }); + + onChange(event); + } + handleRef(input) { this.input = input; } render() { - const { props, handleRef } = this; + const { handleRef } = this; + const { onChange, ...props } = this.props; return ( ); } From d9074ef514210f83970284e2937ddbb7e2266402 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 13:08:43 +0200 Subject: [PATCH 32/84] focus hack cleanup --- src/demos/todomvc/todo-header.jsx | 4 +--- src/demos/todomvc/todos.jsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 37f8fed3ec..2fa340f6f9 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -67,7 +67,6 @@ class Input extends Component { } const TodoHeader = ({ - editing, newTodo, handleNewTodoKeyDown, handleChange, @@ -80,11 +79,10 @@ const TodoHeader = ({ diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index af0d6dd80b..ef24357fb6 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -95,7 +95,7 @@ class Todos extends Component { const completedCount = todos.length - activeTodoCount; return [ - , + , Date: Thu, 29 Mar 2018 13:35:23 +0200 Subject: [PATCH 33/84] start adding styles --- src/demos/todomvc/app.jsx | 7 +++++-- src/demos/todomvc/index.scss | 15 +++++++++++++++ src/demos/todomvc/todos.jsx | 36 ++++++++++++++++++++---------------- 3 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 src/demos/todomvc/index.scss diff --git a/src/demos/todomvc/app.jsx b/src/demos/todomvc/app.jsx index ce8f48fbde..745c948b0a 100644 --- a/src/demos/todomvc/app.jsx +++ b/src/demos/todomvc/app.jsx @@ -1,17 +1,19 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import BaseComponentGlobal from '../../js/abstract/base-component-global'; import Todos from './todos'; import TodoModel from './todo-model'; +import styles from './index.scss'; const todoModel = new TodoModel('react-todos'); const TodoApp = ({ model, }) => ( -
    +
    -
    +
    ); function render() { @@ -24,6 +26,7 @@ function render() { todoModel.subscribe(render); document.addEventListener('DOMContentLoaded', () => { + BaseComponentGlobal.appendGlobalStyles(styles); render(); }); diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss new file mode 100644 index 0000000000..ea52cd932d --- /dev/null +++ b/src/demos/todomvc/index.scss @@ -0,0 +1,15 @@ +@import "../../styles/vertical-rhythm/vertical-rhythm-mixins"; + +.o-todo { + @include vr-pad-items(); + + display: block; + + > * { + @include vr-margin-item(); + + display: block; + } +} + + diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index ef24357fb6..c5a54be546 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -94,22 +94,26 @@ class Todos extends Component { const activeTodoCount = todos.reduce((accum, todo) => todo.completed ? accum : accum + 1, 0); const completedCount = todos.length - activeTodoCount; - return [ - , - , - ((activeTodoCount || completedCount) ? ( - - ) : null), - ]; + return ( +
    + + + + + {(activeTodoCount || completedCount) ? ( + + ) : null} +
    + ); } } From 7917322a632959d61d6753cb50b5954aafe58f0a Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 13:49:35 +0200 Subject: [PATCH 34/84] improving styles --- src/demos/todomvc/index.scss | 38 +++++++++++++++++++++++++++++++++ src/demos/todomvc/todo-item.jsx | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index ea52cd932d..d9496a4bc8 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -1,3 +1,7 @@ +@import "../../styles/settings/colors"; +@import "../../styles/mixins/respond"; +@import "../../styles/mixins/unstyle-list"; +@import "../../styles/typo/typo-mixins"; @import "../../styles/vertical-rhythm/vertical-rhythm-mixins"; .o-todo { @@ -12,4 +16,38 @@ } } +.m-todo-header__title { + @include typo-small-module-title--publico(); + @include respond-down(sm) { + display: none; + } +} + +.m-todo-header__new { + margin-left: 20px; + + flex-grow: 1; +} + +.m-todo__list { + @include unstyle-list(); +} + +.m-todo__item { + @include typo-item-highlight(); + + position: relative; +} + +.m-todo__wrap { + +} + +.m-todo__toggle { + +} + +.m-todo__edit { + +} diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index b41c22a5d1..8bfaccde87 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -127,7 +127,7 @@ class TodoItem extends Component { Date: Thu, 29 Mar 2018 14:06:09 +0200 Subject: [PATCH 35/84] utilised completed state --- src/demos/todomvc/index.scss | 6 ++++++ src/demos/todomvc/todo-item.jsx | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index d9496a4bc8..1da2b2e669 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -48,6 +48,12 @@ } +.m-todo__label { + &.is-todo-completed { + text-decoration: line-through; + } +} + .m-todo__edit { } diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index 8bfaccde87..a3b06f1d05 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import classnames from 'classnames'; import withReact from '../../js/with-react'; import AXAButton from '../../components/m-button'; import AXAIcon from '../../components/a-icon'; @@ -103,7 +104,8 @@ class TodoItem extends Component { render() { const { props, state } = this; - const { todo, onToggle, onDestroy, id } = props; + const { todo, onToggle, onDestroy } = props; + const { title, completed, id } = todo; const htmlFor = `m-todo-${id}`; return ( @@ -112,12 +114,12 @@ class TodoItem extends Component { onToggle(todo)} /> - onDestroy(todo)}> - + From deab83ba586884e7e2c22de038534ca348e29de0 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 14:25:11 +0200 Subject: [PATCH 38/84] moar layout --- src/demos/todomvc/index.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index 0f24810ff9..8cdf4eb615 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -49,10 +49,14 @@ } .m-todo__toggle { + @include size(40px); + margin-right: 20px; } .m-todo__label { + flex-grow: 1; + &.is-todo-completed { text-decoration: line-through; } From 52944eb0f73353a000c53dd62c663666acf2ae06 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 16:19:40 +0200 Subject: [PATCH 39/84] improved inputs --- src/demos/todomvc/index.scss | 55 ++++++++++++++++++++++++++++++- src/demos/todomvc/todo-header.jsx | 2 +- src/demos/todomvc/todo-item.jsx | 2 +- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index 8cdf4eb615..64f147421e 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -32,6 +32,29 @@ flex-grow: 1; } +.m-todo__input { + @include typo-text-longer(); + + display: block; + width: 100%; + padding: 5px 15px; + border: 2px solid $color-prim-gray-wild-sand; + background: $color-prim-white; + + &::placeholder { + color: $color-prim-gray-dusty; + opacity: 1; + } + + &::-ms-clear { + display: none; + } + + @include respond-up(md) { + padding: 10px 20px; + } +} + .m-todo__list { @include unstyle-list(); @include make-container(); @@ -39,31 +62,61 @@ } .m-todo__item { - @include typo-item-highlight(); + @include typo-text-longer(); position: relative; } .m-todo__wrap { @include make-row(null); + + align-items: center; } .m-todo__toggle { @include size(40px); margin-right: 20px; + + cursor: pointer; } .m-todo__label { + margin: 0; + flex-grow: 1; + cursor: pointer; + white-space: pre-line; + word-break: break-all; + &.is-todo-completed { text-decoration: line-through; } } .m-todo__edit { + $left: 45px; + $zIndex: 10; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: $left; + z-index: $zIndex; + width: calc(100% - #{$left}); + padding-top: auto; + padding-bottom: auto; + + @include respond-up(md) { + $left: 40px; + + left: $left; + + width: calc(100% - #{$left}); + } } .m-todo__destroy-icon { diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 2fa340f6f9..d7dd262c55 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -77,7 +77,7 @@ const TodoHeader = ({

    Todos

    Date: Thu, 29 Mar 2018 16:34:31 +0200 Subject: [PATCH 40/84] improved input styling --- src/demos/todomvc/index.scss | 63 +++++++++++++++++++++++-------- src/demos/todomvc/todo-header.jsx | 2 +- src/demos/todomvc/todo-item.jsx | 24 ++++++------ 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index 64f147421e..fdfdd3e845 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -18,21 +18,7 @@ } } -.m-todo-header__title { - @include typo-small-module-title--publico(); - - @include respond-down(sm) { - display: none; - } -} - -.m-todo-header__new { - margin-left: 20px; - - flex-grow: 1; -} - -.m-todo__input { +%a-todo__input { @include typo-text-longer(); display: block; @@ -41,6 +27,8 @@ border: 2px solid $color-prim-gray-wild-sand; background: $color-prim-white; + transition: color 0.2s ease, border-color 0.2s ease; + &::placeholder { color: $color-prim-gray-dusty; opacity: 1; @@ -50,26 +38,67 @@ display: none; } + &:focus { + outline: none; + border-color: $color-prim-blue-deep-sapphire; + color: $color-prim-blue-deep-sapphire; + } + @include respond-up(md) { padding: 10px 20px; } } +.m-todo-header__title { + @include typo-small-module-title--publico(); + + @include respond-down(sm) { + display: none; + } +} + +.m-todo-header__new { + @extend %a-todo__input; + + margin-left: 20px; + + flex-grow: 1; +} + .m-todo__list { + $vr-paddings: + ( + // Extra small screen / phone + xs: $vr-base * 1, // 10px + // Small screen / phone + // sm: 40px, + // Medium screen / tablet + md: $vr-base * 2, // 20px + ); + @include unstyle-list(); @include make-container(); @include make-container-max-widths(); + @include vr-pad-items($vr-paddings, $vr-paddings); + + display: block; + + > * { + @include vr-margin-item($vr-paddings); + } } .m-todo__item { @include typo-text-longer(); - position: relative; + display: block; } .m-todo__wrap { @include make-row(null); + position: relative; + align-items: center; } @@ -96,6 +125,8 @@ } .m-todo__edit { + @extend %a-todo__input; + $left: 45px; $zIndex: 10; diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index d7dd262c55..2fa340f6f9 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -77,7 +77,7 @@ const TodoHeader = ({

    Todos

    onDestroy(todo)}> - - {editing ? - - : null} + {editing ? + + : null} + ); } From 7762d20287badbfd305a43ce7fc2d5ec5680b5fb Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 16:43:08 +0200 Subject: [PATCH 41/84] improving butons --- src/demos/todomvc/index.scss | 4 ++++ src/demos/todomvc/todo-footer.jsx | 8 +++++--- src/demos/todomvc/todo-item.jsx | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index fdfdd3e845..4c9f88e2e9 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -153,3 +153,7 @@ .m-todo__destroy-icon { @include size(20px); } + +.m-todo-footer__count--completed { + margin-right: 20px; +} diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index f53d208376..b985be0339 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import classnames from 'classnames'; import withReact from '../../js/with-react'; import { pluralize, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS } from './utils'; import AXAButton from '../../components/m-button'; @@ -36,6 +37,7 @@ const TodoFooter = ({ ...item, selected: item.state === nowShowing, }); + const hasCompleted = completedCount > 0; return ( @@ -45,12 +47,12 @@ const TodoFooter = ({ - + {count} {activeTodoWord} left - {completedCount > 0 && - Clear Completed + {hasCompleted && + Clear Completed } diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index e1ede5b5d7..d6ab1e97d0 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -122,7 +122,7 @@ class TodoItem extends Component { {title} - onDestroy(todo)}> + onDestroy(todo)} color="red" size="md" motion> From f4c728a8c4c331367397cc6aeac7c864d5bbba81 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 16:45:51 +0200 Subject: [PATCH 42/84] fixed little typo --- src/demos/todomvc/todo-footer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index b985be0339..2bed19b07d 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -21,7 +21,7 @@ const AXAFooterLegalsReact = withReactBound(AXAFooterLegals); const footerItems = [ { name: 'All', url: '#', state: ALL_TODOS }, { name: 'Active', url: '#active', state: ACTIVE_TODOS }, - { name: 'completed', url: '#completed', state: COMPLETED_TODOS }, + { name: 'Completed', url: '#completed', state: COMPLETED_TODOS }, ]; const TodoFooter = ({ From 385f444d35a59101cf3b474dda7ea88edd7b76f6 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 17:16:09 +0200 Subject: [PATCH 43/84] return cancelled of fire --- src/js/fire.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/fire.js b/src/js/fire.js index 8239d2a389..f1726db715 100644 --- a/src/js/fire.js +++ b/src/js/fire.js @@ -15,7 +15,7 @@ function fire(eventTarget, eventName, eventObject, eventInit = {}) { }); // Dispatch the event. - eventTarget.dispatchEvent(event); + return eventTarget.dispatchEvent(event); } export default fire; From 99db53bf3f62b1bf3eafadb55dc7fd308c5f5981 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 17:51:37 +0200 Subject: [PATCH 44/84] fixed custom event check --- src/js/custom-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/custom-event.js b/src/js/custom-event.js index 15772b6f55..7b683b1297 100644 --- a/src/js/custom-event.js +++ b/src/js/custom-event.js @@ -7,7 +7,7 @@ const CustomEvent = (function () { try { - const ce = new window.CustomEvent('test'); + const ce = new window.CustomEvent('test', { cancelable: true }); ce.preventDefault(); if (ce.defaultPrevented !== true) { // IE has problems with .preventDefault() on custom events From 8bf977b069add63cc9e9282fa6153aada3378396 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 18:07:32 +0200 Subject: [PATCH 45/84] start adding custom events to footer links --- src/components/m-footer-links/_template.js | 2 +- .../m-footer-links/js/footer-links.js | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/m-footer-links/_template.js b/src/components/m-footer-links/_template.js index ad122c4c74..c74d4c97ad 100644 --- a/src/components/m-footer-links/_template.js +++ b/src/components/m-footer-links/_template.js @@ -10,7 +10,7 @@ export default function ({ title, items }) { diff --git a/src/components/m-footer-links/js/footer-links.js b/src/components/m-footer-links/js/footer-links.js index 105de1c5bc..097d686395 100644 --- a/src/components/m-footer-links/js/footer-links.js +++ b/src/components/m-footer-links/js/footer-links.js @@ -1,3 +1,5 @@ +import on from '../../../js/on'; +import fire from '../../../js/fire'; import { subscribe } from '../../../js/pubsub'; import DropDown from '../../m-dropdown/js/drop-down'; @@ -5,8 +7,18 @@ const hasDropdownBreakpoints = 'xs'; // @TODO: dependency to a-device-state not explicit export default class FooterLinks { - constructor(wcNode) { + static DEFAULTS = { + link: 'js-footer-links__link', + }; + + constructor(wcNode, options) { this.wcNode = wcNode; + this.options = { + ...FooterLinks.DEFAULTS, + ...options, + }; + + this.handleClick = this.handleClick.bind(this); this.on(); } @@ -25,12 +37,26 @@ export default class FooterLinks { delete this.dropDown; } }); + + this.unClick = on(this.wcNode, 'click', this.options.link, this.handleClick, { passive: false }); } off() { if (this.unsubscribe) { this.unsubscribe(); } + + if (this.unClick) { + this.unClick(); + } + } + + handleClick(event, delegateTarget) { + const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true }); + + if (!cancelled) { + event.preventDefault(); + } } destroy() { From 54b6c522ad9926598b078f59631b04e391a9647c Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 18:08:15 +0200 Subject: [PATCH 46/84] try to add custom event to button --- src/components/m-button/_template.js | 2 +- src/components/m-button/index.js | 16 ++++++ src/components/m-button/js/button.js | 75 ++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/components/m-button/js/button.js diff --git a/src/components/m-button/_template.js b/src/components/m-button/_template.js index 27419811e8..5ffcc35b80 100644 --- a/src/components/m-button/_template.js +++ b/src/components/m-button/_template.js @@ -13,7 +13,7 @@ export default function ({ gpu, arrow, }, childrenFragment) { - const buttonClasses = classnames('m-button', classes, { + const buttonClasses = classnames('m-button', 'js-button', classes, { [`m-button--${color}`]: color, [`m-button--${size}`]: size, 'm-button--ghost': ghost, diff --git a/src/components/m-button/index.js b/src/components/m-button/index.js index 789740858e..c1a0c3d646 100644 --- a/src/components/m-button/index.js +++ b/src/components/m-button/index.js @@ -2,6 +2,7 @@ import styles from './index.scss'; import template from './_template'; import BaseComponentGlobal from '../../js/abstract/base-component-global'; import wcdomready from '../../js/wcdomready'; +import Button from './js/button'; class AXAButton extends BaseComponentGlobal { static get observedAttributes() { return ['arrow', 'classes', 'color', 'ghost', 'motion', 'size', 'tag', 'url']; } @@ -9,6 +10,21 @@ class AXAButton extends BaseComponentGlobal { constructor() { super(styles, template); } + + didRenderCallback() { + if (this.button) { + this.button.destroy(); + } + + this.button = new Button(this); + } + + disconnectedCallback() { + if (this.button) { + this.button.destroy(); + delete this.button; + } + } } wcdomready(() => { diff --git a/src/components/m-button/js/button.js b/src/components/m-button/js/button.js new file mode 100644 index 0000000000..1cbdc895bc --- /dev/null +++ b/src/components/m-button/js/button.js @@ -0,0 +1,75 @@ +import on from '../../../js/on'; +import fire from '../../../js/fire'; + +class Button { + static DEFAULTS = { + button: '.js-button', + }; + + constructor(wcNode, options = {}) { + this.wcNode = wcNode; + + this.options = { + ...Button.DEFAULTS, + ...options, + }; + + this.handleClick = this.handleClick.bind(this); + + this.init(); + } + + init() { + this.button = this.wcNode.querySelector(this.options.button); + + this.on(); + } + + on() { + this.off(); + + console.log(this.button); + + this.unClick = on(this.button, 'click', this.handleClick, { + passive: false, + }); + } + + off() { + if (this.unClick) { + this.unClick(); + } + } + + handleClick(event) { + console.log('button click', this.wcNode); + + const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true }); + + console.log('button click', cancelled); + + if (!cancelled) { + event.preventDefault(); + } + } + + destroy() { + this.off(); + + if (this.button) { + delete this.button; + } + + if (this.wcNode) { + delete this.wcNode; + } + + if (this.options) { + delete this.options; + } + + delete this.handleClick; + } +} + +export default Button; From 07a28d4514f79779c16cb944ba5dcb4fe49544d8 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 18:08:45 +0200 Subject: [PATCH 47/84] use new custom events --- src/demos/demo.react.jsx | 2 +- src/demos/todomvc/todo-footer.jsx | 7 +++++-- src/demos/todomvc/todo-item.jsx | 2 +- src/demos/todomvc/todos.jsx | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index f51ecbf4a0..b30e8c838b 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -29,7 +29,7 @@ class MyEventDemoReact extends React.Component { {this.state.isToggleOn ? 'ON' : 'OFF'} , - {this.state.isToggleOn ? 'ON' : 'OFF'}, + {this.state.isToggleOn ? 'ON' : 'OFF'}, ]); } } diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index 2bed19b07d..3e5f41c490 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -42,7 +42,10 @@ const TodoFooter = ({ return ( - + { + event.preventDefault(); + console.log(`footer link ${event.type}`); + }} /> @@ -52,7 +55,7 @@ const TodoFooter = ({ {hasCompleted && - Clear Completed + Clear Completed } diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index d6ab1e97d0..589ab86686 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -122,7 +122,7 @@ class TodoItem extends Component { {title} - onDestroy(todo)} color="red" size="md" motion> + { console.log('click button', event); onDestroy(todo); }} color="red" size="md" motion> diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index c5a54be546..89818a3c6c 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -56,6 +56,7 @@ class Todos extends Component { } destroy(todo) { + console.log(todo); this.props.model.destroy(todo); } From 40309f32a9b26f89df0752b5cd50194c494ada9b Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:32:35 +0200 Subject: [PATCH 48/84] fixed broken event of catching of withReact --- src/components/m-button/js/button.js | 8 +------- src/components/m-footer-links/js/footer-links.js | 4 ++-- src/demos/todomvc/todo-item.jsx | 11 +++++++++-- src/demos/todomvc/todos.jsx | 1 - src/js/with-react.jsx | 15 +++++++++++---- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/m-button/js/button.js b/src/components/m-button/js/button.js index 1cbdc895bc..d79dad3690 100644 --- a/src/components/m-button/js/button.js +++ b/src/components/m-button/js/button.js @@ -28,8 +28,6 @@ class Button { on() { this.off(); - console.log(this.button); - this.unClick = on(this.button, 'click', this.handleClick, { passive: false, }); @@ -42,11 +40,7 @@ class Button { } handleClick(event) { - console.log('button click', this.wcNode); - - const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true }); - - console.log('button click', cancelled); + const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/components/m-footer-links/js/footer-links.js b/src/components/m-footer-links/js/footer-links.js index 097d686395..9f724335ca 100644 --- a/src/components/m-footer-links/js/footer-links.js +++ b/src/components/m-footer-links/js/footer-links.js @@ -51,8 +51,8 @@ export default class FooterLinks { } } - handleClick(event, delegateTarget) { - const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true }); + handleClick(event) { + const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index 589ab86686..daf04f010a 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -20,6 +20,7 @@ class TodoItem extends Component { this.handleEdit = this.handleEdit.bind(this); this.handleChange = this.handleChange.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleDestroy = this.handleDestroy.bind(this); this.state = { editText: props.todo.title, @@ -102,9 +103,15 @@ class TodoItem extends Component { } } + handleDestroy(event) { + const { props: { todo, onDestroy } } = this; + + onDestroy(todo); + } + render() { const { props, state } = this; - const { todo, editing, onToggle, onDestroy } = props; + const { todo, editing, onToggle } = props; const { title, completed, id } = todo; const htmlFor = `m-todo-${id}`; @@ -122,7 +129,7 @@ class TodoItem extends Component { {title} - { console.log('click button', event); onDestroy(todo); }} color="red" size="md" motion> + diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index 89818a3c6c..c5a54be546 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -56,7 +56,6 @@ class Todos extends Component { } destroy(todo) { - console.log(todo); this.props.model.destroy(todo); } diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 98ada63d2f..ed1e2eb37e 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -28,11 +28,10 @@ const PROP_BLACKLIST = [ * Hello World * ); */ -const withReact = React => (WebComponent, { pure = true } = {}) => { +const withReact = React => (WebComponent, { pure = true, passive = false } = {}) => { const { name } = WebComponent; const displayName = `${name}React`; const WCTagName = dasherize(WebComponent.name); - const eventCache = {}; const Component = pure ? React.PureComponent : React.Component; return class WebComponentWrapper extends Component { @@ -44,6 +43,8 @@ const withReact = React => (WebComponent, { pure = true } = {}) => { super(props); this.handleRef = this.handleRef.bind(this); + + this._eventCache = {}; } componentDidMount() { @@ -51,7 +52,7 @@ const withReact = React => (WebComponent, { pure = true } = {}) => { } componentWillReceiveProps(props) { - const { wcNode } = this; + const { wcNode, _eventCache: eventCache } = this; Object.keys(props).forEach((key) => { if (PROP_BLACKLIST.indexOf(key) !== -1) { @@ -66,7 +67,9 @@ const withReact = React => (WebComponent, { pure = true } = {}) => { eventCache[key](); } - eventCache[key] = on(wcNode, key.substring(2).toLowerCase(), props[key]); + const eventName = key.substring(2).toLowerCase(); + + eventCache[key] = on(wcNode, eventName, props[key], { passive }); } else { // set properties by DOM property API's - not HTML setAttribute -> first class props wcNode[key] = props[key]; @@ -75,12 +78,16 @@ const withReact = React => (WebComponent, { pure = true } = {}) => { } componentWillUnmount() { + const { _eventCache: eventCache } = this; + // clean up bound custom events Object.keys(eventCache).forEach((key) => { if (eventCache[key]) { eventCache[key](); } }); + + delete this.wcNode; } handleRef(wcNode) { From 97cb3f8ae152f779954d6cbb0f22e85c470e5c2f Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:37:07 +0200 Subject: [PATCH 49/84] added support for dashed custom events --- src/components/m-button/js/button.js | 2 +- src/components/m-footer-links/js/footer-links.js | 2 +- src/demos/demo.react.jsx | 2 +- src/demos/todomvc/todo-footer.jsx | 4 ++-- src/demos/todomvc/todo-item.jsx | 2 +- src/js/with-react.jsx | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/m-button/js/button.js b/src/components/m-button/js/button.js index d79dad3690..810be08419 100644 --- a/src/components/m-button/js/button.js +++ b/src/components/m-button/js/button.js @@ -40,7 +40,7 @@ class Button { } handleClick(event) { - const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true, composed: true }); + const cancelled = fire(this.wcNode, 'axa-click', {}, { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/components/m-footer-links/js/footer-links.js b/src/components/m-footer-links/js/footer-links.js index 9f724335ca..93d4233910 100644 --- a/src/components/m-footer-links/js/footer-links.js +++ b/src/components/m-footer-links/js/footer-links.js @@ -52,7 +52,7 @@ export default class FooterLinks { } handleClick(event) { - const cancelled = fire(this.wcNode, 'axaclick', {}, { bubbles: true, cancelable: true, composed: true }); + const cancelled = fire(this.wcNode, 'axa-click', {}, { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/demos/demo.react.jsx b/src/demos/demo.react.jsx index b30e8c838b..56e16141a4 100644 --- a/src/demos/demo.react.jsx +++ b/src/demos/demo.react.jsx @@ -29,7 +29,7 @@ class MyEventDemoReact extends React.Component { {this.state.isToggleOn ? 'ON' : 'OFF'} , - {this.state.isToggleOn ? 'ON' : 'OFF'}, + {this.state.isToggleOn ? 'ON' : 'OFF'}, ]); } } diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index 3e5f41c490..632e40d799 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -42,7 +42,7 @@ const TodoFooter = ({ return ( - { + { event.preventDefault(); console.log(`footer link ${event.type}`); }} /> @@ -55,7 +55,7 @@ const TodoFooter = ({ {hasCompleted && - Clear Completed + Clear Completed } diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index daf04f010a..d44532471e 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -129,7 +129,7 @@ class TodoItem extends Component { {title} - + diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index ed1e2eb37e..4dc81ec3b8 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -67,7 +67,7 @@ const withReact = React => (WebComponent, { pure = true, passive = false } = {}) eventCache[key](); } - const eventName = key.substring(2).toLowerCase(); + const eventName = dasherize(`${keyFrom2}${key.substring(3)}`); eventCache[key] = on(wcNode, eventName, props[key], { passive }); } else { From 734998ab47fd7820d0381d943ba4009847c3d0f9 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:40:19 +0200 Subject: [PATCH 50/84] fixed layout for looong text --- src/demos/todomvc/index.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index 4c9f88e2e9..f416444582 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -113,7 +113,7 @@ .m-todo__label { margin: 0; - flex-grow: 1; + flex: 1; cursor: pointer; white-space: pre-line; From 7e3ff8aaf0771c8b5be48750fe656ed659f51bc8 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:42:28 +0200 Subject: [PATCH 51/84] fixed misisng gap --- src/demos/todomvc/index.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/todomvc/index.scss b/src/demos/todomvc/index.scss index f416444582..3973be9644 100644 --- a/src/demos/todomvc/index.scss +++ b/src/demos/todomvc/index.scss @@ -111,7 +111,7 @@ } .m-todo__label { - margin: 0; + margin: 0 20px 0 0; flex: 1; From 3c06b6000b5f155a1e2aebfc8cb55fc29275e3e7 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:55:30 +0200 Subject: [PATCH 52/84] enabled handling of footer links --- src/components/m-footer-links/_template.js | 4 ++-- src/components/m-footer-links/js/footer-links.js | 7 +++++-- src/demos/todomvc/todo-footer.jsx | 6 ++---- src/demos/todomvc/todos.jsx | 11 ++++++++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/components/m-footer-links/_template.js b/src/components/m-footer-links/_template.js index c74d4c97ad..c8c3a5f78c 100644 --- a/src/components/m-footer-links/_template.js +++ b/src/components/m-footer-links/_template.js @@ -8,9 +8,9 @@ export default function ({ title, items }) { ${title}${raw(arrowIcon)} diff --git a/src/components/m-footer-links/js/footer-links.js b/src/components/m-footer-links/js/footer-links.js index 93d4233910..978d377ccd 100644 --- a/src/components/m-footer-links/js/footer-links.js +++ b/src/components/m-footer-links/js/footer-links.js @@ -1,5 +1,6 @@ import on from '../../../js/on'; import fire from '../../../js/fire'; +import getAttribute from '../../../js/get-attribute'; import { subscribe } from '../../../js/pubsub'; import DropDown from '../../m-dropdown/js/drop-down'; @@ -51,8 +52,10 @@ export default class FooterLinks { } } - handleClick(event) { - const cancelled = fire(this.wcNode, 'axa-click', {}, { bubbles: true, cancelable: true, composed: true }); + handleClick(event, delegateTarget) { + const index = getAttribute(delegateTarget, 'index'); + const { wcNode: { items } } = this; + const cancelled = fire(this.wcNode, 'axa-click', items[index], { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/demos/todomvc/todo-footer.jsx b/src/demos/todomvc/todo-footer.jsx index 632e40d799..fcb5ee7205 100644 --- a/src/demos/todomvc/todo-footer.jsx +++ b/src/demos/todomvc/todo-footer.jsx @@ -31,6 +31,7 @@ const TodoFooter = ({ completedCount, onClearCompleted, nowShowing, + onNowShowing, }) => { const activeTodoWord = pluralize(count, 'item'); const selected = item => ({ @@ -42,10 +43,7 @@ const TodoFooter = ({ return ( - { - event.preventDefault(); - console.log(`footer link ${event.type}`); - }} /> + diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index c5a54be546..a50d08d5ba 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -18,6 +18,7 @@ class Todos extends Component { this.clearCompleted = this.clearCompleted.bind(this); this.handleNewTodoKeyDown = this.handleNewTodoKeyDown.bind(this); this.handleChange = this.handleChange.bind(this); + this.nowShowing = this.nowShowing.bind(this); this.state = { nowShowing: ALL_TODOS, @@ -76,6 +77,14 @@ class Todos extends Component { this.props.model.clearCompleted(); } + nowShowing(event) { + const { detail: { state } } = event; + + this.setState({ + nowShowing: state, + }); + } + render() { const { props: { model: { todos } } } = this; const { state } = this; @@ -110,7 +119,7 @@ class Todos extends Component { /> {(activeTodoCount || completedCount) ? ( - + ) : null} ); From f5dec3c9bec8280738914c733991bac2854821c5 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:56:47 +0200 Subject: [PATCH 53/84] prevent default action --- src/demos/todomvc/todos.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/demos/todomvc/todos.jsx b/src/demos/todomvc/todos.jsx index a50d08d5ba..5aeab9402a 100644 --- a/src/demos/todomvc/todos.jsx +++ b/src/demos/todomvc/todos.jsx @@ -80,6 +80,8 @@ class Todos extends Component { nowShowing(event) { const { detail: { state } } = event; + event.preventDefault(); + this.setState({ nowShowing: state, }); From ef7aa4339c7bf247907f4cccebd8267d7787a0f2 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 19:59:36 +0200 Subject: [PATCH 54/84] updated docs --- CONTRIBUTING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91abb879b3..8cd6eb6066 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -234,10 +234,10 @@ To turn any custom element into a working React Component, you just need to foll 3. `import` any web components you need 4. bind `withReact` to you `import`ed React version 5. wrap all your needed web components - - and may pass optional options + - and may pass optional options for type of component or event init options 6. use them like regular React components in your app - **Note:** events work similiar to React's standard events, but each web components could trigger custom events. Make sure to check them out at the web-components documentation itself! + **Note:** events work similar to React's standard events, but each web components could trigger custom events. Make sure to check them out at the web-components documentation itself! ```js // import your dependencies - 1, 2, and 3 @@ -252,11 +252,14 @@ const withReactBound = withReact(React) // and optionally pass options const AXAButtonReact = withReactBound(AXAButton, { pure: true, + // event init options are also supported + passive: false, }); // 6. use them in your app like regular React components +// note the custom event axa-click - camelcased and on-prefixed in React const MyApp = ({ color, onClick }) => ( - Hello World + Hello World ); ``` From 9b9568f3aab5e79c9b25864e45cd66053963ea60 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 20:04:54 +0200 Subject: [PATCH 55/84] added todo --- src/components/m-footer-links/js/footer-links.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/m-footer-links/js/footer-links.js b/src/components/m-footer-links/js/footer-links.js index 978d377ccd..615b6adb98 100644 --- a/src/components/m-footer-links/js/footer-links.js +++ b/src/components/m-footer-links/js/footer-links.js @@ -53,6 +53,7 @@ export default class FooterLinks { } handleClick(event, delegateTarget) { + // @todo: would be cool to be able to use props here, cause now it needs JSON.parse... const index = getAttribute(delegateTarget, 'index'); const { wcNode: { items } } = this; const cancelled = fire(this.wcNode, 'axa-click', items[index], { bubbles: true, cancelable: true, composed: true }); From 5e665223edc1f08b1273f3115e2b6164f180eaa5 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Thu, 29 Mar 2018 20:27:37 +0200 Subject: [PATCH 56/84] made the logo clickable --- src/components/m-button/js/button.js | 2 +- src/components/m-header-logo/_template.js | 2 +- src/components/m-header-logo/index.js | 16 ++++++ .../m-header-logo/js/header-logo.js | 57 +++++++++++++++++++ src/demos/todomvc/todo-header.jsx | 3 +- src/demos/todomvc/todos.jsx | 12 +++- 6 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/components/m-header-logo/js/header-logo.js diff --git a/src/components/m-button/js/button.js b/src/components/m-button/js/button.js index 810be08419..adc987c8cb 100644 --- a/src/components/m-button/js/button.js +++ b/src/components/m-button/js/button.js @@ -40,7 +40,7 @@ class Button { } handleClick(event) { - const cancelled = fire(this.wcNode, 'axa-click', {}, { bubbles: true, cancelable: true, composed: true }); + const cancelled = fire(this.wcNode, 'axa-click', null, { bubbles: true, cancelable: true, composed: true }); if (!cancelled) { event.preventDefault(); diff --git a/src/components/m-header-logo/_template.js b/src/components/m-header-logo/_template.js index b18f8a8f3e..c76b47cc19 100644 --- a/src/components/m-header-logo/_template.js +++ b/src/components/m-header-logo/_template.js @@ -2,7 +2,7 @@ import html from 'nanohtml'; import raw from 'nanohtml/raw'; export default ({ src, alt = 'AXA Logo', href = '#' } = {}) => html` - + ${src ? html` ${alt} ` : raw('')} diff --git a/src/components/m-header-logo/index.js b/src/components/m-header-logo/index.js index 2c7ecf7792..f01967410a 100644 --- a/src/components/m-header-logo/index.js +++ b/src/components/m-header-logo/index.js @@ -4,6 +4,7 @@ import styles from './index.scss'; // import the template used for this component import template from './_template'; import wcdomready from '../../js/wcdomready'; +import HeaderLogo from './js/header-logo'; class AXAHeaderLogo extends BaseComponentGlobal { static get observedAttributes() { return ['alt', 'href', 'src']; } @@ -17,6 +18,21 @@ class AXAHeaderLogo extends BaseComponentGlobal { this.className = `${this.initialClassName} m-header-logo`; } + + didRenderCallback() { + if (this.logo) { + this.logo.destroy(); + } + + this.logo = new HeaderLogo(this); + } + + disconnectedCallback() { + if (this.logo) { + this.logo.destroy(); + delete this.logo; + } + } } wcdomready(() => { diff --git a/src/components/m-header-logo/js/header-logo.js b/src/components/m-header-logo/js/header-logo.js new file mode 100644 index 0000000000..8f6044efb6 --- /dev/null +++ b/src/components/m-header-logo/js/header-logo.js @@ -0,0 +1,57 @@ +import on from '../../../js/on'; +import fire from '../../../js/fire'; + +class HeaderLogo { + static DEFAULTS = { + link: '.js-header-logo__link', + } + + constructor(wcNode, options = {}) { + this.wcNode = wcNode; + this.options = { + ...HeaderLogo.DEFAULTS, + ...options, + }; + + this.handleClick = this.handleClick.bind(this); + + this.init(); + } + + init() { + this.link = this.wcNode.querySelector(this.options.link); + + this.on(); + } + + on() { + this.off(); + + this.unClick = on(this.link, 'click', this.handleClick, { passive: false }); + } + + handleClick(event) { + const cancelled = fire(this.wcNode, 'axa-click', null, { bubbles: true, cancelable: true, composed: true }); + + if (!cancelled) { + event.preventDefault(); + } + } + + off() { + if (this.unClick) { + this.unClick(); + } + } + + destroy() { + this.off(); + + delete this.link; + delete this.wcNode; + delete this.options; + delete this.handleClick; + } +} + +export default HeaderLogo; diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 2fa340f6f9..4654cd6532 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -70,10 +70,11 @@ const TodoHeader = ({ newTodo, handleNewTodoKeyDown, handleChange, + toggleAll, }) => ( - +

    Todos

    - + Date: Fri, 30 Mar 2018 07:57:39 +0200 Subject: [PATCH 57/84] only re-render components, if their props have changed --- src/js/abstract/base-component.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index 51a42a7e94..1ef74da94b 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -86,7 +86,15 @@ export default class BaseComponent extends HTMLElement { return this[`_${attr}`]; }, set(value) { - this[`_${attr}`] = value; + const name = `_${attr}`; + + // only update the value if it has actually changed + // and only re-render if it has changed + if (this[name] === value) { + return; + } + + this[name] = value; this._props[key] = value; @@ -176,6 +184,12 @@ export default class BaseComponent extends HTMLElement { lifecycleLogger(this.logLifecycle)(`+++ attributeChangedCallback -> ${this.nodeName}#${this._id} | ${name} from ${oldValue} to ${newValue}\n`); } + // only update the value if it has actually changed + // and only re-render if it has changed + if (newValue === oldValue) { + return; + } + const key = camelize(name); this[key] = toProp(newValue); From 2d7ab94749734492a28996d4aa7136b8a1e07103 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 08:09:23 +0200 Subject: [PATCH 58/84] added shouldComponentUpdate to base component's re-render --- src/js/abstract/base-component.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index 1ef74da94b..942fd9bfe6 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -90,7 +90,7 @@ export default class BaseComponent extends HTMLElement { // only update the value if it has actually changed // and only re-render if it has changed - if (this[name] === value) { + if (!this.shouldComponentUpdate(this[name], value)) { return; } @@ -186,7 +186,7 @@ export default class BaseComponent extends HTMLElement { // only update the value if it has actually changed // and only re-render if it has changed - if (newValue === oldValue) { + if (!this.shouldComponentUpdate(newValue, oldValue)) { return; } @@ -195,6 +195,18 @@ export default class BaseComponent extends HTMLElement { this[key] = toProp(newValue); } + /** + * Check if a re-render is really necessary. + * Basic check does a shallow comparison. + * + * @param {*} newValue - the new value of an attribute. + * @param {*} oldValue - the existing value of an attribute. + * @returns {Boolean} - Returns `true` if attributes have changed, else `false`. + */ + shouldComponentUpdate(newValue, oldValue) { + return newValue !== oldValue; + } + /** * disconnectedCallback - description * From d7c71d3eadd2894c0b3c4331b36be0e8b61713e2 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 08:18:35 +0200 Subject: [PATCH 59/84] added docs for should component update --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cd6eb6066..2fb87c330d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,6 +205,12 @@ example.exampleMessage = 'hello world'; **Note:** Be careful of choosing your attribute names, never overwrite existing standard attributes without good reason! +#### `shouldComponentUpdate(newValue, oldValue)` + +`shouldComponentUpdate()` is invoked upon `attributeChangedCallback()` or Property `setter()` invocation to determine if rendering is necessary when new props are being received - it returns `true` if re-rendering is desireable, else `false`. + +**Important:** This does only a shallow comparison, if your need deal with more complex data, like objects or arrays either stick to immutable data structures or override this method to implement your own test. + #### `willRenderCallback(initial)` Invoked before the custom element's [flattened DOM](#flattened-dom) will be rendered. From 03b52312fb95e4ea688d08c934f100c39c4b3a2c Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 08:25:47 +0200 Subject: [PATCH 60/84] removed the input hack --- src/demos/todomvc/todo-header.jsx | 59 +------------------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/src/demos/todomvc/todo-header.jsx b/src/demos/todomvc/todo-header.jsx index 4654cd6532..0539da4d88 100644 --- a/src/demos/todomvc/todo-header.jsx +++ b/src/demos/todomvc/todo-header.jsx @@ -9,63 +9,6 @@ const AXAHeaderReact = withReactBound(AXAHeader); const AXAHeaderMainReact = withReactBound(AXAHeaderMain); const AXAHeaderLogoReact = withReactBound(AXAHeaderLogo); -// @todo: super hacky way to keep the input's focus within custom elements -class Input extends Component { - constructor(props, context) { - super(props, context); - - this.onBlur = this.onBlur.bind(this); - this.onChange = this.onChange.bind(this); - this.handleRef = this.handleRef.bind(this); - - this.state = { - reFocus: false, - }; - } - - onBlur() { - const { state: { reFocus } } = this; - - if (reFocus === true) { - this.setState({ - reFocus: false, - }); - - setTimeout(() => { - this.input.focus(); - }, 10); - } - } - - onChange(event) { - const { props: { onChange } } = this; - - this.setState({ - reFocus: true, - }); - - onChange(event); - } - - handleRef(input) { - this.input = input; - } - - render() { - const { handleRef } = this; - const { onChange, ...props } = this.props; - - return ( - - ); - } -} - const TodoHeader = ({ newTodo, handleNewTodoKeyDown, @@ -77,7 +20,7 @@ const TodoHeader = ({

    Todos

    - Date: Fri, 30 Mar 2018 08:57:43 +0200 Subject: [PATCH 61/84] renamed to shouldUpdateCallback --- CONTRIBUTING.md | 6 +++--- src/js/abstract/base-component.js | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fb87c330d..39ceef6bd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,11 +205,11 @@ example.exampleMessage = 'hello world'; **Note:** Be careful of choosing your attribute names, never overwrite existing standard attributes without good reason! -#### `shouldComponentUpdate(newValue, oldValue)` +#### `shouldUpdateCallback(newValue, oldValue)` -`shouldComponentUpdate()` is invoked upon `attributeChangedCallback()` or Property `setter()` invocation to determine if rendering is necessary when new props are being received - it returns `true` if re-rendering is desireable, else `false`. +`shouldUpdateCallback()` is invoked upon `attributeChangedCallback()` or Property `setter()` invocation to determine if rendering is necessary when new props are being received - it returns `true` if re-rendering is desireable, else `false`. -**Important:** This does only a shallow comparison, if your need deal with more complex data, like objects or arrays either stick to immutable data structures or override this method to implement your own test. +**Important:** This does only a shallow comparison, if you need to deal with more complex data, like objects or arrays either stick to immutable data structures or override this method to implement your own test. #### `willRenderCallback(initial)` diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index 942fd9bfe6..1730b4e1f1 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -90,7 +90,7 @@ export default class BaseComponent extends HTMLElement { // only update the value if it has actually changed // and only re-render if it has changed - if (!this.shouldComponentUpdate(this[name], value)) { + if (!this.shouldUpdateCallback(this[name], value)) { return; } @@ -186,7 +186,7 @@ export default class BaseComponent extends HTMLElement { // only update the value if it has actually changed // and only re-render if it has changed - if (!this.shouldComponentUpdate(newValue, oldValue)) { + if (!this.shouldUpdateCallback(newValue, oldValue)) { return; } @@ -203,7 +203,8 @@ export default class BaseComponent extends HTMLElement { * @param {*} oldValue - the existing value of an attribute. * @returns {Boolean} - Returns `true` if attributes have changed, else `false`. */ - shouldComponentUpdate(newValue, oldValue) { + // eslint-disable-next-line class-methods-use-this + shouldUpdateCallback(newValue, oldValue) { return newValue !== oldValue; } From 2397c93a668431b634166bf4625cb4462d009272 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 13:15:55 +0200 Subject: [PATCH 62/84] adding array partition reducer --- src/js/array-partition.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/js/array-partition.js diff --git a/src/js/array-partition.js b/src/js/array-partition.js new file mode 100644 index 0000000000..f514344025 --- /dev/null +++ b/src/js/array-partition.js @@ -0,0 +1,11 @@ +function partition(filter) { + return function partitionReducer(partitions, item) { + const index = filter(item) ? 0 : 1; + + partitions[index].push(item); + + return partitions; + }; +} + +export default partition; From c770d6a1f6f44d216070b2c9ff635edea2b95736 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 13:16:24 +0200 Subject: [PATCH 63/84] start adding batch props for integrations --- src/js/abstract/base-component.js | 67 +++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index 1730b4e1f1..7bf79b8467 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -5,6 +5,8 @@ import toProp from '../to-prop'; import { publish, subscribe } from '../pubsub'; import debounce from '../debounce'; import camelize from '../camelize'; +import dasherize from '../dasherize'; +import partition from '../array-partition'; import maybe from '../maybe'; import PropertyExistsException from './property-exists-exception'; @@ -83,10 +85,10 @@ export default class BaseComponent extends HTMLElement { // @todo: may we should allow deletion by setting configurable: true Object.defineProperty(this, key, { get() { - return this[`_${attr}`]; + return this[`_${key}`]; }, set(value) { - const name = `_${attr}`; + const name = `_${key}`; // only update the value if it has actually changed // and only re-render if it has changed @@ -104,7 +106,7 @@ export default class BaseComponent extends HTMLElement { if (this._isConnected && this._hasRendered) { if (ENV !== PROD) { - lifecycleLogger(this.logLifecycle)(`\n---> setter for ${key} by _${attr}`); + lifecycleLogger(this.logLifecycle)(`\n---> setter for ${key} by _${key}`); } this.reRender(); @@ -195,6 +197,65 @@ export default class BaseComponent extends HTMLElement { this[key] = toProp(newValue); } + /** + * A fast and simpler way to update multiple props in one go. + * Especially usefull for integrations and to prevent multiple re-renders. + * + * @param {{}} props - DOM properties to be updated. + */ + batchProps(props) { + const { constructor: { observedAttributes } } = this; + const propsKeys = Object.keys(props); + const filter = key => observedAttributes.indexOf(dasherize(key)) > -1; + const [observedProps, customProps] = propsKeys.reduce(partition(filter), [[], []]); + let shouldUpdate = false; + + observedProps.forEach((key) => { + const hasKey = key in this; + + if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { + throw new PropertyExistsException(key, this); + } + + const name = `_${key}`; + const value = props[key]; + const oldValue = this[name]; + + if (!shouldUpdate && !this.shouldUpdateCallback(value, oldValue)) { + return; + } + + shouldUpdate = true; + + this[name] = value; + this._props[key] = value; + + if (hasKey) { + super[key] = value; + } + }); + + customProps.forEach((key) => { + const hasKey = key in this; + + if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { + throw new PropertyExistsException(key, this); + } + + const value = props[key]; + + this[key] = value; + }); + + if (shouldUpdate && this._isConnected && this._hasRendered) { + if (ENV !== PROD) { + lifecycleLogger(this.logLifecycle)(`\n---> batchProps for ${propsKeys.join(', ')}`); + } + + this.render(); + } + } + /** * Check if a re-render is really necessary. * Basic check does a shallow comparison. From a9422a33f2bd3acd8f0319448b85fa426387f005 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 19:37:43 +0200 Subject: [PATCH 64/84] improved FOUC by batch processing of props --- src/js/abstract/base-component.js | 21 +++++------------- src/js/with-react.jsx | 37 ++++++++++++++++++------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index 7bf79b8467..ba9593ab8c 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -6,7 +6,6 @@ import { publish, subscribe } from '../pubsub'; import debounce from '../debounce'; import camelize from '../camelize'; import dasherize from '../dasherize'; -import partition from '../array-partition'; import maybe from '../maybe'; import PropertyExistsException from './property-exists-exception'; @@ -64,6 +63,7 @@ export default class BaseComponent extends HTMLElement { this._initialise(styles, template); this._id = getId(this.nodeName); this._props = {}; + this._hasKeys = {}; this.reRender = debounce(() => this.render(), 50); const { constructor: { observedAttributes } } = this; @@ -82,6 +82,8 @@ export default class BaseComponent extends HTMLElement { throw new PropertyExistsException(key, this); } + this._hasKeys[key] = hasKey; + // @todo: may we should allow deletion by setting configurable: true Object.defineProperty(this, key, { get() { @@ -207,11 +209,10 @@ export default class BaseComponent extends HTMLElement { const { constructor: { observedAttributes } } = this; const propsKeys = Object.keys(props); const filter = key => observedAttributes.indexOf(dasherize(key)) > -1; - const [observedProps, customProps] = propsKeys.reduce(partition(filter), [[], []]); let shouldUpdate = false; - observedProps.forEach((key) => { - const hasKey = key in this; + propsKeys.filter(filter).forEach((key) => { + const hasKey = this._hasKeys[key]; if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { throw new PropertyExistsException(key, this); @@ -235,18 +236,6 @@ export default class BaseComponent extends HTMLElement { } }); - customProps.forEach((key) => { - const hasKey = key in this; - - if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { - throw new PropertyExistsException(key, this); - } - - const value = props[key]; - - this[key] = value; - }); - if (shouldUpdate && this._isConnected && this._hasRendered) { if (ENV !== PROD) { lifecycleLogger(this.logLifecycle)(`\n---> batchProps for ${propsKeys.join(', ')}`); diff --git a/src/js/with-react.jsx b/src/js/with-react.jsx index 4dc81ec3b8..ce0e253f86 100644 --- a/src/js/with-react.jsx +++ b/src/js/with-react.jsx @@ -1,10 +1,17 @@ import dasherize from './dasherize'; +import partition from './array-partition'; import on from './on'; const PROP_BLACKLIST = [ 'children', // children are never passed as props, instead as real DOM children 'style', // @todo: discuss if we need style, cause we normally use BEM ]; +const blackListFilter = key => PROP_BLACKLIST.indexOf(key) === -1; +const isEventFilter = (key) => { + const keyFrom2 = key.charAt(2); + + return key.indexOf('on') === 0 && keyFrom2 === keyFrom2.toUpperCase(); +}; /** * Provides a partially applied function which let's you wrap any WebComponent with React. @@ -53,28 +60,26 @@ const withReact = React => (WebComponent, { pure = true, passive = false } = {}) componentWillReceiveProps(props) { const { wcNode, _eventCache: eventCache } = this; + const propsKeys = Object.keys(props); + const [eventKeys, dataKeys] = propsKeys.reduce(partition(isEventFilter), [[], []]); - Object.keys(props).forEach((key) => { - if (PROP_BLACKLIST.indexOf(key) !== -1) { - return; + eventKeys.forEach((key) => { + if (eventCache[key]) { + eventCache[key](); } - const keyFrom2 = key.charAt(2); + const eventName = dasherize(key.substring(2)); - // bind event handlers - if (key.indexOf('on') === 0 && keyFrom2 === keyFrom2.toUpperCase()) { - if (eventCache[key]) { - eventCache[key](); - } + eventCache[key] = on(wcNode, eventName, props[key], { passive }); + }); - const eventName = dasherize(`${keyFrom2}${key.substring(3)}`); + const dataProps = dataKeys.filter(blackListFilter) + .reduce((data, key) => ({ + ...data, + [key]: props[key], + }), {}); - eventCache[key] = on(wcNode, eventName, props[key], { passive }); - } else { - // set properties by DOM property API's - not HTML setAttribute -> first class props - wcNode[key] = props[key]; - } - }); + wcNode.batchProps(dataProps); } componentWillUnmount() { From 804617a4b3a3084f2bdac8f28be44468fe06d746 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 19:55:58 +0200 Subject: [PATCH 65/84] added docs for batch props --- CONTRIBUTING.md | 5 +++++ src/js/abstract/base-component.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39ceef6bd0..ad4099a36f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,6 +205,11 @@ example.exampleMessage = 'hello world'; **Note:** Be careful of choosing your attribute names, never overwrite existing standard attributes without good reason! +#### `batchProps(props)` + +A fast and simpler way to update multiple props in one go. +Especially useful for integrations and to prevent multiple or delayed re-renders. + #### `shouldUpdateCallback(newValue, oldValue)` `shouldUpdateCallback()` is invoked upon `attributeChangedCallback()` or Property `setter()` invocation to determine if rendering is necessary when new props are being received - it returns `true` if re-rendering is desireable, else `false`. diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index ba9593ab8c..e0ff65bc2e 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -201,7 +201,7 @@ export default class BaseComponent extends HTMLElement { /** * A fast and simpler way to update multiple props in one go. - * Especially usefull for integrations and to prevent multiple re-renders. + * Especially useful for integrations and to prevent multiple re-renders. * * @param {{}} props - DOM properties to be updated. */ From 373c1f1005bf9f309998a52c8eec5e1a8eb5c04e Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Fri, 30 Mar 2018 20:01:39 +0200 Subject: [PATCH 66/84] fixed initial attribute fetching --- src/js/abstract/base-component.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index e0ff65bc2e..a0f4dad866 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -99,7 +99,6 @@ export default class BaseComponent extends HTMLElement { } this[name] = value; - this._props[key] = value; if (hasKey) { @@ -161,8 +160,15 @@ export default class BaseComponent extends HTMLElement { if (this.hasAttribute(attr)) { const value = getAttribute(this, attr); + const hasKey = this._hasKeys[key]; + const name = `_${key}`; + + this[name] = value; + this._props[key] = value; - this[key] = value; + if (hasKey) { + super[key] = value; + } } }); From da1db52d8a27bb9a8283e08f63954330b091f664 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Sat, 31 Mar 2018 08:52:11 +0200 Subject: [PATCH 67/84] added updateProp for batching --- src/js/abstract/base-component.js | 54 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index a0f4dad866..a8f71b8da8 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -215,40 +215,46 @@ export default class BaseComponent extends HTMLElement { const { constructor: { observedAttributes } } = this; const propsKeys = Object.keys(props); const filter = key => observedAttributes.indexOf(dasherize(key)) > -1; - let shouldUpdate = false; + const { shouldUpdate } = propsKeys.filter(filter).reduce(this.updateProp, { props, shouldUpdate: false }); - propsKeys.filter(filter).forEach((key) => { - const hasKey = this._hasKeys[key]; - - if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { - throw new PropertyExistsException(key, this); + if (shouldUpdate && this._isConnected && this._hasRendered) { + if (ENV !== PROD) { + lifecycleLogger(this.logLifecycle)(`\n---> batchProps for ${propsKeys.join(', ')}`); } - const name = `_${key}`; - const value = props[key]; - const oldValue = this[name]; + this.render(); + } + } - if (!shouldUpdate && !this.shouldUpdateCallback(value, oldValue)) { - return; - } + updateProp({ props, shouldUpdate }, key) { + const hasKey = this._hasKeys[key]; - shouldUpdate = true; + if (PROPERTY_WHITELIST.indexOf(key) === -1 && hasKey) { + throw new PropertyExistsException(key, this); + } - this[name] = value; - this._props[key] = value; + const name = `_${key}`; + const value = props[key]; + const oldValue = this[name]; - if (hasKey) { - super[key] = value; - } - }); + if (!shouldUpdate && !this.shouldUpdateCallback(value, oldValue)) { + return { + props, + shouldUpdate: false, + }; + } - if (shouldUpdate && this._isConnected && this._hasRendered) { - if (ENV !== PROD) { - lifecycleLogger(this.logLifecycle)(`\n---> batchProps for ${propsKeys.join(', ')}`); - } + this[name] = value; + this._props[key] = value; - this.render(); + if (hasKey) { + super[key] = value; } + + return { + props, + shouldUpdate: true, + }; } /** From ef3a5d9a7e00f00908e9cc0dc019b18fc4d0783c Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Sat, 31 Mar 2018 08:52:29 +0200 Subject: [PATCH 68/84] add some props changes --- src/demos/todomvc/todo-item.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demos/todomvc/todo-item.jsx b/src/demos/todomvc/todo-item.jsx index d44532471e..d153f99f8d 100644 --- a/src/demos/todomvc/todo-item.jsx +++ b/src/demos/todomvc/todo-item.jsx @@ -129,7 +129,7 @@ class TodoItem extends Component { {title} - + From 55a307d774f466c7d37403a320e14ba092f21077 Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Sat, 31 Mar 2018 08:55:21 +0200 Subject: [PATCH 69/84] added doclet --- src/js/abstract/base-component.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/js/abstract/base-component.js b/src/js/abstract/base-component.js index a8f71b8da8..f10844e984 100644 --- a/src/js/abstract/base-component.js +++ b/src/js/abstract/base-component.js @@ -226,6 +226,14 @@ export default class BaseComponent extends HTMLElement { } } + /** + * Props reducer for batch processing. + * + * @param {{}} props - The properties to be batch processed. + * @param {Boolean} shouldUpdate - Is re-render necessary? + * @param {String} key - the current property's key. + * @returns {{props: {}, shouldUpdate: boolean}} - For the next accumulator iteration. + */ updateProp({ props, shouldUpdate }, key) { const hasKey = this._hasKeys[key]; From 058151a7d86d44bd15d36240c0ebf5d4ec6add8c Mon Sep 17 00:00:00 2001 From: Andreas Deuschlinger Date: Tue, 3 Apr 2018 09:15:38 +0200 Subject: [PATCH 70/84] added active item to footer links --- src/components/m-footer-links/_template.js | 4 ++-- src/components/m-footer-links/index.scss | 1 + src/demos/todomvc/todo-footer.jsx | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/m-footer-links/_template.js b/src/components/m-footer-links/_template.js index c8c3a5f78c..26a506b174 100644 --- a/src/components/m-footer-links/_template.js +++ b/src/components/m-footer-links/_template.js @@ -8,8 +8,8 @@ export default function ({ title, items }) { ${title}${raw(arrowIcon)}