diff options
Diffstat (limited to 'thirdparty/preact/src')
-rw-r--r-- | thirdparty/preact/src/clone-element.js | 10 | ||||
-rw-r--r-- | thirdparty/preact/src/component.js | 102 | ||||
-rw-r--r-- | thirdparty/preact/src/constants.js | 20 | ||||
-rw-r--r-- | thirdparty/preact/src/dom/index.js | 100 | ||||
-rw-r--r-- | thirdparty/preact/src/dom/recycler.js | 25 | ||||
-rw-r--r-- | thirdparty/preact/src/h.js | 51 | ||||
-rw-r--r-- | thirdparty/preact/src/linked-state.js | 28 | ||||
-rw-r--r-- | thirdparty/preact/src/options.js | 18 | ||||
-rw-r--r-- | thirdparty/preact/src/preact.d.ts | 554 | ||||
-rw-r--r-- | thirdparty/preact/src/preact.js | 24 | ||||
-rw-r--r-- | thirdparty/preact/src/preact.js.flow | 9 | ||||
-rw-r--r-- | thirdparty/preact/src/render-queue.js | 23 | ||||
-rw-r--r-- | thirdparty/preact/src/render.js | 20 | ||||
-rw-r--r-- | thirdparty/preact/src/util.js | 68 | ||||
-rw-r--r-- | thirdparty/preact/src/vdom/component-recycler.js | 32 | ||||
-rw-r--r-- | thirdparty/preact/src/vdom/component.js | 270 | ||||
-rw-r--r-- | thirdparty/preact/src/vdom/diff.js | 247 | ||||
-rw-r--r-- | thirdparty/preact/src/vdom/functional-component.js | 25 | ||||
-rw-r--r-- | thirdparty/preact/src/vdom/index.js | 50 | ||||
-rw-r--r-- | thirdparty/preact/src/vnode.js | 14 |
20 files changed, 1690 insertions, 0 deletions
diff --git a/thirdparty/preact/src/clone-element.js b/thirdparty/preact/src/clone-element.js new file mode 100644 index 000000000..fc7103056 --- /dev/null +++ b/thirdparty/preact/src/clone-element.js @@ -0,0 +1,10 @@ +import { clone, extend } from './util'; +import { h } from './h'; + +export function cloneElement(vnode, props) { + return h( + vnode.nodeName, + extend(clone(vnode.attributes), props), + arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children + ); +} diff --git a/thirdparty/preact/src/component.js b/thirdparty/preact/src/component.js new file mode 100644 index 000000000..aefaebe98 --- /dev/null +++ b/thirdparty/preact/src/component.js @@ -0,0 +1,102 @@ +import { FORCE_RENDER } from './constants'; +import { extend, clone, isFunction } from './util'; +import { createLinkedState } from './linked-state'; +import { renderComponent } from './vdom/component'; +import { enqueueRender } from './render-queue'; + +/** Base Component class, for he ES6 Class method of creating Components + * @public + * + * @example + * class MyFoo extends Component { + * render(props, state) { + * return <div />; + * } + * } + */ +export function Component(props, context) { + /** @private */ + this._dirty = true; + // /** @public */ + // this._disableRendering = false; + // /** @public */ + // this.prevState = this.prevProps = this.prevContext = this.base = this.nextBase = this._parentComponent = this._component = this.__ref = this.__key = this._linkedStates = this._renderCallbacks = null; + /** @public */ + this.context = context; + /** @type {object} */ + this.props = props; + /** @type {object} */ + if (!this.state) this.state = {}; +} + + +extend(Component.prototype, { + + /** Returns a `boolean` value indicating if the component should re-render when receiving the given `props` and `state`. + * @param {object} nextProps + * @param {object} nextState + * @param {object} nextContext + * @returns {Boolean} should the component re-render + * @name shouldComponentUpdate + * @function + */ + // shouldComponentUpdate() { + // return true; + // }, + + + /** Returns a function that sets a state property when called. + * Calling linkState() repeatedly with the same arguments returns a cached link function. + * + * Provides some built-in special cases: + * - Checkboxes and radio buttons link their boolean `checked` value + * - Inputs automatically link their `value` property + * - Event paths fall back to any associated Component if not found on an element + * - If linked value is a function, will invoke it and use the result + * + * @param {string} key The path to set - can be a dot-notated deep key + * @param {string} [eventPath] If set, attempts to find the new state value at a given dot-notated path within the object passed to the linkedState setter. + * @returns {function} linkStateSetter(e) + * + * @example Update a "text" state value when an input changes: + * <input onChange={ this.linkState('text') } /> + * + * @example Set a deep state value on click + * <button onClick={ this.linkState('touch.coords', 'touches.0') }>Tap</button + */ + linkState(key, eventPath) { + let c = this._linkedStates || (this._linkedStates = {}); + return c[key+eventPath] || (c[key+eventPath] = createLinkedState(this, key, eventPath)); + }, + + + /** Update component state by copying properties from `state` to `this.state`. + * @param {object} state A hash of state properties to update with new values + */ + setState(state, callback) { + let s = this.state; + if (!this.prevState) this.prevState = clone(s); + extend(s, isFunction(state) ? state(s, this.props) : state); + if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback); + enqueueRender(this); + }, + + + /** Immediately perform a synchronous re-render of the component. + * @private + */ + forceUpdate() { + renderComponent(this, FORCE_RENDER); + }, + + + /** Accepts `props` and `state`, and returns a new Virtual DOM tree to build. + * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). + * @param {object} props Props (eg: JSX attributes) received from parent element/component + * @param {object} state The component's current state + * @param {object} context Context object (if a parent component has provided context) + * @returns VNode + */ + render() {} + +}); diff --git a/thirdparty/preact/src/constants.js b/thirdparty/preact/src/constants.js new file mode 100644 index 000000000..138cd0983 --- /dev/null +++ b/thirdparty/preact/src/constants.js @@ -0,0 +1,20 @@ +// render modes + +export const NO_RENDER = 0; +export const SYNC_RENDER = 1; +export const FORCE_RENDER = 2; +export const ASYNC_RENDER = 3; + +export const EMPTY = {}; + +export const ATTR_KEY = typeof Symbol!=='undefined' ? Symbol.for('preactattr') : '__preactattr_'; + +// DOM properties that should NOT have "px" added when numeric +export const NON_DIMENSION_PROPS = { + boxFlex:1, boxFlexGroup:1, columnCount:1, fillOpacity:1, flex:1, flexGrow:1, + flexPositive:1, flexShrink:1, flexNegative:1, fontWeight:1, lineClamp:1, lineHeight:1, + opacity:1, order:1, orphans:1, strokeOpacity:1, widows:1, zIndex:1, zoom:1 +}; + +// DOM event types that do not bubble and should be attached via useCapture +export const NON_BUBBLING_EVENTS = { blur:1, error:1, focus:1, load:1, resize:1, scroll:1 }; diff --git a/thirdparty/preact/src/dom/index.js b/thirdparty/preact/src/dom/index.js new file mode 100644 index 000000000..248a3cdc5 --- /dev/null +++ b/thirdparty/preact/src/dom/index.js @@ -0,0 +1,100 @@ +import { ATTR_KEY, NON_DIMENSION_PROPS, NON_BUBBLING_EVENTS } from '../constants'; +import options from '../options'; +import { toLowerCase, isString, isFunction, hashToClassName } from '../util'; + + + + +/** Removes a given DOM Node from its parent. */ +export function removeNode(node) { + let p = node.parentNode; + if (p) p.removeChild(node); +} + + +/** Set a named attribute on the given Node, with special behavior for some names and event handlers. + * If `value` is `null`, the attribute/handler will be removed. + * @param {Element} node An element to mutate + * @param {string} name The name/key to set, such as an event or attribute name + * @param {any} value An attribute value, such as a function to be used as an event handler + * @param {any} previousValue The last value that was set for this name/node pair + * @private + */ +export function setAccessor(node, name, value, old, isSvg) { + node[ATTR_KEY][name] = value; + + if (name==='className') name = 'class'; + + if (name==='class' && value && typeof value==='object') { + value = hashToClassName(value); + } + + if (name==='key' || name==='children' || name==='innerHTML') { + // skip these + } + else if (name==='class' && !isSvg) { + node.className = value || ''; + } + else if (name==='style') { + if (!value || isString(value) || isString(old)) { + node.style.cssText = value || ''; + } + if (value && typeof value==='object') { + if (!isString(old)) { + for (let i in old) if (!(i in value)) node.style[i] = ''; + } + for (let i in value) { + node.style[i] = typeof value[i]==='number' && !NON_DIMENSION_PROPS[i] ? (value[i]+'px') : value[i]; + } + } + } + else if (name==='dangerouslySetInnerHTML') { + if (value) node.innerHTML = value.__html; + } + else if (name[0]=='o' && name[1]=='n') { + let l = node._listeners || (node._listeners = {}); + name = toLowerCase(name.substring(2)); + // @TODO: this might be worth it later, un-breaks focus/blur bubbling in IE9: + // if (node.attachEvent) name = name=='focus'?'focusin':name=='blur'?'focusout':name; + if (value) { + if (!l[name]) node.addEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); + } + else if (l[name]) { + node.removeEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); + } + l[name] = value; + } + else if (name!=='list' && name!=='type' && !isSvg && name in node) { + setProperty(node, name, value==null ? '' : value); + if (value==null || value===false) node.removeAttribute(name); + } + else { + let ns = isSvg && name.match(/^xlink\:?(.+)/); + if (value==null || value===false) { + if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1])); + else node.removeAttribute(name); + } + else if (typeof value!=='object' && !isFunction(value)) { + if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1]), value); + else node.setAttribute(name, value); + } + } +} + + +/** Attempt to set a DOM property to the given value. + * IE & FF throw for certain property-value combinations. + */ +function setProperty(node, name, value) { + try { + node[name] = value; + } catch (e) { } +} + + +/** Proxy an event to hooked event handlers + * @private + */ +function eventProxy(e) { + return this._listeners[e.type](options.event && options.event(e) || e); +} diff --git a/thirdparty/preact/src/dom/recycler.js b/thirdparty/preact/src/dom/recycler.js new file mode 100644 index 000000000..22085a916 --- /dev/null +++ b/thirdparty/preact/src/dom/recycler.js @@ -0,0 +1,25 @@ +import { toLowerCase } from '../util'; +import { removeNode } from './index'; + +/** DOM node pool, keyed on nodeName. */ + +const nodes = {}; + +export function collectNode(node) { + removeNode(node); + + if (node instanceof Element) { + node._component = node._componentConstructor = null; + + let name = node.normalizedNodeName || toLowerCase(node.nodeName); + (nodes[name] || (nodes[name] = [])).push(node); + } +} + + +export function createNode(nodeName, isSvg) { + let name = toLowerCase(nodeName), + node = nodes[name] && nodes[name].pop() || (isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName)); + node.normalizedNodeName = name; + return node; +} diff --git a/thirdparty/preact/src/h.js b/thirdparty/preact/src/h.js new file mode 100644 index 000000000..e57ce4bde --- /dev/null +++ b/thirdparty/preact/src/h.js @@ -0,0 +1,51 @@ +import { VNode } from './vnode'; +import options from './options'; + + +let stack = []; + + + +/** JSX/hyperscript reviver +* Benchmarks: https://esbench.com/bench/57ee8f8e330ab09900a1a1a0 + * @see http://jasonformat.com/wtf-is-jsx + * @public + * @example + * /** @jsx h *\/ + * import { render, h } from 'preact'; + * render(<span>foo</span>, document.body); + */ +export function h(nodeName, attributes) { + let children, lastSimple, child, simple, i; + for (i=arguments.length; i-- > 2; ) { + stack.push(arguments[i]); + } + if (attributes && attributes.children) { + if (!stack.length) stack.push(attributes.children); + delete attributes.children; + } + while (stack.length) { + if ((child = stack.pop()) instanceof Array) { + for (i=child.length; i--; ) stack.push(child[i]); + } + else if (child!=null && child!==false) { + if (typeof child=='number' || child===true) child = String(child); + simple = typeof child=='string'; + if (simple && lastSimple) { + children[children.length-1] += child; + } + else { + if (children) children.push(child); + else children = [child]; + lastSimple = simple; + } + } + } + + let p = new VNode(nodeName, attributes || undefined, children); + + // if a "vnode hook" is defined, pass every created VNode to it + if (options.vnode) options.vnode(p); + + return p; +} diff --git a/thirdparty/preact/src/linked-state.js b/thirdparty/preact/src/linked-state.js new file mode 100644 index 000000000..ed72bd8bc --- /dev/null +++ b/thirdparty/preact/src/linked-state.js @@ -0,0 +1,28 @@ +import { isString, delve } from './util'; + +/** Create an Event handler function that sets a given state property. + * @param {Component} component The component whose state should be updated + * @param {string} key A dot-notated key path to update in the component's state + * @param {string} eventPath A dot-notated key path to the value that should be retrieved from the Event or component + * @returns {function} linkedStateHandler + * @private + */ +export function createLinkedState(component, key, eventPath) { + let path = key.split('.'), + p0 = path[0]; + return function(e) { + let t = e && e.currentTarget || this, + s = component.state, + obj = s, + v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? ((t.nodeName+t.type).match(/^input(che|rad)/i) ? t.checked : t.value) : e, + i; + if (path.length>1) { + for (i=0; i<path.length-1; i++) { + obj = obj[path[i]] || (obj[path[i]] = {}); + } + obj[path[i]] = v; + v = s[p0]; + } + component.setState({ [p0]: v }); + }; +} diff --git a/thirdparty/preact/src/options.js b/thirdparty/preact/src/options.js new file mode 100644 index 000000000..35b7418fc --- /dev/null +++ b/thirdparty/preact/src/options.js @@ -0,0 +1,18 @@ +/** Global options + * @public + * @namespace options {Object} + */ +export default { + + /** If `true`, `prop` changes trigger synchronous component updates. + * @name syncComponentUpdates + * @type Boolean + * @default true + */ + //syncComponentUpdates: true, + + /** Processes all created VNodes. + * @param {VNode} vnode A newly-created VNode to normalize/process + */ + //vnode(vnode) { } +}; diff --git a/thirdparty/preact/src/preact.d.ts b/thirdparty/preact/src/preact.d.ts new file mode 100644 index 000000000..2dd8299a9 --- /dev/null +++ b/thirdparty/preact/src/preact.d.ts @@ -0,0 +1,554 @@ +declare namespace preact { + interface ComponentProps { + children?:JSX.Element[]; + key?:string; + } + + interface PreactHTMLAttributes { + key?:string; + } + + interface VNode { + nodeName:ComponentConstructor<any, any>|string; + attributes:{[name:string]:any}; + children:VNode[]; + key:string; + } + + interface ComponentLifecycle<PropsType, StateType> { + componentWillMount?():void; + + componentDidMount?():void; + + componentWillUnmount?():void; + + componentDidUnmount?():void; + + componentWillReceiveProps?(props:PropsType):void; + + shouldComponentUpdate?(props:PropsType):boolean; + + componentWillUpdate?():void; + + componentDidUpdate?():void; + } + + interface ComponentConstructor<PropsType, StateType> { + new (props?:PropsType):Component<PropsType, StateType>; + } + + abstract class Component<PropsType, StateType> implements ComponentLifecycle<PropsType, StateType> { + constructor(props?:PropsType); + + state:StateType; + props:PropsType & ComponentProps; + base:HTMLElement; + + linkState:(name:string) => void; + + setState(state:StateType, opts?:any):void; + + abstract render(props:PropsType & ComponentProps, state:any):JSX.Element; + } + + function h<PropsType>(node:ComponentConstructor<PropsType, any>, params:PropsType, ...children:(JSX.Element|string)[]):JSX.Element; + function h(node:string, params:JSX.HTMLAttributes&JSX.SVGAttributes, ...children:(JSX.Element|string)[]):JSX.Element; + + function render(node:JSX.Element, parent:Element, merge?:boolean):Element; + + function rerender():void; + + function cloneElement(element:JSX.Element, props:any):JSX.Element; + + var options:{ + syncComponentUpdates?:boolean; + debounceRendering?:(render:() => void) => void; + vnode?:(vnode:VNode) => void; + event?:(event:Event) => Event; + }; +} + +declare module "preact" { + export = preact; +} + +declare namespace JSX { + interface Element extends preact.VNode { + + } + + interface ElementClass extends preact.Component<any, any> { + + } + + interface ElementAttributesProperty { + props:any; + } + + interface SVGAttributes { + clipPath?:string; + cx?:number | string; + cy?:number | string; + d?:string; + dx?:number | string; + dy?:number | string; + fill?:string; + fillOpacity?:number | string; + fontFamily?:string; + fontSize?:number | string; + fx?:number | string; + fy?:number | string; + gradientTransform?:string; + gradientUnits?:string; + markerEnd?:string; + markerMid?:string; + markerStart?:string; + offset?:number | string; + opacity?:number | string; + patternContentUnits?:string; + patternUnits?:string; + points?:string; + preserveAspectRatio?:string; + r?:number | string; + rx?:number | string; + ry?:number | string; + spreadMethod?:string; + stopColor?:string; + stopOpacity?:number | string; + stroke?:string; + strokeDasharray?:string; + strokeLinecap?:string; + strokeMiterlimit?:string; + strokeOpacity?:number | string; + strokeWidth?:number | string; + textAnchor?:string; + transform?:string; + version?:string; + viewBox?:string; + x1?:number | string; + x2?:number | string; + x?:number | string; + xlinkActuate?:string; + xlinkArcrole?:string; + xlinkHref?:string; + xlinkRole?:string; + xlinkShow?:string; + xlinkTitle?:string; + xlinkType?:string; + xmlBase?:string; + xmlLang?:string; + xmlSpace?:string; + y1?:number | string; + y2?:number | string; + y?:number | string; + } + + interface PathAttributes { + d:string; + } + + interface EventHandler<E extends Event> { + (event:E):void; + } + + type ClipboardEventHandler = EventHandler<ClipboardEvent>; + type CompositionEventHandler = EventHandler<CompositionEvent>; + type DragEventHandler = EventHandler<DragEvent>; + type FocusEventHandler = EventHandler<FocusEvent>; + type KeyboardEventHandler = EventHandler<KeyboardEvent>; + type MouseEventHandler = EventHandler<MouseEvent>; + type TouchEventHandler = EventHandler<TouchEvent>; + type UIEventHandler = EventHandler<UIEvent>; + type WheelEventHandler = EventHandler<WheelEvent>; + type AnimationEventHandler = EventHandler<AnimationEvent>; + type TransitionEventHandler = EventHandler<TransitionEvent>; + + type GenericEventHandler = EventHandler<Event>; + + interface DOMAttributed { + // Clipboard Events + onCopy?:ClipboardEventHandler; + onCut?:ClipboardEventHandler; + onPaste?:ClipboardEventHandler; + + // Composition Events + onCompositionEnd?:CompositionEventHandler; + onCompositionStart?:CompositionEventHandler; + onCompositionUpdate?:CompositionEventHandler; + + // Focus Events + onFocus?:FocusEventHandler; + onBlur?:FocusEventHandler; + + // Form Events + onChange?:GenericEventHandler; + onInput?:GenericEventHandler; + onSubmit?:GenericEventHandler; + + // Keyboard Events + onKeyDown?:KeyboardEventHandler; + onKeyPress?:KeyboardEventHandler; + onKeyUp?:KeyboardEventHandler; + + // Media Events + onAbort?:GenericEventHandler; + onCanPlay?:GenericEventHandler; + onCanPlayThrough?:GenericEventHandler; + onDurationChange?:GenericEventHandler; + onEmptied?:GenericEventHandler; + onEncrypted?:GenericEventHandler; + onEnded?:GenericEventHandler; + onLoadedData?:GenericEventHandler; + onLoadedMetadata?:GenericEventHandler; + onLoadStart?:GenericEventHandler; + onPause?:GenericEventHandler; + onPlay?:GenericEventHandler; + onPlaying?:GenericEventHandler; + onProgress?:GenericEventHandler; + onRateChange?:GenericEventHandler; + onSeeked?:GenericEventHandler; + onSeeking?:GenericEventHandler; + onStalled?:GenericEventHandler; + onSuspend?:GenericEventHandler; + onTimeUpdate?:GenericEventHandler; + onVolumeChange?:GenericEventHandler; + onWaiting?:GenericEventHandler; + + // MouseEvents + onClick?:MouseEventHandler; + onContextMenu?:MouseEventHandler; + onDoubleClick?:MouseEventHandler; + onDrag?:DragEventHandler; + onDragEnd?:DragEventHandler; + onDragEnter?:DragEventHandler; + onDragExit?:DragEventHandler; + onDragLeave?:DragEventHandler; + onDragOver?:DragEventHandler; + onDragStart?:DragEventHandler; + onDrop?:DragEventHandler; + onMouseDown?:MouseEventHandler; + onMouseEnter?:MouseEventHandler; + onMouseLeave?:MouseEventHandler; + onMouseMove?:MouseEventHandler; + onMouseOut?:MouseEventHandler; + onMouseOver?:MouseEventHandler; + onMouseUp?:MouseEventHandler; + + // Selection Events + onSelect?:GenericEventHandler; + + // Touch Events + onTouchCancel?:TouchEventHandler; + onTouchEnd?:TouchEventHandler; + onTouchMove?:TouchEventHandler; + onTouchStart?:TouchEventHandler; + + // UI Events + onScroll?:UIEventHandler; + + // Wheel Events + onWheel?:WheelEventHandler; + + // Animation Events + onAnimationStart?:AnimationEventHandler; + onAnimationEnd?:AnimationEventHandler; + onAnimationIteration?:AnimationEventHandler; + + // Transition Events + onTransitionEnd?:TransitionEventHandler; + } + + interface HTMLAttributes extends preact.PreactHTMLAttributes, DOMAttributed { + // Standard HTML Attributes + accept?:string; + acceptCharset?:string; + accessKey?:string; + action?:string; + allowFullScreen?:boolean; + allowTransparency?:boolean; + alt?:string; + async?:boolean; + autocomplete?:string; + autofocus?:boolean; + autoPlay?:boolean; + capture?:boolean; + cellPadding?:number | string; + cellSpacing?:number | string; + charSet?:string; + challenge?:string; + checked?:boolean; + class?:string; + className?:string; + cols?:number; + colSpan?:number; + content?:string; + contentEditable?:boolean; + contextMenu?:string; + controls?:boolean; + coords?:string; + crossOrigin?:string; + data?:string; + dateTime?:string; + default?:boolean; + defer?:boolean; + dir?:string; + disabled?:boolean; + download?:any; + draggable?:boolean; + encType?:string; + form?:string; + formAction?:string; + formEncType?:string; + formMethod?:string; + formNoValidate?:boolean; + formTarget?:string; + frameBorder?:number | string; + headers?:string; + height?:number | string; + hidden?:boolean; + high?:number; + href?:string; + hrefLang?:string; + for?:string; + httpEquiv?:string; + icon?:string; + id?:string; + inputMode?:string; + integrity?:string; + is?:string; + keyParams?:string; + keyType?:string; + kind?:string; + label?:string; + lang?:string; + list?:string; + loop?:boolean; + low?:number; + manifest?:string; + marginHeight?:number; + marginWidth?:number; + max?:number | string; + maxLength?:number; + media?:string; + mediaGroup?:string; + method?:string; + min?:number | string; + minLength?:number; + multiple?:boolean; + muted?:boolean; + name?:string; + noValidate?:boolean; + open?:boolean; + optimum?:number; + pattern?:string; + placeholder?:string; + poster?:string; + preload?:string; + radioGroup?:string; + readOnly?:boolean; + rel?:string; + required?:boolean; + role?:string; + rows?:number; + rowSpan?:number; + sandbox?:string; + scope?:string; + scoped?:boolean; + scrolling?:string; + seamless?:boolean; + selected?:boolean; + shape?:string; + size?:number; + sizes?:string; + span?:number; + spellCheck?:boolean; + src?:string; + srcset?:string; + srcDoc?:string; + srcLang?:string; + srcSet?:string; + start?:number; + step?:number | string; + style?:any; + summary?:string; + tabIndex?:number; + target?:string; + title?:string; + type?:string; + useMap?:string; + value?:string | string[]; + width?:number | string; + wmode?:string; + wrap?:string; + + // RDFa Attributes + about?:string; + datatype?:string; + inlist?:any; + prefix?:string; + property?:string; + resource?:string; + typeof?:string; + vocab?:string; + } + + interface IntrinsicElements { + // HTML + a:HTMLAttributes; + abbr:HTMLAttributes; + address:HTMLAttributes; + area:HTMLAttributes; + article:HTMLAttributes; + aside:HTMLAttributes; + audio:HTMLAttributes; + b:HTMLAttributes; + base:HTMLAttributes; + bdi:HTMLAttributes; + bdo:HTMLAttributes; + big:HTMLAttributes; + blockquote:HTMLAttributes; + body:HTMLAttributes; + br:HTMLAttributes; + button:HTMLAttributes; + canvas:HTMLAttributes; + caption:HTMLAttributes; + cite:HTMLAttributes; + code:HTMLAttributes; + col:HTMLAttributes; + colgroup:HTMLAttributes; + data:HTMLAttributes; + datalist:HTMLAttributes; + dd:HTMLAttributes; + del:HTMLAttributes; + details:HTMLAttributes; + dfn:HTMLAttributes; + dialog:HTMLAttributes; + div:HTMLAttributes; + dl:HTMLAttributes; + dt:HTMLAttributes; + em:HTMLAttributes; + embed:HTMLAttributes; + fieldset:HTMLAttributes; + figcaption:HTMLAttributes; + figure:HTMLAttributes; + footer:HTMLAttributes; + form:HTMLAttributes; + h1:HTMLAttributes; + h2:HTMLAttributes; + h3:HTMLAttributes; + h4:HTMLAttributes; + h5:HTMLAttributes; + h6:HTMLAttributes; + head:HTMLAttributes; + header:HTMLAttributes; + hr:HTMLAttributes; + html:HTMLAttributes; + i:HTMLAttributes; + iframe:HTMLAttributes; + img:HTMLAttributes; + input:HTMLAttributes; + ins:HTMLAttributes; + kbd:HTMLAttributes; + keygen:HTMLAttributes; + label:HTMLAttributes; + legend:HTMLAttributes; + li:HTMLAttributes; + link:HTMLAttributes; + main:HTMLAttributes; + map:HTMLAttributes; + mark:HTMLAttributes; + menu:HTMLAttributes; + menuitem:HTMLAttributes; + meta:HTMLAttributes; + meter:HTMLAttributes; + nav:HTMLAttributes; + noscript:HTMLAttributes; + object:HTMLAttributes; + ol:HTMLAttributes; + optgroup:HTMLAttributes; + option:HTMLAttributes; + output:HTMLAttributes; + p:HTMLAttributes; + param:HTMLAttributes; + picture:HTMLAttributes; + pre:HTMLAttributes; + progress:HTMLAttributes; + q:HTMLAttributes; + rp:HTMLAttributes; + rt:HTMLAttributes; + ruby:HTMLAttributes; + s:HTMLAttributes; + samp:HTMLAttributes; + script:HTMLAttributes; + section:HTMLAttributes; + select:HTMLAttributes; + small:HTMLAttributes; + source:HTMLAttributes; + span:HTMLAttributes; + strong:HTMLAttributes; + style:HTMLAttributes; + sub:HTMLAttributes; + summary:HTMLAttributes; + sup:HTMLAttributes; + table:HTMLAttributes; + tbody:HTMLAttributes; + td:HTMLAttributes; + textarea:HTMLAttributes; + tfoot:HTMLAttributes; + th:HTMLAttributes; + thead:HTMLAttributes; + time:HTMLAttributes; + title:HTMLAttributes; + tr:HTMLAttributes; + track:HTMLAttributes; + u:HTMLAttributes; + ul:HTMLAttributes; + "var":HTMLAttributes; + video:HTMLAttributes; + wbr:HTMLAttributes; + + //SVG + svg:SVGAttributes; + + circle:SVGAttributes; + clipPath:SVGAttributes; + defs:SVGAttributes; + ellipse:SVGAttributes; + feBlend:SVGAttributes; + feColorMatrix:SVGAttributes; + feComponentTransfer:SVGAttributes; + feComposite:SVGAttributes; + feConvolveMatrix:SVGAttributes; + feDiffuseLighting:SVGAttributes; + feDisplacementMap:SVGAttributes; + feFlood:SVGAttributes; + feGaussianBlur:SVGAttributes; + feImage:SVGAttributes; + feMerge:SVGAttributes; + feMergeNode:SVGAttributes; + feMorphology:SVGAttributes; + feOffset:SVGAttributes; + feSpecularLighting:SVGAttributes; + feTile:SVGAttributes; + feTurbulence:SVGAttributes; + filter:SVGAttributes; + foreignObject:SVGAttributes; + g:SVGAttributes; + image:SVGAttributes; + line:SVGAttributes; + linearGradient:SVGAttributes; + marker:SVGAttributes; + mask:SVGAttributes; + path:SVGAttributes; + pattern:SVGAttributes; + polygon:SVGAttributes; + polyline:SVGAttributes; + radialGradient:SVGAttributes; + rect:SVGAttributes; + stop:SVGAttributes; + symbol:SVGAttributes; + text:SVGAttributes; + tspan:SVGAttributes; + use:SVGAttributes; + } +} diff --git a/thirdparty/preact/src/preact.js b/thirdparty/preact/src/preact.js new file mode 100644 index 000000000..1fa169c04 --- /dev/null +++ b/thirdparty/preact/src/preact.js @@ -0,0 +1,24 @@ +import { h } from './h'; +import { cloneElement } from './clone-element'; +import { Component } from './component'; +import { render } from './render'; +import { rerender } from './render-queue'; +import options from './options'; + +export default { + h, + cloneElement, + Component, + render, + rerender, + options +}; + +export { + h, + cloneElement, + Component, + render, + rerender, + options +}; diff --git a/thirdparty/preact/src/preact.js.flow b/thirdparty/preact/src/preact.js.flow new file mode 100644 index 000000000..37745faf6 --- /dev/null +++ b/thirdparty/preact/src/preact.js.flow @@ -0,0 +1,9 @@ +/* @flow */ + +import { createElement as h, cloneElement, Component, render } from 'react'; + +export { h, cloneElement, Component, render }; +export default { h, cloneElement, Component, render }; + +declare export function rerender(): void; +declare export var options: Object; diff --git a/thirdparty/preact/src/render-queue.js b/thirdparty/preact/src/render-queue.js new file mode 100644 index 000000000..ff603611b --- /dev/null +++ b/thirdparty/preact/src/render-queue.js @@ -0,0 +1,23 @@ +import options from './options'; +import { defer } from './util'; +import { renderComponent } from './vdom/component'; + +/** Managed queue of dirty components to be re-rendered */ + +// items/itemsOffline swap on each rerender() call (just a simple pool technique) +let items = []; + +export function enqueueRender(component) { + if (!component._dirty && (component._dirty = true) && items.push(component)==1) { + (options.debounceRendering || defer)(rerender); + } +} + + +export function rerender() { + let p, list = items; + items = []; + while ( (p = list.pop()) ) { + if (p._dirty) renderComponent(p); + } +} diff --git a/thirdparty/preact/src/render.js b/thirdparty/preact/src/render.js new file mode 100644 index 000000000..e0e8526ec --- /dev/null +++ b/thirdparty/preact/src/render.js @@ -0,0 +1,20 @@ +import { diff } from './vdom/diff'; + +/** Render JSX into a `parent` Element. + * @param {VNode} vnode A (JSX) VNode to render + * @param {Element} parent DOM element to render into + * @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge` + * @public + * + * @example + * // render a div into <body>: + * render(<div id="hello">hello!</div>, document.body); + * + * @example + * // render a "Thing" component into #foo: + * const Thing = ({ name }) => <span>{ name }</span>; + * render(<Thing name="one" />, document.querySelector('#foo')); + */ +export function render(vnode, parent, merge) { + return diff(merge, vnode, {}, false, parent); +} diff --git a/thirdparty/preact/src/util.js b/thirdparty/preact/src/util.js new file mode 100644 index 000000000..d2e63b090 --- /dev/null +++ b/thirdparty/preact/src/util.js @@ -0,0 +1,68 @@ +/** Copy own-properties from `props` onto `obj`. + * @returns obj + * @private + */ +export function extend(obj, props) { + if (props) { + for (let i in props) obj[i] = props[i]; + } + return obj; +} + + +/** Fast clone. Note: does not filter out non-own properties. + * @see https://esbench.com/bench/56baa34f45df6895002e03b6 + */ +export function clone(obj) { + return extend({}, obj); +} + + +/** Get a deep property value from the given object, expressed in dot-notation. + * @private + */ +export function delve(obj, key) { + for (let p=key.split('.'), i=0; i<p.length && obj; i++) { + obj = obj[p[i]]; + } + return obj; +} + + +/** @private is the given object a Function? */ +export function isFunction(obj) { + return 'function'===typeof obj; +} + + +/** @private is the given object a String? */ +export function isString(obj) { + return 'string'===typeof obj; +} + + +/** Convert a hashmap of CSS classes to a space-delimited className string + * @private + */ +export function hashToClassName(c) { + let str = ''; + for (let prop in c) { + if (c[prop]) { + if (str) str += ' '; + str += prop; + } + } + return str; +} + + +/** Just a memoized String#toLowerCase */ +let lcCache = {}; +export const toLowerCase = s => lcCache[s] || (lcCache[s] = s.toLowerCase()); + + +/** Call a function asynchronously, as soon as possible. + * @param {Function} callback + */ +let resolved = typeof Promise!=='undefined' && Promise.resolve(); +export const defer = resolved ? (f => { resolved.then(f); }) : setTimeout; diff --git a/thirdparty/preact/src/vdom/component-recycler.js b/thirdparty/preact/src/vdom/component-recycler.js new file mode 100644 index 000000000..a70f0ece0 --- /dev/null +++ b/thirdparty/preact/src/vdom/component-recycler.js @@ -0,0 +1,32 @@ +import { Component } from '../component'; + +/** Retains a pool of Components for re-use, keyed on component name. + * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding. + * @private + */ +const components = {}; + + +export function collectComponent(component) { + let name = component.constructor.name, + list = components[name]; + if (list) list.push(component); + else components[name] = [component]; +} + + +export function createComponent(Ctor, props, context) { + let inst = new Ctor(props, context), + list = components[Ctor.name]; + Component.call(inst, props, context); + if (list) { + for (let i=list.length; i--; ) { + if (list[i].constructor===Ctor) { + inst.nextBase = list[i].nextBase; + list.splice(i, 1); + break; + } + } + } + return inst; +} diff --git a/thirdparty/preact/src/vdom/component.js b/thirdparty/preact/src/vdom/component.js new file mode 100644 index 000000000..bb2e4fa5d --- /dev/null +++ b/thirdparty/preact/src/vdom/component.js @@ -0,0 +1,270 @@ +import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants'; +import options from '../options'; +import { isFunction, clone, extend } from '../util'; +import { enqueueRender } from '../render-queue'; +import { getNodeProps } from './index'; +import { diff, mounts, diffLevel, flushMounts, removeOrphanedChildren, recollectNodeTree } from './diff'; +import { isFunctionalComponent, buildFunctionalComponent } from './functional-component'; +import { createComponent, collectComponent } from './component-recycler'; +import { removeNode } from '../dom/index'; + + + +/** Set a component's `props` (generally derived from JSX attributes). + * @param {Object} props + * @param {Object} [opts] + * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering. + * @param {boolean} [opts.render=true] If `false`, no render will be triggered. + */ +export function setComponentProps(component, props, opts, context, mountAll) { + if (component._disable) return; + component._disable = true; + + if ((component.__ref = props.ref)) delete props.ref; + if ((component.__key = props.key)) delete props.key; + + if (!component.base || mountAll) { + if (component.componentWillMount) component.componentWillMount(); + } + else if (component.componentWillReceiveProps) { + component.componentWillReceiveProps(props, context); + } + + if (context && context!==component.context) { + if (!component.prevContext) component.prevContext = component.context; + component.context = context; + } + + if (!component.prevProps) component.prevProps = component.props; + component.props = props; + + component._disable = false; + + if (opts!==NO_RENDER) { + if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { + renderComponent(component, SYNC_RENDER, mountAll); + } + else { + enqueueRender(component); + } + } + + if (component.__ref) component.__ref(component); +} + + + +/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. + * @param {Component} component + * @param {Object} [opts] + * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one. + * @private + */ +export function renderComponent(component, opts, mountAll, isChild) { + if (component._disable) return; + + let skip, rendered, + props = component.props, + state = component.state, + context = component.context, + previousProps = component.prevProps || props, + previousState = component.prevState || state, + previousContext = component.prevContext || context, + isUpdate = component.base, + nextBase = component.nextBase, + initialBase = isUpdate || nextBase, + initialChildComponent = component._component, + inst, cbase; + + // if updating + if (isUpdate) { + component.props = previousProps; + component.state = previousState; + component.context = previousContext; + if (opts!==FORCE_RENDER + && component.shouldComponentUpdate + && component.shouldComponentUpdate(props, state, context) === false) { + skip = true; + } + else if (component.componentWillUpdate) { + component.componentWillUpdate(props, state, context); + } + component.props = props; + component.state = state; + component.context = context; + } + + component.prevProps = component.prevState = component.prevContext = component.nextBase = null; + component._dirty = false; + + if (!skip) { + if (component.render) rendered = component.render(props, state, context); + + // context to pass to the child, can be updated via (grand-)parent component + if (component.getChildContext) { + context = extend(clone(context), component.getChildContext()); + } + + while (isFunctionalComponent(rendered)) { + rendered = buildFunctionalComponent(rendered, context); + } + + let childComponent = rendered && rendered.nodeName, + toUnmount, base; + + if (isFunction(childComponent)) { + // set up high order component link + + + inst = initialChildComponent; + let childProps = getNodeProps(rendered); + + if (inst && inst.constructor===childComponent) { + setComponentProps(inst, childProps, SYNC_RENDER, context); + } + else { + toUnmount = inst; + + inst = createComponent(childComponent, childProps, context); + inst.nextBase = inst.nextBase || nextBase; + inst._parentComponent = component; + component._component = inst; + setComponentProps(inst, childProps, NO_RENDER, context); + renderComponent(inst, SYNC_RENDER, mountAll, true); + } + + base = inst.base; + } + else { + cbase = initialBase; + + // destroy high order component link + toUnmount = initialChildComponent; + if (toUnmount) { + cbase = component._component = null; + } + + if (initialBase || opts===SYNC_RENDER) { + if (cbase) cbase._component = null; + base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); + } + } + + if (initialBase && base!==initialBase && inst!==initialChildComponent) { + let baseParent = initialBase.parentNode; + if (baseParent && base!==baseParent) { + baseParent.replaceChild(base, initialBase); + } + + if (!cbase && !toUnmount && component._parentComponent) { + initialBase._component = null; + recollectNodeTree(initialBase); + } + } + + if (toUnmount) { + unmountComponent(toUnmount, base!==initialBase); + } + + component.base = base; + if (base && !isChild) { + let componentRef = component, + t = component; + while ((t=t._parentComponent)) { componentRef = t; } + base._component = componentRef; + base._componentConstructor = componentRef.constructor; + } + } + + if (!isUpdate || mountAll) { + mounts.unshift(component); + } + else if (!skip && component.componentDidUpdate) { + component.componentDidUpdate(previousProps, previousState, previousContext); + } + + let cb = component._renderCallbacks, fn; + if (cb) while ( (fn = cb.pop()) ) fn.call(component); + + if (!diffLevel && !isChild) flushMounts(); +} + + + +/** Apply the Component referenced by a VNode to the DOM. + * @param {Element} dom The DOM node to mutate + * @param {VNode} vnode A Component-referencing VNode + * @returns {Element} dom The created/mutated element + * @private + */ +export function buildComponentFromVNode(dom, vnode, context, mountAll) { + let c = dom && dom._component, + oldDom = dom, + isDirectOwner = c && dom._componentConstructor===vnode.nodeName, + isOwner = isDirectOwner, + props = getNodeProps(vnode); + while (c && !isOwner && (c=c._parentComponent)) { + isOwner = c.constructor===vnode.nodeName; + } + + if (c && isOwner && (!mountAll || c._component)) { + setComponentProps(c, props, ASYNC_RENDER, context, mountAll); + dom = c.base; + } + else { + if (c && !isDirectOwner) { + unmountComponent(c, true); + dom = oldDom = null; + } + + c = createComponent(vnode.nodeName, props, context); + if (dom && !c.nextBase) c.nextBase = dom; + setComponentProps(c, props, SYNC_RENDER, context, mountAll); + dom = c.base; + + if (oldDom && dom!==oldDom) { + oldDom._component = null; + recollectNodeTree(oldDom); + } + } + + return dom; +} + + + +/** Remove a component from the DOM and recycle it. + * @param {Element} dom A DOM node from which to unmount the given Component + * @param {Component} component The Component instance to unmount + * @private + */ +export function unmountComponent(component, remove) { + // console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`); + let base = component.base; + + component._disable = true; + + if (component.componentWillUnmount) component.componentWillUnmount(); + + component.base = null; + + // recursively tear down & recollect high-order component children: + let inner = component._component; + if (inner) { + unmountComponent(inner, remove); + } + else if (base) { + if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null); + + component.nextBase = base; + + if (remove) { + removeNode(base); + collectComponent(component); + } + removeOrphanedChildren(base.childNodes, !remove); + } + + if (component.__ref) component.__ref(null); + if (component.componentDidUnmount) component.componentDidUnmount(); +} diff --git a/thirdparty/preact/src/vdom/diff.js b/thirdparty/preact/src/vdom/diff.js new file mode 100644 index 000000000..691434e98 --- /dev/null +++ b/thirdparty/preact/src/vdom/diff.js @@ -0,0 +1,247 @@ +import { ATTR_KEY } from '../constants'; +import { isString, isFunction } from '../util'; +import { isSameNodeType, isNamedNode } from './index'; +import { isFunctionalComponent, buildFunctionalComponent } from './functional-component'; +import { buildComponentFromVNode } from './component'; +import { setAccessor } from '../dom/index'; +import { createNode, collectNode } from '../dom/recycler'; +import { unmountComponent } from './component'; + + +/** Diff recursion count, used to track the end of the diff cycle. */ +export const mounts = []; + +/** Diff recursion count, used to track the end of the diff cycle. */ +export let diffLevel = 0; + +let isSvgMode = false; + + +export function flushMounts() { + let c; + while ((c=mounts.pop())) { + if (c.componentDidMount) c.componentDidMount(); + } +} + + +/** Apply differences in a given vnode (and it's deep children) to a real DOM Node. + * @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode` + * @param {VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure + * @returns {Element} dom The created/mutated element + * @private + */ +export function diff(dom, vnode, context, mountAll, parent, componentRoot) { + if (!diffLevel++) isSvgMode = parent instanceof SVGElement; + let ret = idiff(dom, vnode, context, mountAll); + if (parent && ret.parentNode!==parent) parent.appendChild(ret); + if (!--diffLevel && !componentRoot) flushMounts(); + return ret; +} + + +function idiff(dom, vnode, context, mountAll) { + let originalAttributes = vnode && vnode.attributes; + + while (isFunctionalComponent(vnode)) { + vnode = buildFunctionalComponent(vnode, context); + } + + if (vnode==null) vnode = ''; + + if (isString(vnode)) { + if (dom) { + if (dom instanceof Text && dom.parentNode) { + dom.nodeValue = vnode; + return dom; + } + recollectNodeTree(dom); + } + return document.createTextNode(vnode); + } + + if (isFunction(vnode.nodeName)) { + return buildComponentFromVNode(dom, vnode, context, mountAll); + } + + let out = dom, + nodeName = vnode.nodeName, + prevSvgMode = isSvgMode; + + if (!isString(nodeName)) { + nodeName = String(nodeName); + } + + isSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode; + + if (!dom) { + out = createNode(nodeName, isSvgMode); + } + else if (!isNamedNode(dom, nodeName)) { + out = createNode(nodeName, isSvgMode); + // move children into the replacement node + while (dom.firstChild) out.appendChild(dom.firstChild); + // reclaim element nodes + recollectNodeTree(dom); + } + + // fast-path for elements containing a single TextNode: + if (vnode.children && vnode.children.length===1 && typeof vnode.children[0]==='string' && out.childNodes.length===1 && out.firstChild instanceof Text) { + out.firstChild.nodeValue = vnode.children[0]; + } + else if (vnode.children || out.firstChild) { + innerDiffNode(out, vnode.children, context, mountAll); + } + + let props = out[ATTR_KEY]; + if (!props) { + out[ATTR_KEY] = props = {}; + for (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value; + } + + diffAttributes(out, vnode.attributes, props); + + if (originalAttributes && typeof originalAttributes.ref==='function') { + (props.ref = originalAttributes.ref)(out); + } + + isSvgMode = prevSvgMode; + + return out; +} + + +/** Apply child and attribute changes between a VNode and a DOM Node to the DOM. */ +function innerDiffNode(dom, vchildren, context, mountAll) { + let originalChildren = dom.childNodes, + children = [], + keyed = {}, + keyedLen = 0, + min = 0, + len = originalChildren.length, + childrenLen = 0, + vlen = vchildren && vchildren.length, + j, c, vchild, child; + + if (len) { + for (let i=0; i<len; i++) { + let child = originalChildren[i], + key = vlen ? ((c = child._component) ? c.__key : (c = child[ATTR_KEY]) ? c.key : null) : null; + if (key || key===0) { + keyedLen++; + keyed[key] = child; + } + else { + children[childrenLen++] = child; + } + } + } + + if (vlen) { + for (let i=0; i<vlen; i++) { + vchild = vchildren[i]; + child = null; + + // if (isFunctionalComponent(vchild)) { + // vchild = buildFunctionalComponent(vchild); + // } + + // attempt to find a node based on key matching + let key = vchild.key; + if (key!=null) { + if (keyedLen && key in keyed) { + child = keyed[key]; + keyed[key] = undefined; + keyedLen--; + } + } + // attempt to pluck a node of the same type from the existing children + else if (!child && min<childrenLen) { + for (j=min; j<childrenLen; j++) { + c = children[j]; + if (c && isSameNodeType(c, vchild)) { + child = c; + children[j] = undefined; + if (j===childrenLen-1) childrenLen--; + if (j===min) min++; + break; + } + } + if (!child && min<childrenLen && isFunction(vchild.nodeName) && mountAll) { + child = children[min]; + children[min++] = undefined; + } + } + + // morph the matched/found/created DOM child to match vchild (deep) + child = idiff(child, vchild, context, mountAll); + + if (child && child!==dom && child!==originalChildren[i]) { + dom.insertBefore(child, originalChildren[i] || null); + } + } + } + + + if (keyedLen) { + for (let i in keyed) if (keyed[i]) recollectNodeTree(keyed[i]); + } + + // remove orphaned children + if (min<childrenLen) { + removeOrphanedChildren(children); + } +} + + +/** Reclaim children that were unreferenced in the desired VTree */ +export function removeOrphanedChildren(children, unmountOnly) { + for (let i=children.length; i--; ) { + if (children[i]) { + recollectNodeTree(children[i], unmountOnly); + } + } +} + + +/** Reclaim an entire tree of nodes, starting at the root. */ +export function recollectNodeTree(node, unmountOnly) { + // @TODO: Need to make a call on whether Preact should remove nodes not created by itself. + // Currently it *does* remove them. Discussion: https://github.com/developit/preact/issues/39 + //if (!node[ATTR_KEY]) return; + + let component = node._component; + if (component) { + unmountComponent(component, !unmountOnly); + } + else { + if (node[ATTR_KEY] && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null); + + if (!unmountOnly) { + collectNode(node); + } + + if (node.childNodes && node.childNodes.length) { + removeOrphanedChildren(node.childNodes, unmountOnly); + } + } +} + + +/** Apply differences in attributes from a VNode to the given DOM Node. */ +function diffAttributes(dom, attrs, old) { + for (let name in old) { + if (!(attrs && name in attrs) && old[name]!=null) { + setAccessor(dom, name, null, old[name], isSvgMode); + } + } + + // new & updated + if (attrs) { + for (let name in attrs) { + if (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name])) { + setAccessor(dom, name, attrs[name], old[name], isSvgMode); + } + } + } +} diff --git a/thirdparty/preact/src/vdom/functional-component.js b/thirdparty/preact/src/vdom/functional-component.js new file mode 100644 index 000000000..04bc5a464 --- /dev/null +++ b/thirdparty/preact/src/vdom/functional-component.js @@ -0,0 +1,25 @@ +import { EMPTY } from '../constants'; +import { getNodeProps } from './index'; +import { isFunction } from '../util'; + + +/** Check if a VNode is a reference to a stateless functional component. + * A function component is represented as a VNode whose `nodeName` property is a reference to a function. + * If that function is not a Component (ie, has no `.render()` method on a prototype), it is considered a stateless functional component. + * @param {VNode} vnode A VNode + * @private + */ +export function isFunctionalComponent(vnode) { + let nodeName = vnode && vnode.nodeName; + return nodeName && isFunction(nodeName) && !(nodeName.prototype && nodeName.prototype.render); +} + + + +/** Construct a resultant VNode from a VNode referencing a stateless functional component. + * @param {VNode} vnode A VNode with a `nodeName` property that is a reference to a function. + * @private + */ +export function buildFunctionalComponent(vnode, context) { + return vnode.nodeName(getNodeProps(vnode), context || EMPTY); +} diff --git a/thirdparty/preact/src/vdom/index.js b/thirdparty/preact/src/vdom/index.js new file mode 100644 index 000000000..50d4ca2b9 --- /dev/null +++ b/thirdparty/preact/src/vdom/index.js @@ -0,0 +1,50 @@ +import { clone, isString, isFunction, toLowerCase } from '../util'; +import { isFunctionalComponent } from './functional-component'; + + +/** Check if two nodes are equivalent. + * @param {Element} node + * @param {VNode} vnode + * @private + */ +export function isSameNodeType(node, vnode) { + if (isString(vnode)) { + return node instanceof Text; + } + if (isString(vnode.nodeName)) { + return isNamedNode(node, vnode.nodeName); + } + if (isFunction(vnode.nodeName)) { + return node._componentConstructor===vnode.nodeName || isFunctionalComponent(vnode); + } +} + + +export function isNamedNode(node, nodeName) { + return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName); +} + + +/** + * Reconstruct Component-style `props` from a VNode. + * Ensures default/fallback values from `defaultProps`: + * Own-properties of `defaultProps` not present in `vnode.attributes` are added. + * @param {VNode} vnode + * @returns {Object} props + */ +export function getNodeProps(vnode) { + let defaultProps = vnode.nodeName.defaultProps, + props = clone(vnode.attributes); + + if (defaultProps) { + for (let i in defaultProps) { + if (props[i]===undefined) { + props[i] = defaultProps[i]; + } + } + } + + if (vnode.children) props.children = vnode.children; + + return props; +} diff --git a/thirdparty/preact/src/vnode.js b/thirdparty/preact/src/vnode.js new file mode 100644 index 000000000..1c3f10e3d --- /dev/null +++ b/thirdparty/preact/src/vnode.js @@ -0,0 +1,14 @@ +/** Virtual DOM Node */ +export function VNode(nodeName, attributes, children) { + /** @type {string|function} */ + this.nodeName = nodeName; + + /** @type {object<string>|undefined} */ + this.attributes = attributes; + + /** @type {array<VNode>|undefined} */ + this.children = children; + + /** Reference to the given key. */ + this.key = attributes && attributes.key; +} |