UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

517 lines (511 loc) 13.8 kB
/* eslint no-underscore-dangle: "off" */ import { getWindow, getDocument } from 'ssr-window'; import $ from '../../shared/dom7.js'; import $h from './$h.js'; import { id as generateId, merge, eventNameToColonCase, deleteProps } from '../../shared/utils.js'; import vdom from './vdom.js'; import patch from './patch.js'; import $jsx from './$jsx.js'; /* eslint-disable no-shadow */ /* eslint-disable no-return-assign */ /* eslint-disable no-plusplus */ /* eslint-disable no-param-reassign */ /* eslint-disable no-sequences */ const types = [{ name: 'array', init: i => i, type: i => [i].find(Array.isArray), update: (i, o) => [o].filter(Array.isArray).find(() => (i.length = 0, i.push(...o))), insert: function (i, x, o) { if (o === void 0) { o = []; } return i.splice(Math.max(x, 0), 0, ...[o].flat()); }, replace: function (i, x, o) { if (o === void 0) { o = []; } return i.splice(Math.max(x, 0), Math.min(++x, 1), ...[o].flat()); }, append: function (i, o) { if (o === void 0) { o = []; } return i.push(...[o].flat()); }, prepend: function (i, o) { if (o === void 0) { o = []; } return i.unshift(...[o].flat()); }, swap: (i, a, b) => { [i[a], i[b]] = [i[b], i[a]]; }, fromTo: function (i, a, b) { if (b === void 0) { b = a; } return i.splice(Math.max(b, 0), 0, ...i.splice(Math.max(a, 0), 1)); }, remove: function (i, o, a) { if (a === void 0) { a = i.map((_, x) => x); } return [o].flat().filter(i => a.includes(i)).sort((a, b) => b - a).forEach(x => i.splice(x, 1)); }, clear: i => i.length = 0 }, { name: 'object', init: i => i, type: i => [i].filter(i => [i !== null, i !== undefined].every(i => i)).find(i => Object.getPrototypeOf(i) === Object.prototype), update: (i, o) => Object.assign(i, o), insert: () => {}, replace: () => {}, append: () => {}, prepend: () => {}, swap: () => ({}), // N/A fromTo: () => ({}), // N/A remove: (i, o) => [o].flat().forEach(k => delete i[k]), clear: i => Object.keys(i).forEach(k => delete i[k]) }, { name: 'atoms', type: () => true, init: function (i, o) { if (o === void 0) { o = {}; } return Object.defineProperty(o, 'value', { get: () => i, set: v => { // eslint-disable-next-line i = v; } }), o; }, update: function (i, v) { if (v === void 0) { v = i.value; } i.value = v; }, insert: () => ({}), // N/A replace: () => ({}), // N/A append: () => ({}), // N/A prepend: () => ({}), // N/A swap: () => ({}), // N/A fromTo: () => ({}), // N/A remove: () => ({}), // N/A clear: i => { i.value = undefined; } }]; /* eslint-enable no-shadow */ /* eslint-enable no-return-assign */ /* eslint-enable no-plusplus */ /* eslint-enable no-param-reassign */ /* eslint-enable no-sequences */ class Component { constructor(app, component, props, _temp) { if (props === void 0) { props = {}; } let { el, context, children } = _temp === void 0 ? {} : _temp; const document = getDocument(); merge(this, { f7: app, props: props || {}, context: context || {}, id: component.id || generateId(), children: children || [], theme: { ios: app.theme === 'ios', md: app.theme === 'md' }, style: component.style, __updateQueue: [], __eventHandlers: [], __onceEventHandlers: [], __onBeforeMount: [], __onMounted: [], __onBeforeUpdate: [], __onUpdated: [], __onBeforeUnmount: [], __onUnmounted: [] }); const createComponent = () => { return component(this.props, this.getComponentContext(true)); }; const getRenderFuncion = componentResult => new Promise((resolve, reject) => { if (typeof componentResult === 'function') { resolve(componentResult); } else if (componentResult instanceof Promise) { componentResult.then(render => { resolve(render); }).catch(err => { reject(err); }); } else { reject(new Error('Framework7: Component render function is not a "function" type. Didn\'t you forget to "return $render"?')); } }); return new Promise((resolve, reject) => { const componentResult = createComponent(); getRenderFuncion(componentResult).then(render => { this.renderFunction = render; const tree = this.render(); if (el) { this.vnode = vdom(tree, this, true); if (this.style) { this.styleEl = document.createElement('style'); this.styleEl.innerHTML = this.style; } this.el = el; patch(this.el, this.vnode); this.el = this.vnode.elm; this.$el = $(this.el); this.attachEvents(); this.el.f7Component = this; this.mount(); resolve(this); return; } // Make Dom if (tree) { this.vnode = vdom(tree, this, true); this.el = document.createElement(this.vnode.sel || 'div'); patch(this.el, this.vnode); this.$el = $(this.el); } if (this.style) { this.styleEl = document.createElement('style'); this.styleEl.innerHTML = this.style; } this.attachEvents(); if (this.el) { this.el.f7Component = this; } resolve(this); }).catch(err => { reject(err); }); }); } on(eventName, handler) { if (!this.__eventHandlers) return; this.__eventHandlers.push({ eventName, handler }); } once(eventName, handler) { if (!this.__eventHandlers) return; this.__onceEventHandlers.push({ eventName, handler }); } getComponentRef() { const self = this; return initialValue => { let value = initialValue; const obj = {}; Object.defineProperty(obj, 'value', { get() { return value; }, set(v) { value = v; self.update(); } }); return obj; }; } getComponentStore() { const { state, _gettersPlain, dispatch } = this.f7.store; const $store = { state, dispatch }; $store.getters = new Proxy(_gettersPlain, { get: (target, prop) => { const obj = target[prop]; const callback = v => { obj.value = v; this.update(); }; obj.onUpdated(callback); return obj; } }); return $store; } /* eslint-disable no-sequences */ getUseState() { var _this = this; return o => { const obj = [o].reduce(function (t, _i, _x, _a, i) { if (i === void 0) { i = t.init(_i); } return { state: i, update: v => (t.update(i, v), _this.update()), remove: v => (t.remove(i, v), _this.update()), clear: () => (t.clear(i), _this.update()), insert: (x, v) => (t.insert(i, x, v), _this.update()), replace: (x, v) => (t.replace(i, x, v), _this.update()), append: v => (t.append(i, v), _this.update()), prepend: v => (t.prepend(i, v), _this.update()), swap: (a, b) => (t.swap(i, a, b), _this.update()), fromTo: (a, b) => (t.fromTo(i, a, b), _this.update()), method: function (f) { if (f === void 0) { f = () => ({}); } return f(i), _this.update(); }, async: function (f) { if (f === void 0) { f = () => Promise.reject(i); } return f(i).then(() => _this.update()); } }; }, types.find(i => i.type(o))); obj.length = 12; obj[Symbol.iterator] = function Iterate() { const values = Object.values(this); values.splice(values.indexOf(12), 1); let index = 0; return { next() { if (index < values.length) { const val = values[index]; index += 1; return { value: val, done: false }; } return { done: true }; } }; }; return obj; }; } /* eslint-enable no-sequences */ getComponentContext(includeHooks) { const ctx = { $f7route: this.context.f7route, $f7router: this.context.f7router, $h, $, $id: this.id, $f7: this.f7, $f7ready: this.f7ready.bind(this), $theme: this.theme, $tick: this.tick.bind(this), $update: this.update.bind(this), $emit: this.emit.bind(this), $store: this.getComponentStore(), $ref: this.getComponentRef(), $el: {}, $useState: this.getUseState() }; Object.defineProperty(ctx.$el, 'value', { get: () => { return this.$el; } }); if (includeHooks) Object.assign(ctx, { $on: this.on.bind(this), $once: this.once.bind(this), $onBeforeMount: handler => this.__onBeforeMount.push(handler), $onMounted: handler => this.__onMounted.push(handler), $onBeforeUpdate: handler => this.__onBeforeUpdate.push(handler), $onUpdated: handler => this.__onUpdated.push(handler), $onBeforeUnmount: handler => this.__onBeforeUnmount.push(handler), $onUnmounted: handler => this.__onUnmounted.push(handler) }); return ctx; } render() { return this.renderFunction(this.getComponentContext()); } emit(name, data) { if (!this.el) return; this.$el.trigger(name, data); } attachEvents() { const { $el } = this; if (!this.__eventHandlers) return; this.__eventHandlers.forEach(_ref => { let { eventName, handler } = _ref; $el.on(eventNameToColonCase(eventName), handler); }); this.__onceEventHandlers.forEach(_ref2 => { let { eventName, handler } = _ref2; $el.once(eventNameToColonCase(eventName), handler); }); } detachEvents() { const { $el } = this; if (!this.__eventHandlers) return; this.__eventHandlers.forEach(_ref3 => { let { eventName, handler } = _ref3; $el.on(eventNameToColonCase(eventName), handler); }); this.__onceEventHandlers.forEach(_ref4 => { let { eventName, handler } = _ref4; $el.once(eventNameToColonCase(eventName), handler); }); } startUpdateQueue() { const window = getWindow(); if (this.__requestAnimationFrameId) return; const update = () => { this.hook('onBeforeUpdate'); const tree = this.render(); // Make Dom if (tree) { const newVNode = vdom(tree, this, false); this.vnode = patch(this.vnode, newVNode); } }; this.__requestAnimationFrameId = window.requestAnimationFrame(() => { if (this.__updateIsPending) update(); let resolvers = [...this.__updateQueue]; this.__updateQueue = []; this.__updateIsPending = false; window.cancelAnimationFrame(this.__requestAnimationFrameId); delete this.__requestAnimationFrameId; delete this.__updateIsPending; resolvers.forEach(resolver => resolver()); resolvers = []; }); } tick(callback) { return new Promise(resolve => { function resolver() { resolve(); if (callback) callback(); } this.__updateQueue.push(resolver); this.startUpdateQueue(); }); } update(callback) { if (this.__destroyed) return new Promise(() => {}); return new Promise(resolve => { const resolver = () => { resolve(); if (callback) callback(); }; this.__updateIsPending = true; this.__updateQueue.push(resolver); this.startUpdateQueue(); }); } setState(callback) { return this.update(callback); } f7ready(callback) { if (this.f7.initialized) { callback(this.f7); return; } this.f7.once('init', () => { callback(this.f7); }); } mount(mountMethod) { this.hook('onBeforeMount', this.$el); if (this.styleEl) $('head').append(this.styleEl); if (mountMethod) mountMethod(this.el); this.hook('onMounted', this.$el); } destroy() { if (this.__destroyed) return; const window = getWindow(); this.hook('onBeforeUnmount'); if (this.styleEl) $(this.styleEl).remove(); this.detachEvents(); this.hook('onUnmounted'); // Delete component instance if (this.el && this.el.f7Component) { this.el.f7Component = null; delete this.el.f7Component; } // Patch with empty node if (this.vnode) { this.vnode = patch(this.vnode, { sel: this.vnode.sel, data: {} }); } // Clear update queue window.cancelAnimationFrame(this.__requestAnimationFrameId); this.__updateQueue = []; this.__eventHandlers = []; this.__onceEventHandlers = []; this.__onBeforeMount = []; this.__onMounted = []; this.__onBeforeUpdate = []; this.__onUpdated = []; this.__onBeforeUnmount = []; this.__onUnmounted = []; // Delete all props deleteProps(this); this.__destroyed = true; } hook(name) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (this.__destroyed) return; this[`__${name}`].forEach(handler => { handler(...args); }); } } Component.$jsx = $jsx; export default Component;