| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 | const camelizeRE = /-(\w)/g;const camelize = str => {  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')};const hyphenateRE = /\B([A-Z])/g;const hyphenate = str => {  return str.replace(hyphenateRE, '-$1').toLowerCase()};function getInitialProps (propsList) {  const res = {};  propsList.forEach(key => {    res[key] = undefined;  });  return res}function injectHook (options, key, hook) {  options[key] = [].concat(options[key] || []);  options[key].unshift(hook);}function callHooks (vm, hook) {  if (vm) {    const hooks = vm.$options[hook] || [];    hooks.forEach(hook => {      hook.call(vm);    });  }}function createCustomEvent (name, args) {  return new CustomEvent(name, {    bubbles: false,    cancelable: false,    detail: args  })}const isBoolean = val => /function Boolean/.test(String(val));const isNumber = val => /function Number/.test(String(val));function convertAttributeValue (value, name, { type } = {}) {  if (isBoolean(type)) {    if (value === 'true' || value === 'false') {      return value === 'true'    }    if (value === '' || value === name || value != null) {      return true    }    return value  } else if (isNumber(type)) {    const parsed = parseFloat(value, 10);    return isNaN(parsed) ? value : parsed  } else {    return value  }}function toVNodes (h, children) {  const res = [];  for (let i = 0, l = children.length; i < l; i++) {    res.push(toVNode(h, children[i]));  }  return res}function toVNode (h, node) {  if (node.nodeType === 3) {    return node.data.trim() ? node.data : null  } else if (node.nodeType === 1) {    const data = {      attrs: getAttributes(node),      domProps: {        innerHTML: node.innerHTML      }    };    if (data.attrs.slot) {      data.slot = data.attrs.slot;      delete data.attrs.slot;    }    return h(node.tagName, data)  } else {    return null  }}function getAttributes (node) {  const res = {};  for (let i = 0, l = node.attributes.length; i < l; i++) {    const attr = node.attributes[i];    res[attr.nodeName] = attr.nodeValue;  }  return res}function wrap (Vue, Component) {  const isAsync = typeof Component === 'function' && !Component.cid;  let isInitialized = false;  let hyphenatedPropsList;  let camelizedPropsList;  let camelizedPropsMap;  function initialize (Component) {    if (isInitialized) return    const options = typeof Component === 'function'      ? Component.options      : Component;    // extract props info    const propsList = Array.isArray(options.props)      ? options.props      : Object.keys(options.props || {});    hyphenatedPropsList = propsList.map(hyphenate);    camelizedPropsList = propsList.map(camelize);    const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {};    camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => {      map[key] = originalPropsAsObject[propsList[i]];      return map    }, {});    // proxy $emit to native DOM events    injectHook(options, 'beforeCreate', function () {      const emit = this.$emit;      this.$emit = (name, ...args) => {        this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args));        return emit.call(this, name, ...args)      };    });    injectHook(options, 'created', function () {      // sync default props values to wrapper on created      camelizedPropsList.forEach(key => {        this.$root.props[key] = this[key];      });    });    // proxy props as Element properties    camelizedPropsList.forEach(key => {      Object.defineProperty(CustomElement.prototype, key, {        get () {          return this._wrapper.props[key]        },        set (newVal) {          this._wrapper.props[key] = newVal;        },        enumerable: false,        configurable: true      });    });    isInitialized = true;  }  function syncAttribute (el, key) {    const camelized = camelize(key);    const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined;    el._wrapper.props[camelized] = convertAttributeValue(      value,      key,      camelizedPropsMap[camelized]    );  }  class CustomElement extends HTMLElement {    constructor () {      const self = super();      self.attachShadow({ mode: 'open' });      const wrapper = self._wrapper = new Vue({        name: 'shadow-root',        customElement: self,        shadowRoot: self.shadowRoot,        data () {          return {            props: {},            slotChildren: []          }        },        render (h) {          return h(Component, {            ref: 'inner',            props: this.props          }, this.slotChildren)        }      });      // Use MutationObserver to react to future attribute & slot content change      const observer = new MutationObserver(mutations => {        let hasChildrenChange = false;        for (let i = 0; i < mutations.length; i++) {          const m = mutations[i];          if (isInitialized && m.type === 'attributes' && m.target === self) {            syncAttribute(self, m.attributeName);          } else {            hasChildrenChange = true;          }        }        if (hasChildrenChange) {          wrapper.slotChildren = Object.freeze(toVNodes(            wrapper.$createElement,            self.childNodes          ));        }      });      observer.observe(self, {        childList: true,        subtree: true,        characterData: true,        attributes: true      });    }    get vueComponent () {      return this._wrapper.$refs.inner    }    connectedCallback () {      const wrapper = this._wrapper;      if (!wrapper._isMounted) {        // initialize attributes        const syncInitialAttributes = () => {          wrapper.props = getInitialProps(camelizedPropsList);          hyphenatedPropsList.forEach(key => {            syncAttribute(this, key);          });        };        if (isInitialized) {          syncInitialAttributes();        } else {          // async & unresolved          Component().then(resolved => {            if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') {              resolved = resolved.default;            }            initialize(resolved);            syncInitialAttributes();          });        }        // initialize children        wrapper.slotChildren = Object.freeze(toVNodes(          wrapper.$createElement,          this.childNodes        ));        wrapper.$mount();        this.shadowRoot.appendChild(wrapper.$el);      } else {        callHooks(this.vueComponent, 'activated');      }    }    disconnectedCallback () {      callHooks(this.vueComponent, 'deactivated');    }  }  if (!isAsync) {    initialize(Component);  }  return CustomElement}export default wrap;
 |