wallace 0.4.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/README.md +29 -0
- package/lib/component.js +151 -135
- package/lib/extend.js +20 -0
- package/lib/index.js +10 -31
- 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 -62
- package/lib/repeaters/sequential.js +46 -0
- package/lib/stubs.js +6 -0
- package/lib/types.d.ts +118 -63
- package/lib/utils.js +22 -59
- package/lib/watch.js +42 -0
- package/package.json +4 -3
- package/lib/initCalls.js +0 -108
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Wallace
|
|
2
|
+
|
|
3
|
+
This package contains the library for the [Wallace](https://github.com/wallace-js/wallace) framework, which you import into your source files:
|
|
4
|
+
|
|
5
|
+
```jsx
|
|
6
|
+
import { mount } from "wallace";
|
|
7
|
+
|
|
8
|
+
const MyComponent = () => <div>Hello world</div>;
|
|
9
|
+
|
|
10
|
+
mount("main", Component);
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It requires the [babel-plugin-wallace](https://www.npmjs.com/package/babel-plugin-wallace) to work, which is a dependency of this package, always at the same version.
|
|
14
|
+
|
|
15
|
+
Although you can install these packages with:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npm i wallace -D
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You are better off creating an empty project with:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
npx create-wallace-app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
As that sets up your babel and webpack configurations for you.
|
|
28
|
+
|
|
29
|
+
For more detailed documentation see the [Wallace repository on github](https://github.com/wallace-js/wallace).
|
package/lib/component.js
CHANGED
|
@@ -1,154 +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._p = {}; // The previous values for watches to compare against.
|
|
15
|
-
this._r = {}; // The current values read during an update.
|
|
16
|
-
}
|
|
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
|
+
},
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Updates the DOM and renders nested components.
|
|
18
|
+
*/
|
|
19
|
+
update: function () {
|
|
20
|
+
this._u(0, this._l);
|
|
21
|
+
},
|
|
19
22
|
|
|
20
|
-
|
|
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;
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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)
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
45
|
+
The callback is an object whose key is the lookup and value is a function which
|
|
46
|
+
applies the effect.
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const run = this._r;
|
|
38
|
-
if (run[key] === undefined) {
|
|
39
|
-
let oldValue = this._p[key];
|
|
40
|
-
const newValue = this._q[key](props, this);
|
|
41
|
-
this._p[key] = newValue;
|
|
42
|
-
const rtn = [newValue, oldValue, newValue !== oldValue];
|
|
43
|
-
run[key] = rtn;
|
|
44
|
-
return rtn;
|
|
45
|
-
}
|
|
46
|
-
return run[key];
|
|
47
|
-
};
|
|
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
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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++;
|
|
61
109
|
}
|
|
62
110
|
}
|
|
63
111
|
}
|
|
64
112
|
};
|
|
65
113
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.ctrl = ctrl;
|
|
72
|
-
this.update();
|
|
73
|
-
};
|
|
114
|
+
Object.defineProperty(ComponentBase.prototype, "hidden", {
|
|
115
|
+
set: function (value) {
|
|
116
|
+
this.el.hidden = value;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
74
119
|
|
|
75
120
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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.
|
|
78
125
|
*/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
adjustedIndex,
|
|
92
|
-
thisElement;
|
|
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
|
+
};
|
|
93
138
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this._r = {};
|
|
98
|
-
/*
|
|
99
|
-
Watches is an array of objects with keys:
|
|
100
|
-
e: the element reference (string)
|
|
101
|
-
c: the callbacks (object)
|
|
102
|
-
?v: visibility toggle (object)
|
|
103
|
-
|
|
104
|
-
The display toggle has keys:
|
|
105
|
-
q: the query key in lookup
|
|
106
|
-
s: the number of watches to skip as their node is underneath
|
|
107
|
-
r: reversed
|
|
108
|
-
?d: detacher
|
|
109
|
-
|
|
110
|
-
The detacher has keys:
|
|
111
|
-
i: the initial element index
|
|
112
|
-
s: the stash key of the detacher (plain object)
|
|
113
|
-
e: the parent element key
|
|
114
|
-
*/
|
|
115
|
-
while (i < il) {
|
|
116
|
-
watch = watches[i];
|
|
117
|
-
element = this._e[watch.e];
|
|
118
|
-
displayToggle = watch.v;
|
|
119
|
-
i++;
|
|
120
|
-
shouldBeVisible = true;
|
|
121
|
-
if (displayToggle) {
|
|
122
|
-
lookupTrue = !!this._rq(props, displayToggle.q)[0];
|
|
123
|
-
shouldBeVisible = displayToggle.r ? lookupTrue : !lookupTrue;
|
|
124
|
-
detacher = displayToggle.d;
|
|
125
|
-
if (detacher) {
|
|
126
|
-
index = detacher.i;
|
|
127
|
-
parent = this._e[detacher.e];
|
|
128
|
-
detachedElements = this._s[detacher.s];
|
|
129
|
-
detachedElement = detachedElements[index];
|
|
130
|
-
if (shouldBeVisible && detachedElement) {
|
|
131
|
-
adjustedIndex =
|
|
132
|
-
index -
|
|
133
|
-
Object.keys(detachedElements).filter(function (k) {
|
|
134
|
-
return k < index && detachedElements[k];
|
|
135
|
-
}).length;
|
|
136
|
-
parent.insertBefore(detachedElement, parent.childNodes[adjustedIndex]);
|
|
137
|
-
detachedElements[index] = null;
|
|
138
|
-
} else if (!shouldBeVisible && !detachedElement) {
|
|
139
|
-
thisElement = this._e[watch.e];
|
|
140
|
-
parent.removeChild(thisElement);
|
|
141
|
-
detachedElements[index] = thisElement;
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
element.hidden = !shouldBeVisible;
|
|
145
|
-
}
|
|
146
|
-
if (!shouldBeVisible) {
|
|
147
|
-
i += displayToggle.s;
|
|
148
|
-
}
|
|
139
|
+
const proto = (Component.prototype = Object.create(baseComponent.prototype, {
|
|
140
|
+
constructor: {
|
|
141
|
+
value: Component
|
|
149
142
|
}
|
|
150
|
-
|
|
151
|
-
|
|
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;
|
|
152
152
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
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,31 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
nestComponent,
|
|
12
|
-
saveRef,
|
|
13
|
-
stashMisc
|
|
14
|
-
} from "./initCalls";
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
Component,
|
|
18
|
-
defineComponent,
|
|
19
|
-
extendComponent,
|
|
20
|
-
findElement,
|
|
21
|
-
getKeyedRepeater,
|
|
22
|
-
getSequentialRepeater,
|
|
23
|
-
KeyedRepeater,
|
|
24
|
-
mount,
|
|
25
|
-
nestComponent,
|
|
26
|
-
onEvent,
|
|
27
|
-
saveRef,
|
|
28
|
-
SequentialRepeater,
|
|
29
|
-
stashMisc,
|
|
30
|
-
watch
|
|
31
|
-
};
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trimChildren } from "../utils";
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* Gets a component from the pool.
|
|
@@ -8,28 +8,13 @@ function getComponent(pool, componentDefinition, ctrl, key, props) {
|
|
|
8
8
|
if (pool.hasOwnProperty(key)) {
|
|
9
9
|
component = pool[key];
|
|
10
10
|
} else {
|
|
11
|
-
component =
|
|
11
|
+
component = new componentDefinition();
|
|
12
12
|
pool[key] = component;
|
|
13
13
|
}
|
|
14
14
|
component.render(props, ctrl);
|
|
15
15
|
return component;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Trims the unwanted child elements from the end.
|
|
20
|
-
*
|
|
21
|
-
* @param {Node} e
|
|
22
|
-
* @param {Array} childNodes
|
|
23
|
-
* @param {Int} itemsLength
|
|
24
|
-
*/
|
|
25
|
-
function trimChildren(e, childNodes, itemsLength) {
|
|
26
|
-
let lastIndex = childNodes.length - 1;
|
|
27
|
-
let keepIndex = itemsLength - 1;
|
|
28
|
-
for (let i = lastIndex; i > keepIndex; i--) {
|
|
29
|
-
e.removeChild(childNodes[i]);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
/**
|
|
34
19
|
* Pulls an item forward in an array, to replicate insertBefore.
|
|
35
20
|
* @param {Array} arr
|
|
@@ -103,48 +88,3 @@ proto.patch = function (e, items, ctrl) {
|
|
|
103
88
|
this.keys = newKeys;
|
|
104
89
|
trimChildren(e, childNodes, itemsLength);
|
|
105
90
|
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Repeats nested components, yielding from its pool sequentially.
|
|
109
|
-
*
|
|
110
|
-
* @param {componentDefinition} componentDefinition - The class ComponentDefinition to create.
|
|
111
|
-
*/
|
|
112
|
-
export function SequentialRepeater(componentDefinition) {
|
|
113
|
-
this.def = componentDefinition;
|
|
114
|
-
this.pool = []; // pool of component instances
|
|
115
|
-
this.count = 0; // Child element count
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Updates the element's childNodes to match the items.
|
|
120
|
-
* Performance is important.
|
|
121
|
-
*
|
|
122
|
-
* @param {DOMElement} e - The DOM element to patch.
|
|
123
|
-
* @param {Array} items - Array of items which will be passed as props.
|
|
124
|
-
* @param {any} ctrl - The parent item's controller.
|
|
125
|
-
*/
|
|
126
|
-
SequentialRepeater.prototype.patch = function (e, items, ctrl) {
|
|
127
|
-
const pool = this.pool;
|
|
128
|
-
const componentDefinition = this.def;
|
|
129
|
-
const childNodes = e.childNodes;
|
|
130
|
-
const itemsLength = items.length;
|
|
131
|
-
let component,
|
|
132
|
-
poolCount = pool.length,
|
|
133
|
-
childElementCount = this.count;
|
|
134
|
-
|
|
135
|
-
for (let i = 0; i < itemsLength; i++) {
|
|
136
|
-
if (i < poolCount) {
|
|
137
|
-
component = pool[i];
|
|
138
|
-
} else {
|
|
139
|
-
component = buildComponent(componentDefinition);
|
|
140
|
-
pool.push(component);
|
|
141
|
-
poolCount++;
|
|
142
|
-
}
|
|
143
|
-
component.render(items[i], ctrl);
|
|
144
|
-
if (i >= childElementCount) {
|
|
145
|
-
e.appendChild(component.el);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
this.count = itemsLength;
|
|
149
|
-
trimChildren(e, childNodes, itemsLength);
|
|
150
|
-
};
|
|
@@ -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
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
6. Inheritance
|
|
17
17
|
7. Stubs
|
|
18
18
|
8. TypeScript
|
|
19
|
-
9.
|
|
19
|
+
9. Helpers
|
|
20
20
|
|
|
21
21
|
For more detailed documentation go to https://github.com/wallace-js/wallace
|
|
22
22
|
|
|
@@ -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
|
|
|
@@ -71,7 +73,7 @@ The arguments are:
|
|
|
71
73
|
3. props for the element (optional)
|
|
72
74
|
4. controller (optional)
|
|
73
75
|
|
|
74
|
-
`mount` returns the component instance, allowing you to call
|
|
76
|
+
`mount` returns the component instance, allowing you to call its methods:
|
|
75
77
|
|
|
76
78
|
```tsx
|
|
77
79
|
root.update();
|
|
@@ -100,11 +102,11 @@ places.
|
|
|
100
102
|
|
|
101
103
|
#### Overriding
|
|
102
104
|
|
|
103
|
-
You can override these methods, and add new ones using `methods`
|
|
105
|
+
You can override these methods, and add new ones using `methods` property of the
|
|
104
106
|
component definition:
|
|
105
107
|
|
|
106
108
|
```tsx
|
|
107
|
-
MyComponent.methods
|
|
109
|
+
MyComponent.methods = {
|
|
108
110
|
render(props) {
|
|
109
111
|
this.ctrl = new MyController(this, props);
|
|
110
112
|
this.update();
|
|
@@ -112,7 +114,7 @@ MyComponent.methods({
|
|
|
112
114
|
getName() {
|
|
113
115
|
return 'wallace';
|
|
114
116
|
}
|
|
115
|
-
}
|
|
117
|
+
};
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
This has the same effect as setting them on the prototype:
|
|
@@ -124,11 +126,11 @@ MyComponent.prototype.render = function () {};
|
|
|
124
126
|
You can use `this.base` to access methods on the base `Component` class:
|
|
125
127
|
|
|
126
128
|
```tsx
|
|
127
|
-
MyComponent.methods
|
|
129
|
+
MyComponent.methods = {
|
|
128
130
|
render(props) {
|
|
129
131
|
this.base.render.call(this, props, ctrl);
|
|
130
132
|
}
|
|
131
|
-
}
|
|
133
|
+
};
|
|
132
134
|
```
|
|
133
135
|
|
|
134
136
|
Note that `base` is not the same as `super` in classes which access the lowest override.
|
|
@@ -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`.
|
|
@@ -201,27 +202,16 @@ Notes:
|
|
|
201
202
|
|
|
202
203
|
## 4. Directives
|
|
203
204
|
|
|
204
|
-
Directives are attributes with special behaviours
|
|
205
|
-
detailed information on its JSDoc, which should display as a tool tip\* when you hover
|
|
206
|
-
over it in your IDE.
|
|
205
|
+
Directives are attributes with special behaviours.
|
|
207
206
|
|
|
208
|
-
You can
|
|
207
|
+
You can see the list of available directives by hovering over any JSX element, like
|
|
208
|
+
a `div`
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
- `hide` sets an element or component's hidden property.
|
|
214
|
-
- `html` Set the element's `innnerHTML` property.
|
|
215
|
-
- `if` excludes an element from the DOM.
|
|
216
|
-
- `on[EventName]` creates an event handler (note the code is copied)
|
|
217
|
-
- `props` specifes props for a nested or repeated component, in which case it must be
|
|
218
|
-
an array.
|
|
219
|
-
- `ref` saves a reference to an element or nested component.
|
|
220
|
-
- `show` sets and element or component's hidden property.
|
|
221
|
-
- `style:xyz` sets a specific style property.
|
|
222
|
-
- `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class `xyz`.
|
|
210
|
+
You will get more details by hovering on the directive itself, but unfortunetely the
|
|
211
|
+
tool tip won't display when you use a qualifier, like `class:danger`. To see it you can
|
|
212
|
+
temporarily change it to something `class x:danger`.
|
|
223
213
|
|
|
224
|
-
|
|
214
|
+
You can define your own directives in your babel config.
|
|
225
215
|
|
|
226
216
|
## 5. Controllers
|
|
227
217
|
|
|
@@ -266,12 +256,12 @@ const TaskList = (_, {ctrl}) => (
|
|
|
266
256
|
</div>
|
|
267
257
|
);
|
|
268
258
|
|
|
269
|
-
TaskList.methods
|
|
259
|
+
TaskList.methods = {
|
|
270
260
|
render(_, ctrl) {
|
|
271
261
|
this.ctrl = new TaskController(this, ctrl);
|
|
272
262
|
this.update();
|
|
273
263
|
}
|
|
274
|
-
}
|
|
264
|
+
};
|
|
275
265
|
```
|
|
276
266
|
|
|
277
267
|
## 6. Inheritance
|
|
@@ -392,12 +382,12 @@ const Task: Uses<null, null, TaskMethods> = (_, { self }) => (
|
|
|
392
382
|
<div>{self.getName()}</div>
|
|
393
383
|
));
|
|
394
384
|
|
|
395
|
-
Task.methods
|
|
385
|
+
Task.methods = {
|
|
396
386
|
getName() { return 'wallace' },
|
|
397
387
|
render(props, ctrl) { // types are already known
|
|
398
388
|
this.props = { ...props, notallowed: 1 }; // type error
|
|
399
389
|
}
|
|
400
|
-
}
|
|
390
|
+
};
|
|
401
391
|
```
|
|
402
392
|
|
|
403
393
|
The type will pass into the object passed into `methods` so it recognises custom methods
|
|
@@ -445,7 +435,7 @@ Wallace defines some other types you may use:
|
|
|
445
435
|
constructor, not a class)
|
|
446
436
|
- `ComponentInstance<Props, Controller, Methods>` - a component instance.
|
|
447
437
|
|
|
448
|
-
##
|
|
438
|
+
## 9. Helpers
|
|
449
439
|
|
|
450
440
|
Each of these has their own JSDoc, we just lsit them here.
|
|
451
441
|
|
|
@@ -469,11 +459,24 @@ mount("elementId", MyComponent, props, ctrl);
|
|
|
469
459
|
|
|
470
460
|
### watch
|
|
471
461
|
|
|
472
|
-
Returns a Proxy of an object which calls `callback` when
|
|
462
|
+
Returns a Proxy of an object which calls `callback` when it, or its nested objects are
|
|
463
|
+
modified:
|
|
473
464
|
|
|
474
465
|
```
|
|
475
|
-
watch(
|
|
466
|
+
const watchedObj = watch([], () => console.log('obj modified));
|
|
467
|
+
watchedObj[0] = 'foo; // Calls callback.
|
|
476
468
|
```
|
|
469
|
+
|
|
470
|
+
### protect
|
|
471
|
+
|
|
472
|
+
Returns a Proxy of an object which throws an error if it, or its nested objects are
|
|
473
|
+
modified.
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
const protectedObj = protect([]);
|
|
477
|
+
watchedObj[0] = 'foo'; // throws error.
|
|
478
|
+
```
|
|
479
|
+
|
|
477
480
|
---
|
|
478
481
|
Report any issues to https://github.com/wallace-js/wallace (and please give it a ★)
|
|
479
482
|
|
|
@@ -493,11 +496,10 @@ declare module "wallace" {
|
|
|
493
496
|
props: Props,
|
|
494
497
|
xargs?: {
|
|
495
498
|
ctrl: Controller;
|
|
499
|
+
props: Props;
|
|
496
500
|
self: ComponentInstance<Props, Controller, Methods>;
|
|
497
501
|
event: Event;
|
|
498
|
-
ev: Event;
|
|
499
502
|
element: HTMLElement;
|
|
500
|
-
el: HTMLElement;
|
|
501
503
|
}
|
|
502
504
|
): JSX.Element;
|
|
503
505
|
nest?({
|
|
@@ -518,10 +520,8 @@ declare module "wallace" {
|
|
|
518
520
|
show?: boolean;
|
|
519
521
|
hide?: boolean;
|
|
520
522
|
}): JSX.Element;
|
|
521
|
-
methods
|
|
522
|
-
|
|
523
|
-
ThisType<ComponentInstance<Props, Controller, Methods>>
|
|
524
|
-
): void;
|
|
523
|
+
methods?: ComponenMethods<Props, Controller> &
|
|
524
|
+
ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
525
525
|
readonly prototype?: ComponenMethods<Props, Controller> &
|
|
526
526
|
ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
527
527
|
// Methods will not be available on nested component, so omit.
|
|
@@ -555,6 +555,11 @@ declare module "wallace" {
|
|
|
555
555
|
Methods extends object = {}
|
|
556
556
|
> = ComponentFunction<Props, Controller, Methods>;
|
|
557
557
|
|
|
558
|
+
export interface Ref {
|
|
559
|
+
node: HTMLElement | ComponentInstance;
|
|
560
|
+
update(): void;
|
|
561
|
+
}
|
|
562
|
+
|
|
558
563
|
/**
|
|
559
564
|
* The type for a component instance.
|
|
560
565
|
*/
|
|
@@ -566,7 +571,7 @@ declare module "wallace" {
|
|
|
566
571
|
el: HTMLElement;
|
|
567
572
|
props: Props;
|
|
568
573
|
ctrl: Controller;
|
|
569
|
-
|
|
574
|
+
refs: { [key: string]: Ref };
|
|
570
575
|
base: Component<Props, Controller>;
|
|
571
576
|
} & Component<Props, Controller> &
|
|
572
577
|
Methods;
|
|
@@ -643,8 +648,21 @@ declare module "wallace" {
|
|
|
643
648
|
): ComponentInstance<Props, Controller, Methods>;
|
|
644
649
|
|
|
645
650
|
/**
|
|
646
|
-
* Returns a Proxy of an object which
|
|
647
|
-
*
|
|
651
|
+
* Returns a Proxy of an object which throws an error when it, or its nested objects
|
|
652
|
+
* are modified:
|
|
653
|
+
*
|
|
654
|
+
* ```js
|
|
655
|
+
* const protectedObj = protect([]);
|
|
656
|
+
* watchedObj[0] = 'foo'; // throws error.
|
|
657
|
+
* ```
|
|
658
|
+
*/
|
|
659
|
+
export function protect<T>(target: T): T;
|
|
660
|
+
|
|
661
|
+
type WatchCallback = (target: any, key: string, value: any) => void;
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Returns a Proxy of an object which calls `callback` when it, or its nested objects
|
|
665
|
+
* are modified:
|
|
648
666
|
*
|
|
649
667
|
* ```js
|
|
650
668
|
* ar = watch([], callback)
|
|
@@ -656,18 +674,23 @@ declare module "wallace" {
|
|
|
656
674
|
* obj.y = {}
|
|
657
675
|
* obj.y.z = 1000
|
|
658
676
|
* ```
|
|
659
|
-
* The callback
|
|
677
|
+
* The callback accepts parameters:
|
|
678
|
+
*
|
|
679
|
+
* - `target` - the object which is being modified.
|
|
680
|
+
* - `key` - the key being set.
|
|
681
|
+
* - `value` - the value it is being set to.
|
|
682
|
+
*
|
|
683
|
+
* The callback is called after the modification has occured.
|
|
660
684
|
*
|
|
661
685
|
* Some methods like `Array.push` set the index and then the length immediately after,
|
|
662
686
|
* so we use a timeout period to avoid calling the callback twice for what is really a
|
|
663
687
|
* single operation.
|
|
664
688
|
*
|
|
665
689
|
* @param {*} target - Any object, including arrays.
|
|
666
|
-
* @param {*} timeout - Any value in ms. Defaults to 100.
|
|
667
690
|
* @param {*} callback - A callback function.
|
|
668
691
|
* @returns a Proxy of the object.
|
|
669
692
|
*/
|
|
670
|
-
export function watch<T>(target: T, callback:
|
|
693
|
+
export function watch<T>(target: T, callback: WatchCallback): T;
|
|
671
694
|
}
|
|
672
695
|
|
|
673
696
|
type MustBeExpression = Exclude<any, string>;
|
|
@@ -739,8 +762,6 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
739
762
|
* );
|
|
740
763
|
* ```
|
|
741
764
|
*
|
|
742
|
-
* Unfortunately you lose the tooltip in that format.
|
|
743
|
-
*
|
|
744
765
|
* Note that destructured props are converted to member expressions, so these examples
|
|
745
766
|
* work even though it looks like you're setting a local variable.
|
|
746
767
|
*/
|
|
@@ -760,11 +781,34 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
760
781
|
* ```
|
|
761
782
|
* <div class:danger="danger red" toggle:danger={expr}></div>
|
|
762
783
|
* ```
|
|
763
|
-
*
|
|
764
|
-
* Unfortunately you lose the tooltip in that format.
|
|
765
784
|
*/
|
|
766
785
|
class?: any;
|
|
767
786
|
|
|
787
|
+
/**
|
|
788
|
+
* ## Wallace directive: css
|
|
789
|
+
*
|
|
790
|
+
* Shorthand for `fixed:class`:
|
|
791
|
+
*
|
|
792
|
+
* ```
|
|
793
|
+
* <div css={foo} ></div>
|
|
794
|
+
* ```
|
|
795
|
+
*/
|
|
796
|
+
css?: string;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* ## Wallace directive: fixed
|
|
800
|
+
*
|
|
801
|
+
* Sets the value of an attribute from an expression at point of component definition,
|
|
802
|
+
* as such the expression may not access props or xargs. See also `css` directive.
|
|
803
|
+
*
|
|
804
|
+
* Requires a qualifer, which is the name of the attribute to set.
|
|
805
|
+
*
|
|
806
|
+
* ```
|
|
807
|
+
* <div fixed:class={foo} ></div>
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
fixed?: string;
|
|
811
|
+
|
|
768
812
|
/** ## Wallace directive: hide
|
|
769
813
|
*
|
|
770
814
|
* Set the element's `hidden` property and if true, does not render dynamic elements
|
|
@@ -806,19 +850,25 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
806
850
|
/**
|
|
807
851
|
* ## Wallace directive: ref
|
|
808
852
|
*
|
|
809
|
-
* 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()`.
|
|
810
857
|
*
|
|
811
858
|
* ```
|
|
812
|
-
* <div ref:title
|
|
859
|
+
* <div ref:title>
|
|
860
|
+
* {name}
|
|
861
|
+
* </div>
|
|
813
862
|
* ```
|
|
814
863
|
*
|
|
815
864
|
* ```
|
|
816
|
-
* component.
|
|
865
|
+
* component.refs.title.update();
|
|
866
|
+
* component.refs.title.node.textContent = 'hello';
|
|
817
867
|
* ```
|
|
818
868
|
*
|
|
819
869
|
* Requires a qualifier, but you lose the tooltip in that format.
|
|
820
870
|
*/
|
|
821
|
-
ref?:
|
|
871
|
+
ref?: null;
|
|
822
872
|
|
|
823
873
|
/** ## Wallace directive: show
|
|
824
874
|
*
|
|
@@ -876,25 +926,30 @@ declare namespace JSX {
|
|
|
876
926
|
* <MyComponent.nest props={singleProps} />
|
|
877
927
|
* <MyComponent.repeat items={arrayOfProps} />
|
|
878
928
|
* ```
|
|
879
|
-
*
|
|
929
|
+
* Note that repeated components must not have siblings.
|
|
880
930
|
*
|
|
881
931
|
* Available Wallace directives:
|
|
882
932
|
*
|
|
883
933
|
* - `apply` runs a callback to modify an element.
|
|
884
934
|
* - `bind` updates a value when an input is changed.
|
|
885
935
|
* - `class:xyz` defines a set of classes to be toggled.
|
|
936
|
+
* - `css` shorthand for `fixed:class`.
|
|
937
|
+
* - `fixed:xyz` sets a attribute from an expression at definition.
|
|
886
938
|
* - `hide` sets an element or component's hidden property.
|
|
887
939
|
* - `html` Set the element's `innnerHTML` property.
|
|
888
940
|
* - `if` excludes an element from the DOM.
|
|
889
941
|
* - `items` set items for repeated component, must be an array of props.
|
|
890
942
|
* - `on[EventName]` creates an event handler (note the code is copied)
|
|
891
943
|
* - `props` specifes props for a nested components.
|
|
892
|
-
* - `ref` saves a reference to
|
|
944
|
+
* - `ref:xyz` saves a reference to node, allowing partial updates or access to element/component.
|
|
893
945
|
* - `show` sets and element or component's hidden property.
|
|
894
946
|
* - `style:xyz` sets a specific style property.
|
|
895
|
-
* - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
|
|
947
|
+
* - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
|
|
948
|
+
* `xyz`.
|
|
896
949
|
*
|
|
897
|
-
*
|
|
950
|
+
* You will get more details by hovering on the directive itself, but unfortunetely
|
|
951
|
+
* the tool tip won't display when you use a qualifier, like `class:danger`. To see
|
|
952
|
+
* it you cantemporarily change it to something `class x:danger`.
|
|
898
953
|
*/
|
|
899
954
|
[elemName: string]: DirectiveAttributes & Record<string, any>;
|
|
900
955
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,73 +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 = buildComponent(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
|
-
export function getElement(elementOrId) {
|
|
20
|
-
return typeof elementOrId === "string"
|
|
21
|
-
? document.getElementById(elementOrId)
|
|
22
|
-
: elementOrId;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
9
|
/**
|
|
26
|
-
*
|
|
10
|
+
* Trims the unwanted child elements from the end.
|
|
11
|
+
*
|
|
12
|
+
* @param {Node} e
|
|
13
|
+
* @param {Array} childNodes
|
|
14
|
+
* @param {Int} itemsLength
|
|
27
15
|
*/
|
|
28
|
-
export function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
component.el = dom;
|
|
35
|
-
proto._b(component, dom);
|
|
36
|
-
return component;
|
|
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
|
+
}
|
|
37
22
|
}
|
|
38
23
|
|
|
39
24
|
/**
|
|
40
|
-
*
|
|
25
|
+
* Stash something on the component. Returns the element.
|
|
26
|
+
* The generated code is expected to keep track of the position.
|
|
41
27
|
*/
|
|
42
|
-
export function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
get(target, key) {
|
|
47
|
-
if (key == "isProxy") return true;
|
|
48
|
-
const prop = target[key];
|
|
49
|
-
if (typeof prop == "undefined") return;
|
|
50
|
-
// set value as proxy if object
|
|
51
|
-
if (!prop.isProxy && typeof prop === "object")
|
|
52
|
-
target[key] = new Proxy(prop, handler);
|
|
53
|
-
return target[key];
|
|
54
|
-
},
|
|
28
|
+
export function stashMisc(element, stash, object) {
|
|
29
|
+
stash.push(object);
|
|
30
|
+
return element;
|
|
31
|
+
}
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!active) {
|
|
60
|
-
setTimeout(() => {
|
|
61
|
-
active = false;
|
|
62
|
-
}, grace);
|
|
63
|
-
active = true;
|
|
64
|
-
callback();
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
callback();
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
return new Proxy(target, handler);
|
|
33
|
+
export function onEvent(element, eventName, callback) {
|
|
34
|
+
element.addEventListener(eventName, callback);
|
|
35
|
+
return element;
|
|
73
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,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Everything in here is used by or modified by the Babel plugin.
|
|
3
|
-
*/
|
|
4
|
-
import { Component } from "./component";
|
|
5
|
-
import { buildComponent, replaceNode } from "./utils";
|
|
6
|
-
import { KeyedRepeater, SequentialRepeater } from "./repeaters";
|
|
7
|
-
|
|
8
|
-
const throwAway = document.createElement("template");
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A utility function that has to be in here because it needs _createConstructor and
|
|
12
|
-
* we'd otherwise get cirular inmports.
|
|
13
|
-
*
|
|
14
|
-
* Calls to this function which provide the 2nd argument:
|
|
15
|
-
*
|
|
16
|
-
* const Foo = extendComponent(Bar, () => <div></div>))
|
|
17
|
-
*
|
|
18
|
-
* Are modified by the Babel plugin to become this:
|
|
19
|
-
*
|
|
20
|
-
* const Foo = defineComponent(,,,,Bar);
|
|
21
|
-
*
|
|
22
|
-
* So it should never be called with 2nd arg in real life.
|
|
23
|
-
*/
|
|
24
|
-
export function extendComponent(base, componentDef) {
|
|
25
|
-
// This function call will have been replaced if 2nd arg is a valid component func.
|
|
26
|
-
// and therefor we would not receive it.
|
|
27
|
-
if (componentDef)
|
|
28
|
-
throw new Error("2nd arg to extendComponent must be a JSX arrow function");
|
|
29
|
-
return _createConstructor(base);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/*
|
|
33
|
-
Everything after this is used by code generated by the Babel plugin.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
export function findElement(rootElement, path) {
|
|
37
|
-
return path.reduce((acc, index) => acc.childNodes[index], rootElement);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function nestComponent(rootElement, path, cls) {
|
|
41
|
-
const el = findElement(rootElement, path),
|
|
42
|
-
child = buildComponent(cls);
|
|
43
|
-
replaceNode(el, child.el);
|
|
44
|
-
return child;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Saves a reference to element or nested component. Returns the element.
|
|
49
|
-
*/
|
|
50
|
-
export function saveRef(element, component, name) {
|
|
51
|
-
component.ref[name] = element;
|
|
52
|
-
return element;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Stash something on the component. Returns the element.
|
|
57
|
-
* The generated code is expected to keep track of the position.
|
|
58
|
-
*/
|
|
59
|
-
export function stashMisc(element, component, object) {
|
|
60
|
-
component._s.push(object);
|
|
61
|
-
return element;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function onEvent(element, eventName, callback) {
|
|
65
|
-
element.addEventListener(eventName, callback);
|
|
66
|
-
return element;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function getKeyedRepeater(cls, keyFn) {
|
|
70
|
-
return new KeyedRepeater(cls, keyFn);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function getSequentialRepeater(cls) {
|
|
74
|
-
return new SequentialRepeater(cls);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
|
|
78
|
-
const ComponentDefinition = _createConstructor(inheritFrom || Component);
|
|
79
|
-
const prototype = ComponentDefinition.prototype;
|
|
80
|
-
throwAway.innerHTML = html;
|
|
81
|
-
//Ensure these do not clash with fields on the component itself.
|
|
82
|
-
prototype._w = watches;
|
|
83
|
-
prototype._q = queries;
|
|
84
|
-
prototype._b = buildFunction;
|
|
85
|
-
prototype._n = throwAway.content.firstChild;
|
|
86
|
-
return ComponentDefinition;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function _createConstructor(base) {
|
|
90
|
-
const ComponentDefinition = function () {
|
|
91
|
-
base.call(this);
|
|
92
|
-
};
|
|
93
|
-
ComponentDefinition.stubs = {};
|
|
94
|
-
Object.assign(ComponentDefinition.stubs, base.stubs);
|
|
95
|
-
// This is a helper function for the user.
|
|
96
|
-
ComponentDefinition.methods = function (obj) {
|
|
97
|
-
Object.assign(ComponentDefinition.prototype, obj);
|
|
98
|
-
};
|
|
99
|
-
ComponentDefinition.prototype = Object.create(base && base.prototype, {
|
|
100
|
-
constructor: {
|
|
101
|
-
value: ComponentDefinition,
|
|
102
|
-
writable: true,
|
|
103
|
-
configurable: true
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
ComponentDefinition.prototype.base = Component.prototype;
|
|
107
|
-
return ComponentDefinition;
|
|
108
|
-
}
|