wallace 0.5.0 → 0.6.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 +151 -139
- package/lib/extend.js +20 -0
- package/lib/index.js +10 -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/stubs.js +6 -0
- package/lib/types.d.ts +28 -36
- 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,170 @@
|
|
|
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)
|
|
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
|
+
};
|
|
107
138
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
r: reversed
|
|
112
|
-
?d: detacher
|
|
113
|
-
|
|
114
|
-
The detacher has keys:
|
|
115
|
-
i: the initial element index
|
|
116
|
-
s: the stash key of the detacher (plain object)
|
|
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
|
+
|
|
155
|
+
Component.stubs = {} && baseComponent.stubs;
|
|
156
|
+
return Component;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
|
|
160
|
+
const ComponentDefinition = createConstructor(inheritFrom || ComponentBase);
|
|
161
|
+
const proto = ComponentDefinition.prototype;
|
|
162
|
+
throwAway.innerHTML = html;
|
|
163
|
+
//Ensure these do not clash with fields on the component itself.
|
|
164
|
+
proto._w = watches;
|
|
165
|
+
proto._q = queries;
|
|
166
|
+
proto._b = buildFunction;
|
|
167
|
+
proto._n = throwAway.content.firstChild;
|
|
168
|
+
proto.base = ComponentBase.prototype;
|
|
169
|
+
return ComponentDefinition;
|
|
170
|
+
}
|
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,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
stashMisc
|
|
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 { getStub } from "./stubs";
|
|
9
|
+
export { findElement, onEvent, stashMisc } from "./utils";
|
|
10
|
+
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/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,8 +520,8 @@ 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.
|
|
@@ -574,6 +555,11 @@ declare module "wallace" {
|
|
|
574
555
|
Methods extends object = {}
|
|
575
556
|
> = ComponentFunction<Props, Controller, Methods>;
|
|
576
557
|
|
|
558
|
+
export interface Ref {
|
|
559
|
+
node: HTMLElement | ComponentInstance;
|
|
560
|
+
update(): void;
|
|
561
|
+
}
|
|
562
|
+
|
|
577
563
|
/**
|
|
578
564
|
* The type for a component instance.
|
|
579
565
|
*/
|
|
@@ -585,7 +571,7 @@ declare module "wallace" {
|
|
|
585
571
|
el: HTMLElement;
|
|
586
572
|
props: Props;
|
|
587
573
|
ctrl: Controller;
|
|
588
|
-
|
|
574
|
+
refs: { [key: string]: Ref };
|
|
589
575
|
base: Component<Props, Controller>;
|
|
590
576
|
} & Component<Props, Controller> &
|
|
591
577
|
Methods;
|
|
@@ -864,19 +850,25 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
864
850
|
/**
|
|
865
851
|
* ## Wallace directive: ref
|
|
866
852
|
*
|
|
867
|
-
* Saves a reference to
|
|
853
|
+
* Saves a reference to a node allowing you to:
|
|
854
|
+
*
|
|
855
|
+
* 1. Access the element or component as `ref.node`
|
|
856
|
+
* 2. Update the all nested elements using `ref.update()`.
|
|
868
857
|
*
|
|
869
858
|
* ```
|
|
870
|
-
* <div ref:title
|
|
859
|
+
* <div ref:title>
|
|
860
|
+
* {name}
|
|
861
|
+
* </div>
|
|
871
862
|
* ```
|
|
872
863
|
*
|
|
873
864
|
* ```
|
|
874
|
-
* component.
|
|
865
|
+
* component.refs.title.update();
|
|
866
|
+
* component.refs.title.node.textContent = 'hello';
|
|
875
867
|
* ```
|
|
876
868
|
*
|
|
877
869
|
* Requires a qualifier, but you lose the tooltip in that format.
|
|
878
870
|
*/
|
|
879
|
-
ref?:
|
|
871
|
+
ref?: null;
|
|
880
872
|
|
|
881
873
|
/** ## Wallace directive: show
|
|
882
874
|
*
|
|
@@ -949,7 +941,7 @@ declare namespace JSX {
|
|
|
949
941
|
* - `items` set items for repeated component, must be an array of props.
|
|
950
942
|
* - `on[EventName]` creates an event handler (note the code is copied)
|
|
951
943
|
* - `props` specifes props for a nested components.
|
|
952
|
-
* - `ref` saves a reference to
|
|
944
|
+
* - `ref:xyz` saves a reference to node, allowing partial updates or access to element/component.
|
|
953
945
|
* - `show` sets and element or component's hidden property.
|
|
954
946
|
* - `style:xyz` sets a specific style property.
|
|
955
947
|
* - `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.6.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.6.0",
|
|
17
18
|
"browserify": "^17.0.1"
|
|
18
19
|
},
|
|
19
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "451efab81055751fbda64eb80252ec150460e375"
|
|
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
|
-
}
|