UNPKG

10.1 kBJavaScriptView Raw
1var Million = (function (exports) {
2 'use strict';
3
4 /**
5 * Field on DOM node that stores the previous VNode
6 */
7 const OLD_VNODE_FIELD = '__m_old_vnode';
8
9 /**
10 * Creates an element from a VNode
11 * @param {VNode} vnode - VNode to convert to HTMLElement or Text
12 * @param {boolean} attachField - Attach OLD_VNODE_FIELD
13 * @returns {HTMLElement|Text}
14 */
15 const createElement = (vnode, attachField = true) => {
16 if (typeof vnode === 'string')
17 return document.createTextNode(vnode);
18 const el = document.createElement(vnode.tag);
19 if (vnode.props) {
20 for (const name of Object.keys(vnode.props)) {
21 el[name] = vnode.props[name];
22 }
23 }
24 if (vnode.children) {
25 for (let i = 0; i < vnode.children.length; ++i) {
26 el.appendChild(createElement(vnode.children[i]));
27 }
28 }
29 if (attachField)
30 el[OLD_VNODE_FIELD] = vnode;
31 return el;
32 };
33
34 /**
35 * Attaches ns props to svg element
36 * @param {VElement} vnode - SVG VNode
37 * @returns {VElement}
38 */
39 const svg = (vnode) => {
40 if (!vnode.props)
41 vnode.props = {};
42 ns(vnode.tag, vnode.props, vnode.children);
43 return vnode;
44 };
45 const ns = (tag, props, children) => {
46 props.ns = 'http://www.w3.org/2000/svg';
47 if (children && tag !== 'foreignObject') {
48 children.forEach((child) => {
49 if (typeof child === 'string')
50 return;
51 if (child.props)
52 ns(child.tag, child.props, child.children);
53 });
54 }
55 };
56 /**
57 * Generates a style string based on a styleObject
58 * @param {object} styleObject - Object with styles
59 * @returns
60 */
61 const style = (styleObject) => {
62 return Object.entries(styleObject)
63 .map((style) => style.join(':'))
64 .join(';');
65 };
66 /**
67 * Generates a className string based on a classObject
68 * @param {object} classObject - Object with classes paired with boolean values to toggle
69 * @returns {string}
70 */
71 const className = (classObject) => {
72 return Object.keys(classObject)
73 .filter((className) => classObject[className])
74 .join(' ');
75 };
76 /**
77 * Helper method for creating a VNode
78 * @param {string} tag - The tagName of an HTMLElement
79 * @param {VProps=} props - DOM properties and attributes of an HTMLElement
80 * @param {VNode[]=} children - Children of an HTMLElement
81 * @param {VFlags=} flag - Compiler flag for VNode
82 * @returns {VElement}
83 */
84 const m = (tag, props, children, flag) => {
85 let key;
86 if (props?.key) {
87 key = props.key;
88 delete props.key;
89 }
90 return {
91 tag,
92 props,
93 children,
94 key,
95 flag,
96 };
97 };
98
99 var VFlags;
100 (function (VFlags) {
101 VFlags[VFlags["NO_CHILDREN"] = 0] = "NO_CHILDREN";
102 VFlags[VFlags["ONLY_TEXT_CHILDREN"] = 1] = "ONLY_TEXT_CHILDREN";
103 VFlags[VFlags["ANY_CHILDREN"] = 2] = "ANY_CHILDREN";
104 })(VFlags || (VFlags = {}));
105 var VActions;
106 (function (VActions) {
107 VActions[VActions["INSERT_TOP"] = 0] = "INSERT_TOP";
108 VActions[VActions["INSERT_BOTTOM"] = 1] = "INSERT_BOTTOM";
109 VActions[VActions["DELETE_TOP"] = 2] = "DELETE_TOP";
110 VActions[VActions["DELETE_BOTTOM"] = 3] = "DELETE_BOTTOM";
111 VActions[VActions["ANY_ACTION"] = 4] = "ANY_ACTION";
112 })(VActions || (VActions = {}));
113
114 /**
115 * Diffs two VNode props and modifies the DOM node based on the necessary changes
116 * @param {HTMLElement} el - Target element to be modified
117 * @param {VProps} oldProps - Old VNode props
118 * @param {VProps} newProps - New VNode props
119 */
120 /* istanbul ignore next */
121 const patchProps = (el, oldProps, newProps) => {
122 const cache = [];
123 for (const oldPropName of Object.keys(oldProps)) {
124 const newPropValue = newProps[oldPropName];
125 if (newPropValue) {
126 el[oldPropName] = newPropValue;
127 cache.push(oldPropName);
128 }
129 else {
130 el.removeAttribute(oldPropName);
131 delete el[oldPropName];
132 }
133 }
134 for (const newPropName of Object.keys(newProps)) {
135 if (!cache.includes(newPropName)) {
136 el[newPropName] = newProps[newPropName];
137 }
138 }
139 };
140 /**
141 * Diffs two VNode children and modifies the DOM node based on the necessary changes
142 * @param {HTMLElement} el - Target element to be modified
143 * @param {VNode[]} oldVNodeChildren - Old VNode children
144 * @param {VNode[]} newVNodeChildren - New VNode children
145 */
146 const patchChildren = (el, oldVNodeChildren, newVNodeChildren) => {
147 const childNodes = [...el.childNodes];
148 /* istanbul ignore next */
149 if (oldVNodeChildren) {
150 for (let i = 0; i < oldVNodeChildren.length; ++i) {
151 patch(childNodes[i], newVNodeChildren[i], oldVNodeChildren[i]);
152 }
153 }
154 /* istanbul ignore next */
155 const slicedNewVNodeChildren = newVNodeChildren.slice(oldVNodeChildren?.length ?? 0);
156 for (let i = 0; i < slicedNewVNodeChildren.length; ++i) {
157 el.appendChild(createElement(slicedNewVNodeChildren[i], false));
158 }
159 };
160 const replaceElementWithVNode = (el, newVNode) => {
161 if (typeof newVNode === 'string') {
162 el.textContent = newVNode;
163 return el;
164 }
165 else {
166 const newElement = createElement(newVNode);
167 el.replaceWith(newElement);
168 return newElement;
169 }
170 };
171 /**
172 * Diffs two VNodes and modifies the DOM node based on the necessary changes
173 * @param {HTMLElement|Text} el - Target element to be modified
174 * @param {VNode} newVNode - New VNode
175 * @param {VNode=} prevVNode - Previous VNode
176 * @returns {HTMLElement|Text}
177 */
178 const patch = (el, newVNode, prevVNode) => {
179 if (!newVNode) {
180 el.remove();
181 return el;
182 }
183 const oldVNode = prevVNode ?? el[OLD_VNODE_FIELD];
184 const hasString = typeof oldVNode === 'string' || typeof newVNode === 'string';
185 if (hasString && oldVNode !== newVNode)
186 return replaceElementWithVNode(el, newVNode);
187 if (!hasString) {
188 if ((!oldVNode?.key && !newVNode?.key) ||
189 oldVNode?.key !== newVNode?.key) {
190 if (oldVNode?.tag !== newVNode?.tag &&
191 !newVNode.children &&
192 !newVNode.props) {
193 // newVNode has no props/children is replaced because it is generally
194 // faster to create a empty HTMLElement rather than iteratively/recursively
195 // remove props/children
196 return replaceElementWithVNode(el, newVNode);
197 }
198 if (oldVNode && !(el instanceof Text)) {
199 patchProps(el, oldVNode.props || {}, newVNode.props || {});
200 switch (newVNode.flag) {
201 case VFlags.NO_CHILDREN: {
202 el.textContent = '';
203 break;
204 }
205 case VFlags.ONLY_TEXT_CHILDREN: {
206 el.textContent = newVNode.children.join('');
207 break;
208 }
209 default: {
210 const [action, numberOfNodes] = newVNode.action ?? [VActions.ANY_ACTION, 0];
211 switch (action) {
212 case VActions.INSERT_TOP: {
213 for (let i = numberOfNodes - 1; i >= 0; --i) {
214 el.insertBefore(createElement(newVNode.children[i]), el.firstChild);
215 }
216 break;
217 }
218 case VActions.INSERT_BOTTOM: {
219 for (let i = 0; i < numberOfNodes; i++) {
220 el.appendChild(createElement(newVNode.children[i]));
221 }
222 break;
223 }
224 case VActions.DELETE_TOP: {
225 for (let i = numberOfNodes - 1; i >= 0; --i) {
226 el.removeChild(el.firstChild);
227 }
228 break;
229 }
230 case VActions.DELETE_BOTTOM: {
231 for (let i = 0; i < numberOfNodes; i++) {
232 el.removeChild(el.lastChild);
233 }
234 break;
235 }
236 default: {
237 patchChildren(el, oldVNode.children || [], newVNode.children);
238 break;
239 }
240 }
241 break;
242 }
243 }
244 }
245 }
246 }
247 if (!prevVNode)
248 el[OLD_VNODE_FIELD] = newVNode;
249 return el;
250 };
251
252 exports.OLD_VNODE_FIELD = OLD_VNODE_FIELD;
253 exports.className = className;
254 exports.createElement = createElement;
255 exports.m = m;
256 exports.patch = patch;
257 exports.patchChildren = patchChildren;
258 exports.patchProps = patchProps;
259 exports.style = style;
260 exports.svg = svg;
261
262 Object.defineProperty(exports, '__esModule', { value: true });
263
264 return exports;
265
266}({}));
267ts.createElement = createElement;
268 exports.m = m;
269 exports.patch = patch;
270 exports.patchChildren = patchChildren;
271 exports.patchProps = patchProps;
272 exports.style = style;
273 exports.svg = svg;
274
275 Object.defineProperty(exports, '__esModule', { value: true });
276
277})));