message: fix: observable properties on web-component
  Properties which are changed on web-components do not cause a
  re-rendering of the component itself.
  Setters on observableAttributes are now used to trigger a
  attributeChangedCallback for re-rendering the components view.
  A requestAnimationFrame then combines several attribute changes to
  cause one re-render only.
diff: @@ -5,7 +5,7 @@ export type CustomElementOptions = {
  observedAttributes?: string[]
  };
  -export const customElement = (componentClass, options: CustomElementOptions = {}) => class extends HTMLElement {
  +export const customElement = (componentClass, options: CustomElementOptions = {}) => class CustomElement extends HTMLElement {
  private _shadowRoot;
  private _component;
  public on;
  @@ -17,18 +17,37 @@ export const customElement = (componentClass, options: CustomElementOptions = {}
  get state() { return this._component.state; }
  static get observedAttributes() {
  - return { ...options }['observedAttributes'];
  + return options.observedAttributes;
  }
  connectedCallback() {
  if (this.isConnected && !this._component) {
  const opts = options || {};
  this._shadowRoot = opts.shadow ? this.attachShadow({ mode: 'open' }) : this;
  - const props = {}
  + const props = {};
  Array.from(this.attributes).forEach(item => props[item.name] = item.value);
  +
  + // add getters/ setters to allow observation on observedAttributes
  + (opts.observedAttributes || []).forEach(name => {
  + props[name] = this[name];
  + Object.defineProperty(this, name, {
  + get(): any {
  + return props[name];
  + },
  + set(this: CustomElement, value: unknown) {
  + // trigger change event
  + this.attributeChangedCallback(name, props[name], value)
  + },
  + configurable: true,
  + enumerable: true
  + });
  + })
  +
  const children = this.children ? Array.from(this.children) : [];
  children.forEach(el => el.parentElement.removeChild(el));
  this._component = new componentClass({ ...props, children }).mount(this._shadowRoot, opts);
  + // attach props to component
  + this._component._props = props;
  if (this._component.mounted) {
  const new_state = this._component.mounted(props, children, this._component.state);
  if (typeof new_state !== 'undefined') this._component.state = new_state;
  @@ -45,8 +64,16 @@ export const customElement = (componentClass, options: CustomElementOptions = {}
  this._component = null;
  }
  - attributeChangedCallback(...args) {
  - this._component?.run('attributeChanged', ...args);
  + attributeChangedCallback(name, oldValue, value) {
  + this._component?.run('attributeChanged', name, oldValue, value);
  + // store the new property/ attribute
  + this._component._props[name] = value
  + if (value !== oldValue && !(options.render === false)) {
  + window.requestAnimationFrame(() => {
  + // re-render state with new combined props on next animation frame
  + this._component?.run('.')
  + })
  + }
  }
  }
  