wallace 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/component.js +155 -139
- package/lib/extend.js +20 -0
- package/lib/index.js +11 -28
- package/lib/mount.js +17 -0
- package/lib/nest.js +8 -0
- package/lib/refs.js +19 -0
- package/lib/{repeaters.js → repeaters/keyed.js} +2 -60
- package/lib/repeaters/sequential.js +46 -0
- package/lib/router.jsx +120 -0
- package/lib/stubs.js +6 -0
- package/lib/types.d.ts +55 -37
- package/lib/utils.js +23 -53
- package/lib/watch.js +42 -0
- package/package.json +4 -3
- package/lib/initCalls.js +0 -116
package/lib/component.js
CHANGED
|
@@ -1,158 +1,174 @@
|
|
|
1
|
+
const throwAway = document.createElement("template");
|
|
1
2
|
const NO_LOOKUP = "__";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.el = root;
|
|
15
|
-
this.ref = {};
|
|
16
|
-
this._b(this, root);
|
|
17
|
-
}
|
|
4
|
+
const ComponentBase = {
|
|
5
|
+
stubs: {},
|
|
6
|
+
prototype: {
|
|
7
|
+
/**
|
|
8
|
+
* The render function that gets called by parent components.
|
|
9
|
+
*/
|
|
10
|
+
render: function (props, ctrl) {
|
|
11
|
+
this.props = props;
|
|
12
|
+
this.ctrl = ctrl;
|
|
13
|
+
this.update();
|
|
14
|
+
},
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Updates the DOM and renders nested components.
|
|
18
|
+
*/
|
|
19
|
+
update: function () {
|
|
20
|
+
this._u(0, this._l);
|
|
21
|
+
},
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
_u: function (i, il) {
|
|
24
|
+
let watch,
|
|
25
|
+
element,
|
|
26
|
+
parent,
|
|
27
|
+
displayToggle,
|
|
28
|
+
detacher,
|
|
29
|
+
lookupTrue,
|
|
30
|
+
shouldBeVisible,
|
|
31
|
+
detachedElements,
|
|
32
|
+
detachedElement,
|
|
33
|
+
index,
|
|
34
|
+
adjustedIndex;
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
const watches = this._w,
|
|
37
|
+
props = this.props,
|
|
38
|
+
previous = this._p;
|
|
39
|
+
/*
|
|
40
|
+
Watches is an array of objects with keys:
|
|
41
|
+
e: the element index (number)
|
|
42
|
+
c: the callbacks (object)
|
|
43
|
+
?v: visibility toggle (object)
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
proto._gs = function (name) {
|
|
33
|
-
return this.constructor.stubs[name];
|
|
34
|
-
};
|
|
45
|
+
The callback is an object whose key is the lookup and value is a function which
|
|
46
|
+
applies the effect.
|
|
35
47
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const run = this._r;
|
|
42
|
-
if (run[key] === undefined) {
|
|
43
|
-
let oldValue = this._p[key];
|
|
44
|
-
const newValue = this._q[key](props, this);
|
|
45
|
-
this._p[key] = newValue;
|
|
46
|
-
const rtn = [newValue, oldValue, newValue !== oldValue];
|
|
47
|
-
run[key] = rtn;
|
|
48
|
-
return rtn;
|
|
49
|
-
}
|
|
50
|
-
return run[key];
|
|
51
|
-
};
|
|
48
|
+
The visibility toggle has keys:
|
|
49
|
+
q: the query key in lookup
|
|
50
|
+
s: the number of watches to skip as their node is underneath
|
|
51
|
+
r: reversed
|
|
52
|
+
?d: detacher
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
The detacher has keys:
|
|
55
|
+
i: the initial element index
|
|
56
|
+
s: the stash key of the detacher (plain object)
|
|
57
|
+
e: the parent element key
|
|
58
|
+
*/
|
|
59
|
+
while (i < il) {
|
|
60
|
+
watch = watches[i];
|
|
61
|
+
element = this._e[watch.e];
|
|
62
|
+
displayToggle = watch.v;
|
|
63
|
+
shouldBeVisible = true;
|
|
64
|
+
if (displayToggle) {
|
|
65
|
+
lookupTrue = !!this._q[displayToggle.q](props, this);
|
|
66
|
+
shouldBeVisible = displayToggle.r ? lookupTrue : !lookupTrue;
|
|
67
|
+
detacher = displayToggle.d;
|
|
68
|
+
if (detacher) {
|
|
69
|
+
index = detacher.i;
|
|
70
|
+
parent = this._e[detacher.e];
|
|
71
|
+
detachedElements = this._s[detacher.s];
|
|
72
|
+
detachedElement = detachedElements[index];
|
|
73
|
+
if (shouldBeVisible && detachedElement) {
|
|
74
|
+
adjustedIndex =
|
|
75
|
+
index -
|
|
76
|
+
Object.keys(detachedElements).filter(function (k) {
|
|
77
|
+
return k < index && detachedElements[k];
|
|
78
|
+
}).length;
|
|
79
|
+
parent.insertBefore(detachedElement, parent.childNodes[adjustedIndex]);
|
|
80
|
+
detachedElements[index] = null;
|
|
81
|
+
} else if (!shouldBeVisible && !detachedElement) {
|
|
82
|
+
parent.removeChild(element);
|
|
83
|
+
detachedElements[index] = element;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
element.hidden = !shouldBeVisible;
|
|
87
|
+
}
|
|
88
|
+
if (!shouldBeVisible) {
|
|
89
|
+
i += displayToggle.s;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (shouldBeVisible) {
|
|
93
|
+
const prev = previous[i],
|
|
94
|
+
callbacks = watch.c;
|
|
95
|
+
for (let key in callbacks) {
|
|
96
|
+
if (key === NO_LOOKUP) {
|
|
97
|
+
callbacks[key](element, props, this);
|
|
98
|
+
} else {
|
|
99
|
+
const oldValue = prev[key],
|
|
100
|
+
newValue = this._q[key](props, this);
|
|
101
|
+
if (oldValue !== newValue) {
|
|
102
|
+
callbacks[key](element, props, this, newValue);
|
|
103
|
+
prev[key] = newValue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
i++;
|
|
65
109
|
}
|
|
66
110
|
}
|
|
67
111
|
}
|
|
68
112
|
};
|
|
69
113
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.ctrl = ctrl;
|
|
76
|
-
this.update();
|
|
77
|
-
};
|
|
114
|
+
Object.defineProperty(ComponentBase.prototype, "hidden", {
|
|
115
|
+
set: function (value) {
|
|
116
|
+
this.el.hidden = value;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
78
119
|
|
|
79
120
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
121
|
+
* Creates the constructor function for a component definition.
|
|
122
|
+
*
|
|
123
|
+
* @param {*} baseComponent - a component definition to inherit from.
|
|
124
|
+
* @returns the newly created component definition function.
|
|
82
125
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
adjustedIndex,
|
|
96
|
-
thisElement;
|
|
97
|
-
|
|
98
|
-
const watches = this._w;
|
|
99
|
-
const props = this.props;
|
|
100
|
-
const il = watches.length;
|
|
101
|
-
this._r = {};
|
|
102
|
-
/*
|
|
103
|
-
Watches is an array of objects with keys:
|
|
104
|
-
e: the element reference (string)
|
|
105
|
-
c: the callbacks (object)
|
|
106
|
-
?v: visibility toggle (object)
|
|
107
|
-
|
|
108
|
-
The display toggle has keys:
|
|
109
|
-
q: the query key in lookup
|
|
110
|
-
s: the number of watches to skip as their node is underneath
|
|
111
|
-
r: reversed
|
|
112
|
-
?d: detacher
|
|
126
|
+
export function createConstructor(baseComponent) {
|
|
127
|
+
const Component = function () {
|
|
128
|
+
const root = (this.el = this._n.cloneNode(true)),
|
|
129
|
+
dynamicElements = (this._e = []),
|
|
130
|
+
stash = (this._s = []),
|
|
131
|
+
previous = (this._p = []),
|
|
132
|
+
refs = (this.refs = {});
|
|
133
|
+
this.ctrl = {};
|
|
134
|
+
this.props = {};
|
|
135
|
+
this._l = this._w.length;
|
|
136
|
+
this._b(this, root, dynamicElements, stash, previous, refs);
|
|
137
|
+
};
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
e: the parent element key
|
|
118
|
-
*/
|
|
119
|
-
while (i < il) {
|
|
120
|
-
watch = watches[i];
|
|
121
|
-
element = this._e[watch.e];
|
|
122
|
-
displayToggle = watch.v;
|
|
123
|
-
i++;
|
|
124
|
-
shouldBeVisible = true;
|
|
125
|
-
if (displayToggle) {
|
|
126
|
-
lookupTrue = !!this._rq(props, displayToggle.q)[0];
|
|
127
|
-
shouldBeVisible = displayToggle.r ? lookupTrue : !lookupTrue;
|
|
128
|
-
detacher = displayToggle.d;
|
|
129
|
-
if (detacher) {
|
|
130
|
-
index = detacher.i;
|
|
131
|
-
parent = this._e[detacher.e];
|
|
132
|
-
detachedElements = this._s[detacher.s];
|
|
133
|
-
detachedElement = detachedElements[index];
|
|
134
|
-
if (shouldBeVisible && detachedElement) {
|
|
135
|
-
adjustedIndex =
|
|
136
|
-
index -
|
|
137
|
-
Object.keys(detachedElements).filter(function (k) {
|
|
138
|
-
return k < index && detachedElements[k];
|
|
139
|
-
}).length;
|
|
140
|
-
parent.insertBefore(detachedElement, parent.childNodes[adjustedIndex]);
|
|
141
|
-
detachedElements[index] = null;
|
|
142
|
-
} else if (!shouldBeVisible && !detachedElement) {
|
|
143
|
-
thisElement = this._e[watch.e];
|
|
144
|
-
parent.removeChild(thisElement);
|
|
145
|
-
detachedElements[index] = thisElement;
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
element.hidden = !shouldBeVisible;
|
|
149
|
-
}
|
|
150
|
-
if (!shouldBeVisible) {
|
|
151
|
-
i += displayToggle.s;
|
|
152
|
-
}
|
|
139
|
+
const proto = (Component.prototype = Object.create(baseComponent.prototype, {
|
|
140
|
+
constructor: {
|
|
141
|
+
value: Component
|
|
153
142
|
}
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
// This lets us assign to prototype without replacing it.
|
|
146
|
+
Object.defineProperty(Component, "methods", {
|
|
147
|
+
set: function (value) {
|
|
148
|
+
Object.assign(proto, value);
|
|
149
|
+
},
|
|
150
|
+
get: function () {
|
|
151
|
+
return proto;
|
|
156
152
|
}
|
|
157
|
-
}
|
|
158
|
-
|
|
153
|
+
});
|
|
154
|
+
Component.create = function (props, ctrl) {
|
|
155
|
+
const component = new Component();
|
|
156
|
+
component.render(props, ctrl);
|
|
157
|
+
return component;
|
|
158
|
+
};
|
|
159
|
+
Component.stubs = {} && baseComponent.stubs;
|
|
160
|
+
return Component;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
|
|
164
|
+
const ComponentDefinition = createConstructor(inheritFrom || ComponentBase);
|
|
165
|
+
const proto = ComponentDefinition.prototype;
|
|
166
|
+
throwAway.innerHTML = html;
|
|
167
|
+
//Ensure these do not clash with fields on the component itself.
|
|
168
|
+
proto._w = watches;
|
|
169
|
+
proto._q = queries;
|
|
170
|
+
proto._b = buildFunction;
|
|
171
|
+
proto._n = throwAway.content.firstChild;
|
|
172
|
+
proto.base = ComponentBase.prototype;
|
|
173
|
+
return ComponentDefinition;
|
|
174
|
+
}
|
package/lib/extend.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createConstructor } from "./component";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calls to this function which provide the 2nd argument:
|
|
5
|
+
*
|
|
6
|
+
* const Foo = extendComponent(Bar, () => <div></div>))
|
|
7
|
+
*
|
|
8
|
+
* Are modified by the Babel plugin to become this:
|
|
9
|
+
*
|
|
10
|
+
* const Foo = defineComponent(,,,,Bar);
|
|
11
|
+
*
|
|
12
|
+
* So it should never be called with 2nd arg in real life.
|
|
13
|
+
*/
|
|
14
|
+
export function extendComponent(base, componentDef) {
|
|
15
|
+
// This function call will have been replaced if 2nd arg is a valid component func.
|
|
16
|
+
// and therefore we would not receive it.
|
|
17
|
+
if (componentDef)
|
|
18
|
+
throw new Error("2nd arg to extendComponent must be a JSX arrow function");
|
|
19
|
+
return createConstructor(base);
|
|
20
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "./initCalls";
|
|
13
|
-
|
|
14
|
-
export {
|
|
15
|
-
Component,
|
|
16
|
-
defineComponent,
|
|
17
|
-
extendComponent,
|
|
18
|
-
findElement,
|
|
19
|
-
KeyedRepeater,
|
|
20
|
-
mount,
|
|
21
|
-
nestComponent,
|
|
22
|
-
onEvent,
|
|
23
|
-
protect,
|
|
24
|
-
saveRef,
|
|
25
|
-
SequentialRepeater,
|
|
26
|
-
stashMisc,
|
|
27
|
-
watch
|
|
28
|
-
};
|
|
1
|
+
export { defineComponent } from "./component";
|
|
2
|
+
export { extendComponent } from "./extend";
|
|
3
|
+
export { mount } from "./mount";
|
|
4
|
+
export { nestComponent } from "./nest";
|
|
5
|
+
export { saveRef } from "./refs";
|
|
6
|
+
export { KeyedRepeater } from "./repeaters/keyed";
|
|
7
|
+
export { SequentialRepeater } from "./repeaters/sequential";
|
|
8
|
+
export { Router, route } from "./router";
|
|
9
|
+
export { getStub } from "./stubs";
|
|
10
|
+
export { findElement, onEvent, stashMisc } from "./utils";
|
|
11
|
+
export { watch, protect } from "./watch";
|
package/lib/mount.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { replaceNode } from "./utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates and mounts a component onto an element.
|
|
5
|
+
*
|
|
6
|
+
* @param {any} elementOrId Either a string representing an id, or an element.
|
|
7
|
+
* @param {callable} componentDefinition The class of Component to create
|
|
8
|
+
* @param {object} props The props to pass to the component (optional)
|
|
9
|
+
*/
|
|
10
|
+
export function mount(elementOrId, componentDefinition, props, ctrl) {
|
|
11
|
+
const component = new componentDefinition();
|
|
12
|
+
component.render(props, ctrl);
|
|
13
|
+
const element =
|
|
14
|
+
typeof elementOrId === "string" ? document.getElementById(elementOrId) : elementOrId;
|
|
15
|
+
replaceNode(element, component.el);
|
|
16
|
+
return component;
|
|
17
|
+
}
|
package/lib/nest.js
ADDED
package/lib/refs.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function Ref(component, node, start, end) {
|
|
2
|
+
this.node = node;
|
|
3
|
+
this._c = component;
|
|
4
|
+
this._s = start;
|
|
5
|
+
this._e = end;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
Ref.prototype.update = function () {
|
|
9
|
+
this._c._u(this._s, this._e);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Saves a reference to a node (element or nested component)
|
|
14
|
+
* Returns the node.
|
|
15
|
+
*/
|
|
16
|
+
export function saveRef(node, component, refs, name, start, end) {
|
|
17
|
+
refs[name] = new Ref(component, node, start, end);
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { trimChildren } from "../utils";
|
|
2
|
+
|
|
1
3
|
/*
|
|
2
4
|
* Gets a component from the pool.
|
|
3
5
|
*/
|
|
@@ -13,21 +15,6 @@ function getComponent(pool, componentDefinition, ctrl, key, props) {
|
|
|
13
15
|
return component;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
/**
|
|
17
|
-
* Trims the unwanted child elements from the end.
|
|
18
|
-
*
|
|
19
|
-
* @param {Node} e
|
|
20
|
-
* @param {Array} childNodes
|
|
21
|
-
* @param {Int} itemsLength
|
|
22
|
-
*/
|
|
23
|
-
function trimChildren(e, childNodes, itemsLength) {
|
|
24
|
-
let lastIndex = childNodes.length - 1;
|
|
25
|
-
let keepIndex = itemsLength - 1;
|
|
26
|
-
for (let i = lastIndex; i > keepIndex; i--) {
|
|
27
|
-
e.removeChild(childNodes[i]);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
/**
|
|
32
19
|
* Pulls an item forward in an array, to replicate insertBefore.
|
|
33
20
|
* @param {Array} arr
|
|
@@ -101,48 +88,3 @@ proto.patch = function (e, items, ctrl) {
|
|
|
101
88
|
this.keys = newKeys;
|
|
102
89
|
trimChildren(e, childNodes, itemsLength);
|
|
103
90
|
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Repeats nested components, yielding from its pool sequentially.
|
|
107
|
-
*
|
|
108
|
-
* @param {componentDefinition} componentDefinition - The class ComponentDefinition to create.
|
|
109
|
-
*/
|
|
110
|
-
export function SequentialRepeater(componentDefinition) {
|
|
111
|
-
this.def = componentDefinition;
|
|
112
|
-
this.pool = []; // pool of component instances
|
|
113
|
-
this.count = 0; // Child element count
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Updates the element's childNodes to match the items.
|
|
118
|
-
* Performance is important.
|
|
119
|
-
*
|
|
120
|
-
* @param {DOMElement} e - The DOM element to patch.
|
|
121
|
-
* @param {Array} items - Array of items which will be passed as props.
|
|
122
|
-
* @param {any} ctrl - The parent item's controller.
|
|
123
|
-
*/
|
|
124
|
-
SequentialRepeater.prototype.patch = function (e, items, ctrl) {
|
|
125
|
-
const pool = this.pool;
|
|
126
|
-
const componentDefinition = this.def;
|
|
127
|
-
const childNodes = e.childNodes;
|
|
128
|
-
const itemsLength = items.length;
|
|
129
|
-
let component,
|
|
130
|
-
poolCount = pool.length,
|
|
131
|
-
childElementCount = this.count;
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < itemsLength; i++) {
|
|
134
|
-
if (i < poolCount) {
|
|
135
|
-
component = pool[i];
|
|
136
|
-
} else {
|
|
137
|
-
component = new componentDefinition();
|
|
138
|
-
pool.push(component);
|
|
139
|
-
poolCount++;
|
|
140
|
-
}
|
|
141
|
-
component.render(items[i], ctrl);
|
|
142
|
-
if (i >= childElementCount) {
|
|
143
|
-
e.appendChild(component.el);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
this.count = itemsLength;
|
|
147
|
-
trimChildren(e, childNodes, itemsLength);
|
|
148
|
-
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { trimChildren } from "../utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Repeats nested components, yielding from its pool sequentially.
|
|
5
|
+
*
|
|
6
|
+
* @param {componentDefinition} componentDefinition - The ComponentDefinition to create.
|
|
7
|
+
*/
|
|
8
|
+
export function SequentialRepeater(componentDefinition) {
|
|
9
|
+
this.def = componentDefinition;
|
|
10
|
+
this.pool = []; // pool of component instances
|
|
11
|
+
this.count = 0; // Child element count
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Updates the element's childNodes to match the items.
|
|
16
|
+
* Performance is important.
|
|
17
|
+
*
|
|
18
|
+
* @param {DOMElement} e - The DOM element to patch.
|
|
19
|
+
* @param {Array} items - Array of items which will be passed as props.
|
|
20
|
+
* @param {any} ctrl - The parent item's controller.
|
|
21
|
+
*/
|
|
22
|
+
SequentialRepeater.prototype.patch = function (e, items, ctrl) {
|
|
23
|
+
const pool = this.pool;
|
|
24
|
+
const componentDefinition = this.def;
|
|
25
|
+
const childNodes = e.childNodes;
|
|
26
|
+
const itemsLength = items.length;
|
|
27
|
+
let component,
|
|
28
|
+
poolCount = pool.length,
|
|
29
|
+
childElementCount = this.count;
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < itemsLength; i++) {
|
|
32
|
+
if (i < poolCount) {
|
|
33
|
+
component = pool[i];
|
|
34
|
+
} else {
|
|
35
|
+
component = new componentDefinition();
|
|
36
|
+
pool.push(component);
|
|
37
|
+
poolCount++;
|
|
38
|
+
}
|
|
39
|
+
component.render(items[i], ctrl);
|
|
40
|
+
if (i >= childElementCount) {
|
|
41
|
+
e.appendChild(component.el);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
this.count = itemsLength;
|
|
45
|
+
trimChildren(e, childNodes, itemsLength);
|
|
46
|
+
};
|
package/lib/router.jsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The Router is a component which mounts other components based on hash (URL bit after #).
|
|
3
|
+
|
|
4
|
+
It currently expects the route to be made of chunks separated by / which are either
|
|
5
|
+
text or placeholders:
|
|
6
|
+
|
|
7
|
+
/todos/detail/{id:int}/notes
|
|
8
|
+
|
|
9
|
+
It performs no validation yet, and may change to use regex in future.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const events = ["load", "hashchange"];
|
|
13
|
+
const noMod = x => x;
|
|
14
|
+
const converters = {
|
|
15
|
+
int: v => parseInt(v),
|
|
16
|
+
float: v => parseFloat(v),
|
|
17
|
+
date: v => new Date(v)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function route(path, componentDef, converter, cleanup) {
|
|
21
|
+
return new Route(path, componentDef, converter, cleanup);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Router = () => <div></div>;
|
|
25
|
+
|
|
26
|
+
Router.methods = {
|
|
27
|
+
render(props, ctrl) {
|
|
28
|
+
const defaultError = (error, router) => (router.el.innerHTML = error.message);
|
|
29
|
+
this.error = props.error || defaultError;
|
|
30
|
+
this.current = null;
|
|
31
|
+
events.forEach(e => window.addEventListener(e, () => this.onHashChange()));
|
|
32
|
+
if (props.atts) {
|
|
33
|
+
Object.keys(props.atts).forEach(k => {
|
|
34
|
+
this.el.setAttribute(k, props.atts[k]);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
this.base.render.call(this, props, ctrl);
|
|
38
|
+
},
|
|
39
|
+
async onHashChange() {
|
|
40
|
+
const path = location.hash.slice(1) || "",
|
|
41
|
+
routes = this.props.routes,
|
|
42
|
+
len = routes.length;
|
|
43
|
+
let i = 0,
|
|
44
|
+
routeData;
|
|
45
|
+
try {
|
|
46
|
+
while (i < len) {
|
|
47
|
+
let route = routes[i];
|
|
48
|
+
if ((routeData = route.match(path))) {
|
|
49
|
+
const component = await route.getComponent(routeData, this.ctrl);
|
|
50
|
+
this.current && this.current.cleanup();
|
|
51
|
+
this.mount(component);
|
|
52
|
+
this.current = route;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Router unable to match path "${path}"`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.error(error, this);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
mount(component) {
|
|
63
|
+
this.el.innerHTML = "";
|
|
64
|
+
this.el.appendChild(component.el);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function Route(path, def, convert, cleanup) {
|
|
69
|
+
this.chunks = path
|
|
70
|
+
.split("/")
|
|
71
|
+
.map(s => (s.startsWith("{") ? new RouteArg(s.slice(1, -1)) : s));
|
|
72
|
+
this.def = def;
|
|
73
|
+
this._convert = convert || noMod;
|
|
74
|
+
this._cleanup = cleanup || noMod;
|
|
75
|
+
this.component = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Route.prototype = {
|
|
79
|
+
match(url) {
|
|
80
|
+
const parts = url.split("?", 2),
|
|
81
|
+
hash = parts[0],
|
|
82
|
+
query = parts[1],
|
|
83
|
+
args = {},
|
|
84
|
+
definedChunksCount = this.chunks.length,
|
|
85
|
+
foundChunks = hash.split("/");
|
|
86
|
+
if (definedChunksCount !== foundChunks.length) return;
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < definedChunksCount) {
|
|
89
|
+
let definedChunk = this.chunks[i];
|
|
90
|
+
let foundChunk = foundChunks[i];
|
|
91
|
+
if (definedChunk instanceof RouteArg) {
|
|
92
|
+
args[definedChunk.name] = definedChunk.convert(foundChunk);
|
|
93
|
+
} else if (definedChunk != foundChunk) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
return { args, params: new URLSearchParams(query), url };
|
|
99
|
+
},
|
|
100
|
+
async getComponent(routeData, ctrl) {
|
|
101
|
+
if (!this.component) {
|
|
102
|
+
this.component = new this.def();
|
|
103
|
+
}
|
|
104
|
+
const props = await this._convert(routeData);
|
|
105
|
+
this.component.render(props, ctrl);
|
|
106
|
+
return this.component;
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* Allows user to delete component or perform other cleanup.
|
|
110
|
+
*/
|
|
111
|
+
cleanup() {
|
|
112
|
+
this._cleanup(this);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function RouteArg(str) {
|
|
117
|
+
let chunks = str.split(":");
|
|
118
|
+
this.name = chunks[0];
|
|
119
|
+
this.convert = converters[chunks[1]] || noMod;
|
|
120
|
+
}
|
package/lib/stubs.js
ADDED
package/lib/types.d.ts
CHANGED
|
@@ -47,9 +47,11 @@ const MyComponent = ({title}, {ctrl, event}) => (
|
|
|
47
47
|
|
|
48
48
|
The **xargs** contains:
|
|
49
49
|
|
|
50
|
-
- `ctrl`
|
|
51
|
-
- `
|
|
52
|
-
- `
|
|
50
|
+
- `ctrl` refers to the controller.
|
|
51
|
+
- `props` refers to the props, in case you want the non-destructured version too.
|
|
52
|
+
- `self` refers to the component instance (as `this` is not allowed).
|
|
53
|
+
- `event` refers to the event in an event callback.
|
|
54
|
+
- `element` refers to the element in an event callback, or in `apply`.
|
|
53
55
|
|
|
54
56
|
The function will be replaced by a very different one during compilation, therefore:
|
|
55
57
|
|
|
@@ -141,10 +143,9 @@ so use `self` from the **xargs** in component functions.
|
|
|
141
143
|
|
|
142
144
|
Component instances have the following fields:
|
|
143
145
|
|
|
144
|
-
- `props` the data for this component instance
|
|
145
|
-
|
|
146
|
-
- `
|
|
147
|
-
- `ref` a dictionary of named elements.
|
|
146
|
+
- `props` the data for this component instance.
|
|
147
|
+
- `ctrl` the controller object.
|
|
148
|
+
- `refs` an object with references to node.
|
|
148
149
|
- `el` the component instance's root element.
|
|
149
150
|
|
|
150
151
|
Both `props` and `ctrl` are set during the `render` method before calling `update`.
|
|
@@ -212,27 +213,6 @@ temporarily change it to something `class x:danger`.
|
|
|
212
213
|
|
|
213
214
|
You can define your own directives in your babel config.
|
|
214
215
|
|
|
215
|
-
Each has more
|
|
216
|
-
detailed information on its JSDoc, which should display as a tool tip\* when you hover
|
|
217
|
-
over it in your IDE.
|
|
218
|
-
|
|
219
|
-
You can also
|
|
220
|
-
|
|
221
|
-
- `apply` runs a callback to modify an element.
|
|
222
|
-
- `bind` updates a value when an input is changed.
|
|
223
|
-
- `class:xyz` defines a set of classes to be toggled.
|
|
224
|
-
- `hide` sets an element or component's hidden property.
|
|
225
|
-
- `html` Set the element's `innnerHTML` property.
|
|
226
|
-
- `if` excludes an element from the DOM.
|
|
227
|
-
- `on[EventName]` creates an event handler (note the code is copied)
|
|
228
|
-
- `props` specifes props for a nested or repeated component, in which case it must be
|
|
229
|
-
an array.
|
|
230
|
-
- `ref` saves a reference to an element or nested component.
|
|
231
|
-
- `show` sets and element or component's hidden property.
|
|
232
|
-
- `style:xyz` sets a specific style property.
|
|
233
|
-
- `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class `xyz`.
|
|
234
|
-
|
|
235
|
-
|
|
236
216
|
## 5. Controllers
|
|
237
217
|
|
|
238
218
|
A controller is just an object you create which gets passed down to every nested
|
|
@@ -516,6 +496,7 @@ declare module "wallace" {
|
|
|
516
496
|
props: Props,
|
|
517
497
|
xargs?: {
|
|
518
498
|
ctrl: Controller;
|
|
499
|
+
props: Props;
|
|
519
500
|
self: ComponentInstance<Props, Controller, Methods>;
|
|
520
501
|
event: Event;
|
|
521
502
|
element: HTMLElement;
|
|
@@ -539,12 +520,13 @@ declare module "wallace" {
|
|
|
539
520
|
show?: boolean;
|
|
540
521
|
hide?: boolean;
|
|
541
522
|
}): JSX.Element;
|
|
542
|
-
|
|
543
|
-
|
|
523
|
+
methods?: ComponenMethods<Props, Controller> &
|
|
524
|
+
ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
544
525
|
readonly prototype?: ComponenMethods<Props, Controller> &
|
|
545
526
|
ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
546
527
|
// Methods will not be available on nested component, so omit.
|
|
547
528
|
readonly stubs?: Record<string, ComponentFunction<Props, Controller>>;
|
|
529
|
+
create?(props: Props): ComponentInstance<Props, Controller, Methods>;
|
|
548
530
|
}
|
|
549
531
|
|
|
550
532
|
type ComponenMethods<Props, Controller> = {
|
|
@@ -574,6 +556,11 @@ declare module "wallace" {
|
|
|
574
556
|
Methods extends object = {}
|
|
575
557
|
> = ComponentFunction<Props, Controller, Methods>;
|
|
576
558
|
|
|
559
|
+
export interface Ref {
|
|
560
|
+
node: HTMLElement | ComponentInstance;
|
|
561
|
+
update(): void;
|
|
562
|
+
}
|
|
563
|
+
|
|
577
564
|
/**
|
|
578
565
|
* The type for a component instance.
|
|
579
566
|
*/
|
|
@@ -585,7 +572,7 @@ declare module "wallace" {
|
|
|
585
572
|
el: HTMLElement;
|
|
586
573
|
props: Props;
|
|
587
574
|
ctrl: Controller;
|
|
588
|
-
|
|
575
|
+
refs: { [key: string]: Ref };
|
|
589
576
|
base: Component<Props, Controller>;
|
|
590
577
|
} & Component<Props, Controller> &
|
|
591
578
|
Methods;
|
|
@@ -594,7 +581,6 @@ declare module "wallace" {
|
|
|
594
581
|
* The component constructor function (typed as a class, but isn't).
|
|
595
582
|
*/
|
|
596
583
|
export class Component<Props = any, Controller = any> {
|
|
597
|
-
render(props: Props, ctrl?: Controller): void;
|
|
598
584
|
/**
|
|
599
585
|
* The base render method looks like this:
|
|
600
586
|
*
|
|
@@ -705,6 +691,32 @@ declare module "wallace" {
|
|
|
705
691
|
* @returns a Proxy of the object.
|
|
706
692
|
*/
|
|
707
693
|
export function watch<T>(target: T, callback: WatchCallback): T;
|
|
694
|
+
|
|
695
|
+
export type RouteData = {
|
|
696
|
+
args: { [key: string]: any };
|
|
697
|
+
params: URLSearchParams;
|
|
698
|
+
url: string;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
export function route<Props>(
|
|
702
|
+
path: string,
|
|
703
|
+
componentDef: ComponentFunction<Props>,
|
|
704
|
+
converter: RouteConverter<Props>
|
|
705
|
+
): Route<Props>;
|
|
706
|
+
|
|
707
|
+
type RouteConverter<Props> = (routedata: RouteData) => Props;
|
|
708
|
+
|
|
709
|
+
export type Route<Props> = [string, ComponentFunction<Props>, RouteConverter<Props>?];
|
|
710
|
+
export type RouterProps = {
|
|
711
|
+
routes: readonly Route<unknown>[];
|
|
712
|
+
atts?: Record<string, unknown>;
|
|
713
|
+
error?: (error: Error, router: Router) => void;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
export class Router extends Component {
|
|
717
|
+
static nest?({ props }: { props?: RouterProps }): JSX.Element;
|
|
718
|
+
mount(component: Component<any>): void;
|
|
719
|
+
}
|
|
708
720
|
}
|
|
709
721
|
|
|
710
722
|
type MustBeExpression = Exclude<any, string>;
|
|
@@ -864,19 +876,25 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
864
876
|
/**
|
|
865
877
|
* ## Wallace directive: ref
|
|
866
878
|
*
|
|
867
|
-
* Saves a reference to
|
|
879
|
+
* Saves a reference to a node allowing you to:
|
|
880
|
+
*
|
|
881
|
+
* 1. Access the element or component as `ref.node`
|
|
882
|
+
* 2. Update the all nested elements using `ref.update()`.
|
|
868
883
|
*
|
|
869
884
|
* ```
|
|
870
|
-
* <div ref:title
|
|
885
|
+
* <div ref:title>
|
|
886
|
+
* {name}
|
|
887
|
+
* </div>
|
|
871
888
|
* ```
|
|
872
889
|
*
|
|
873
890
|
* ```
|
|
874
|
-
* component.
|
|
891
|
+
* component.refs.title.update();
|
|
892
|
+
* component.refs.title.node.textContent = 'hello';
|
|
875
893
|
* ```
|
|
876
894
|
*
|
|
877
895
|
* Requires a qualifier, but you lose the tooltip in that format.
|
|
878
896
|
*/
|
|
879
|
-
ref?:
|
|
897
|
+
ref?: null;
|
|
880
898
|
|
|
881
899
|
/** ## Wallace directive: show
|
|
882
900
|
*
|
|
@@ -949,7 +967,7 @@ declare namespace JSX {
|
|
|
949
967
|
* - `items` set items for repeated component, must be an array of props.
|
|
950
968
|
* - `on[EventName]` creates an event handler (note the code is copied)
|
|
951
969
|
* - `props` specifes props for a nested components.
|
|
952
|
-
* - `ref` saves a reference to
|
|
970
|
+
* - `ref:xyz` saves a reference to node, allowing partial updates or access to element/component.
|
|
953
971
|
* - `show` sets and element or component's hidden property.
|
|
954
972
|
* - `style:xyz` sets a specific style property.
|
|
955
973
|
* - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
|
package/lib/utils.js
CHANGED
|
@@ -1,66 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* @param {any} elementOrId Either a string representing an id, or an element.
|
|
5
|
-
* @param {callable} componentDefinition The class of Component to create
|
|
6
|
-
* @param {object} props The props to pass to the component (optional)
|
|
7
|
-
*/
|
|
8
|
-
export function mount(elementOrId, componentDefinition, props, ctrl) {
|
|
9
|
-
const component = new componentDefinition();
|
|
10
|
-
component.render(props, ctrl);
|
|
11
|
-
replaceNode(getElement(elementOrId), component.el);
|
|
12
|
-
return component;
|
|
1
|
+
export function findElement(rootElement, path) {
|
|
2
|
+
return path.reduce((acc, index) => acc.childNodes[index], rootElement);
|
|
13
3
|
}
|
|
14
4
|
|
|
15
5
|
export function replaceNode(nodeToReplace, newNode) {
|
|
16
6
|
nodeToReplace.parentNode.replaceChild(newNode, nodeToReplace);
|
|
17
7
|
}
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Trims the unwanted child elements from the end.
|
|
11
|
+
*
|
|
12
|
+
* @param {Node} e
|
|
13
|
+
* @param {Array} childNodes
|
|
14
|
+
* @param {Int} itemsLength
|
|
15
|
+
*/
|
|
16
|
+
export function trimChildren(e, childNodes, itemsLength) {
|
|
17
|
+
let lastIndex = childNodes.length - 1;
|
|
18
|
+
let keepIndex = itemsLength - 1;
|
|
19
|
+
for (let i = lastIndex; i > keepIndex; i--) {
|
|
20
|
+
e.removeChild(childNodes[i]);
|
|
21
|
+
}
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
const MUTATING_METHODS = ["push", "pop", "shift", "unshift", "splice", "reverse", "sort"];
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Note that proxies have property `isProxy` set to true.
|
|
25
|
+
* Stash something on the component. Returns the element.
|
|
26
|
+
* The generated code is expected to keep track of the position.
|
|
31
27
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (key == "isProxy") return true;
|
|
36
|
-
const prop = target[key];
|
|
37
|
-
if (typeof prop == "undefined") return;
|
|
38
|
-
if (typeof prop === "object") return new Proxy(prop, handler);
|
|
39
|
-
if (
|
|
40
|
-
Array.isArray(target) &&
|
|
41
|
-
typeof target[key] === "function" &&
|
|
42
|
-
MUTATING_METHODS.includes(key)
|
|
43
|
-
) {
|
|
44
|
-
return (...args) => {
|
|
45
|
-
const result = target[key](...args);
|
|
46
|
-
callback(target, key, args);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return prop;
|
|
51
|
-
},
|
|
52
|
-
set(target, key, value) {
|
|
53
|
-
target[key] = value;
|
|
54
|
-
callback(target, key, value);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
return new Proxy(target, handler);
|
|
28
|
+
export function stashMisc(element, stash, object) {
|
|
29
|
+
stash.push(object);
|
|
30
|
+
return element;
|
|
59
31
|
}
|
|
60
32
|
|
|
61
|
-
export function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
throw new Error("Attempted to modify protected object");
|
|
65
|
-
});
|
|
33
|
+
export function onEvent(element, eventName, callback) {
|
|
34
|
+
element.addEventListener(eventName, callback);
|
|
35
|
+
return element;
|
|
66
36
|
}
|
package/lib/watch.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const MUTATING_METHODS = ["push", "pop", "shift", "unshift", "splice", "reverse", "sort"];
|
|
2
|
+
/**
|
|
3
|
+
* Returns a proxy which calls a callback when the object or its nested objects are
|
|
4
|
+
* modified.
|
|
5
|
+
*
|
|
6
|
+
* Note that proxies have property `isProxy` set to true.
|
|
7
|
+
*/
|
|
8
|
+
export function watch(target, callback) {
|
|
9
|
+
const handler = {
|
|
10
|
+
get(target, key) {
|
|
11
|
+
if (key == "isProxy") return true;
|
|
12
|
+
const prop = target[key];
|
|
13
|
+
if (typeof prop == "undefined") return;
|
|
14
|
+
if (typeof prop === "object") return new Proxy(prop, handler);
|
|
15
|
+
if (
|
|
16
|
+
Array.isArray(target) &&
|
|
17
|
+
typeof target[key] === "function" &&
|
|
18
|
+
MUTATING_METHODS.includes(key)
|
|
19
|
+
) {
|
|
20
|
+
return (...args) => {
|
|
21
|
+
const result = target[key](...args);
|
|
22
|
+
callback(target, key, args);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return prop;
|
|
27
|
+
},
|
|
28
|
+
set(target, key, value) {
|
|
29
|
+
target[key] = value;
|
|
30
|
+
callback(target, key, value);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return new Proxy(target, handler);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function protect(obj) {
|
|
38
|
+
return watch(obj, (target, key, value) => {
|
|
39
|
+
console.log("target", target, "key", key, "value", value);
|
|
40
|
+
throw new Error("Attempted to modify protected object");
|
|
41
|
+
});
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wallace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"author": "Andrew Buchan",
|
|
5
5
|
"description": "The framework that brings you FREEDOM!!",
|
|
6
6
|
"license": "ISC",
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"/lib"
|
|
10
10
|
],
|
|
11
|
+
"sideEffects": false,
|
|
11
12
|
"types": "./lib/types.d.ts",
|
|
12
13
|
"scripts": {
|
|
13
14
|
"test": "jest --clearCache && jest"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"babel-plugin-wallace": "^0.
|
|
17
|
+
"babel-plugin-wallace": "^0.7.0",
|
|
17
18
|
"browserify": "^17.0.1"
|
|
18
19
|
},
|
|
19
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "6cd84a9e6135e31d01880a82e6294ae0034bca59"
|
|
20
21
|
}
|
package/lib/initCalls.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Everything in here is used by or modified by the Babel plugin.
|
|
3
|
-
*/
|
|
4
|
-
import { Component } from "./component";
|
|
5
|
-
import { replaceNode } from "./utils";
|
|
6
|
-
|
|
7
|
-
const throwAway = document.createElement("template");
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A utility function that has to be in here because it needs _createConstructor and
|
|
11
|
-
* we'd otherwise get cirular inmports.
|
|
12
|
-
*
|
|
13
|
-
* Calls to this function which provide the 2nd argument:
|
|
14
|
-
*
|
|
15
|
-
* const Foo = extendComponent(Bar, () => <div></div>))
|
|
16
|
-
*
|
|
17
|
-
* Are modified by the Babel plugin to become this:
|
|
18
|
-
*
|
|
19
|
-
* const Foo = defineComponent(,,,,Bar);
|
|
20
|
-
*
|
|
21
|
-
* So it should never be called with 2nd arg in real life.
|
|
22
|
-
*/
|
|
23
|
-
export function extendComponent(base, componentDef) {
|
|
24
|
-
// This function call will have been replaced if 2nd arg is a valid component func.
|
|
25
|
-
// and therefor we would not receive it.
|
|
26
|
-
if (componentDef)
|
|
27
|
-
throw new Error("2nd arg to extendComponent must be a JSX arrow function");
|
|
28
|
-
return _createConstructor(base);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
Everything after this is used by code generated by the Babel plugin.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
export function findElement(rootElement, path) {
|
|
36
|
-
return path.reduce((acc, index) => acc.childNodes[index], rootElement);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function nestComponent(rootElement, path, componentDefinition) {
|
|
40
|
-
const el = findElement(rootElement, path),
|
|
41
|
-
child = new componentDefinition();
|
|
42
|
-
replaceNode(el, child.el);
|
|
43
|
-
return child;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Saves a reference to element or nested component. Returns the element.
|
|
48
|
-
*/
|
|
49
|
-
export function saveRef(element, component, name) {
|
|
50
|
-
component.ref[name] = element;
|
|
51
|
-
return element;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Stash something on the component. Returns the element.
|
|
56
|
-
* The generated code is expected to keep track of the position.
|
|
57
|
-
*/
|
|
58
|
-
export function stashMisc(element, component, object) {
|
|
59
|
-
component._s.push(object);
|
|
60
|
-
return element;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function onEvent(element, eventName, callback) {
|
|
64
|
-
element.addEventListener(eventName, callback);
|
|
65
|
-
return element;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
|
|
69
|
-
const ComponentDefinition = _createConstructor(inheritFrom || Component);
|
|
70
|
-
const prototype = ComponentDefinition.prototype;
|
|
71
|
-
throwAway.innerHTML = html;
|
|
72
|
-
//Ensure these do not clash with fields on the component itself.
|
|
73
|
-
prototype._w = watches;
|
|
74
|
-
prototype._q = queries;
|
|
75
|
-
prototype._b = buildFunction;
|
|
76
|
-
prototype._n = throwAway.content.firstChild;
|
|
77
|
-
return ComponentDefinition;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Creates a new component definition.
|
|
82
|
-
*
|
|
83
|
-
* @param {*} base - a component definition to inherit from - defaults to Component.
|
|
84
|
-
* @returns the newly created component definition function.
|
|
85
|
-
*/
|
|
86
|
-
function _createConstructor(base) {
|
|
87
|
-
const ComponentDefinition = function () {
|
|
88
|
-
base.call(this);
|
|
89
|
-
};
|
|
90
|
-
const proto = Object.create(base && base.prototype, {
|
|
91
|
-
constructor: {
|
|
92
|
-
value: ComponentDefinition,
|
|
93
|
-
writable: true,
|
|
94
|
-
configurable: true
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
ComponentDefinition.prototype = proto;
|
|
98
|
-
|
|
99
|
-
// methods lets us assign to prototype without replacing it.
|
|
100
|
-
Object.defineProperty(ComponentDefinition, "methods", {
|
|
101
|
-
set: function (value) {
|
|
102
|
-
Object.assign(proto, value);
|
|
103
|
-
},
|
|
104
|
-
get: function () {
|
|
105
|
-
return proto;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Set up stubs
|
|
110
|
-
ComponentDefinition.stubs = {};
|
|
111
|
-
Object.assign(ComponentDefinition.stubs, base.stubs);
|
|
112
|
-
|
|
113
|
-
// Helper to access base prototype.
|
|
114
|
-
proto.base = Component.prototype;
|
|
115
|
-
return ComponentDefinition;
|
|
116
|
-
}
|