wallace 0.3.0 → 0.5.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 +12 -8
- package/lib/index.js +2 -5
- package/lib/initCalls.js +56 -32
- package/lib/repeaters.js +2 -4
- package/lib/types.d.ts +180 -53
- package/lib/utils.js +28 -35
- package/package.json +3 -3
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,18 +1,19 @@
|
|
|
1
1
|
const NO_LOOKUP = "__";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The base component.
|
|
4
|
+
* The base component constructor.
|
|
5
5
|
*/
|
|
6
6
|
export function Component() {
|
|
7
|
-
this.ctrl = undefined;
|
|
8
|
-
this.props = undefined;
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
// Internal state objects
|
|
12
|
-
this._e = {}; // The dynamic elements in the DOM.
|
|
13
|
-
this._s = []; // A stash for misc objects.
|
|
7
|
+
this.ctrl = undefined;
|
|
8
|
+
this.props = undefined;
|
|
9
|
+
// Internal state objects (_e is created during build)
|
|
10
|
+
this._s = []; // A stash for misc objects like repeaters.
|
|
14
11
|
this._p = {}; // The previous values for watches to compare against.
|
|
15
12
|
this._r = {}; // The current values read during an update.
|
|
13
|
+
const root = this._n.cloneNode(true);
|
|
14
|
+
this.el = root;
|
|
15
|
+
this.ref = {};
|
|
16
|
+
this._b(this, root);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
Component.stubs = {};
|
|
@@ -25,6 +26,9 @@ Object.defineProperty(proto, "hidden", {
|
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Gets a stub by name.
|
|
31
|
+
*/
|
|
28
32
|
proto._gs = function (name) {
|
|
29
33
|
return this.constructor.stubs[name];
|
|
30
34
|
};
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { mount, watch } from "./utils";
|
|
1
|
+
import { mount, watch, protect } from "./utils";
|
|
2
2
|
import { Component } from "./component";
|
|
3
3
|
import { KeyedRepeater, SequentialRepeater } from "./repeaters";
|
|
4
4
|
import {
|
|
5
5
|
extendComponent,
|
|
6
6
|
defineComponent,
|
|
7
7
|
findElement,
|
|
8
|
-
getKeyedRepeater,
|
|
9
|
-
getSequentialRepeater,
|
|
10
8
|
onEvent,
|
|
11
9
|
nestComponent,
|
|
12
10
|
saveRef,
|
|
@@ -18,12 +16,11 @@ export {
|
|
|
18
16
|
defineComponent,
|
|
19
17
|
extendComponent,
|
|
20
18
|
findElement,
|
|
21
|
-
getKeyedRepeater,
|
|
22
|
-
getSequentialRepeater,
|
|
23
19
|
KeyedRepeater,
|
|
24
20
|
mount,
|
|
25
21
|
nestComponent,
|
|
26
22
|
onEvent,
|
|
23
|
+
protect,
|
|
27
24
|
saveRef,
|
|
28
25
|
SequentialRepeater,
|
|
29
26
|
stashMisc,
|
package/lib/initCalls.js
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything in here is used by or modified by the Babel plugin.
|
|
3
|
+
*/
|
|
1
4
|
import { Component } from "./component";
|
|
2
|
-
import {
|
|
3
|
-
|
|
5
|
+
import { replaceNode } from "./utils";
|
|
6
|
+
|
|
4
7
|
const throwAway = document.createElement("template");
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
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.
|
|
8
22
|
*/
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
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);
|
|
12
29
|
}
|
|
13
30
|
|
|
31
|
+
/*
|
|
32
|
+
Everything after this is used by code generated by the Babel plugin.
|
|
33
|
+
*/
|
|
34
|
+
|
|
14
35
|
export function findElement(rootElement, path) {
|
|
15
36
|
return path.reduce((acc, index) => acc.childNodes[index], rootElement);
|
|
16
37
|
}
|
|
17
38
|
|
|
18
|
-
export function nestComponent(rootElement, path,
|
|
39
|
+
export function nestComponent(rootElement, path, componentDefinition) {
|
|
19
40
|
const el = findElement(rootElement, path),
|
|
20
|
-
child =
|
|
41
|
+
child = new componentDefinition();
|
|
21
42
|
replaceNode(el, child.el);
|
|
22
43
|
return child;
|
|
23
44
|
}
|
|
@@ -44,49 +65,52 @@ export function onEvent(element, eventName, callback) {
|
|
|
44
65
|
return element;
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
export function getKeyedRepeater(cls, keyFn) {
|
|
48
|
-
return new KeyedRepeater(cls, keyFn);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getSequentialRepeater(cls) {
|
|
52
|
-
return new SequentialRepeater(cls);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function extendComponent(base, componentDef) {
|
|
56
|
-
// This function call will have been replaced if 2nd arg is a valid component func.
|
|
57
|
-
// and therefor we would not receive it.
|
|
58
|
-
if (componentDef)
|
|
59
|
-
throw new Error("2nd arg to extendComponent must be a JSX arrow function");
|
|
60
|
-
return _createConstructor(base);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
68
|
export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
|
|
64
69
|
const ComponentDefinition = _createConstructor(inheritFrom || Component);
|
|
65
70
|
const prototype = ComponentDefinition.prototype;
|
|
71
|
+
throwAway.innerHTML = html;
|
|
66
72
|
//Ensure these do not clash with fields on the component itself.
|
|
67
73
|
prototype._w = watches;
|
|
68
74
|
prototype._q = queries;
|
|
69
75
|
prototype._b = buildFunction;
|
|
70
|
-
prototype._n =
|
|
76
|
+
prototype._n = throwAway.content.firstChild;
|
|
71
77
|
return ComponentDefinition;
|
|
72
78
|
}
|
|
73
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
|
+
*/
|
|
74
86
|
function _createConstructor(base) {
|
|
75
87
|
const ComponentDefinition = function () {
|
|
76
88
|
base.call(this);
|
|
77
89
|
};
|
|
78
|
-
|
|
79
|
-
Object.assign(ComponentDefinition.stubs, base.stubs);
|
|
80
|
-
// This is a helper function for the user.
|
|
81
|
-
ComponentDefinition.methods = function (obj) {
|
|
82
|
-
Object.assign(ComponentDefinition.prototype, obj);
|
|
83
|
-
};
|
|
84
|
-
ComponentDefinition.prototype = Object.create(base && base.prototype, {
|
|
90
|
+
const proto = Object.create(base && base.prototype, {
|
|
85
91
|
constructor: {
|
|
86
92
|
value: ComponentDefinition,
|
|
87
93
|
writable: true,
|
|
88
94
|
configurable: true
|
|
89
95
|
}
|
|
90
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;
|
|
91
115
|
return ComponentDefinition;
|
|
92
116
|
}
|
package/lib/repeaters.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { buildComponent } from "./utils";
|
|
2
|
-
|
|
3
1
|
/*
|
|
4
2
|
* Gets a component from the pool.
|
|
5
3
|
*/
|
|
@@ -8,7 +6,7 @@ function getComponent(pool, componentDefinition, ctrl, key, props) {
|
|
|
8
6
|
if (pool.hasOwnProperty(key)) {
|
|
9
7
|
component = pool[key];
|
|
10
8
|
} else {
|
|
11
|
-
component =
|
|
9
|
+
component = new componentDefinition();
|
|
12
10
|
pool[key] = component;
|
|
13
11
|
}
|
|
14
12
|
component.render(props, ctrl);
|
|
@@ -136,7 +134,7 @@ SequentialRepeater.prototype.patch = function (e, items, ctrl) {
|
|
|
136
134
|
if (i < poolCount) {
|
|
137
135
|
component = pool[i];
|
|
138
136
|
} else {
|
|
139
|
-
component =
|
|
137
|
+
component = new componentDefinition();
|
|
140
138
|
pool.push(component);
|
|
141
139
|
poolCount++;
|
|
142
140
|
}
|
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
|
|
|
@@ -71,7 +71,7 @@ The arguments are:
|
|
|
71
71
|
3. props for the element (optional)
|
|
72
72
|
4. controller (optional)
|
|
73
73
|
|
|
74
|
-
`mount` returns the component instance, allowing you to call
|
|
74
|
+
`mount` returns the component instance, allowing you to call its methods:
|
|
75
75
|
|
|
76
76
|
```tsx
|
|
77
77
|
root.update();
|
|
@@ -98,10 +98,13 @@ render(props, ctrl) {
|
|
|
98
98
|
Updates the DOM. Only called internally by `render`, but you can call it from other
|
|
99
99
|
places.
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
#### Overriding
|
|
102
|
+
|
|
103
|
+
You can override these methods, and add new ones using `methods` property of the
|
|
104
|
+
component definition:
|
|
102
105
|
|
|
103
106
|
```tsx
|
|
104
|
-
MyComponent.methods
|
|
107
|
+
MyComponent.methods = {
|
|
105
108
|
render(props) {
|
|
106
109
|
this.ctrl = new MyController(this, props);
|
|
107
110
|
this.update();
|
|
@@ -109,7 +112,7 @@ MyComponent.methods({
|
|
|
109
112
|
getName() {
|
|
110
113
|
return 'wallace';
|
|
111
114
|
}
|
|
112
|
-
}
|
|
115
|
+
};
|
|
113
116
|
```
|
|
114
117
|
|
|
115
118
|
This has the same effect as setting them on the prototype:
|
|
@@ -118,7 +121,21 @@ This has the same effect as setting them on the prototype:
|
|
|
118
121
|
MyComponent.prototype.render = function () {};
|
|
119
122
|
```
|
|
120
123
|
|
|
121
|
-
You
|
|
124
|
+
You can use `this.base` to access methods on the base `Component` class:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
MyComponent.methods = {
|
|
128
|
+
render(props) {
|
|
129
|
+
this.base.render.call(this, props, ctrl);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Note that `base` is not the same as `super` in classes which access the lowest override.
|
|
135
|
+
|
|
136
|
+
You access the instance as `this` in methods, but cannot use `this` in arrow functions,
|
|
137
|
+
so use `self` from the **xargs** in component functions.
|
|
138
|
+
|
|
122
139
|
|
|
123
140
|
### 1.4 Fields
|
|
124
141
|
|
|
@@ -171,7 +188,7 @@ const TopTasks = (tasks) => (
|
|
|
171
188
|
|
|
172
189
|
const TaskList = (tasks) => (
|
|
173
190
|
<div>
|
|
174
|
-
<Task.repeat
|
|
191
|
+
<Task.repeat items={tasks} />
|
|
175
192
|
</div>
|
|
176
193
|
);
|
|
177
194
|
```
|
|
@@ -184,11 +201,22 @@ Notes:
|
|
|
184
201
|
|
|
185
202
|
## 4. Directives
|
|
186
203
|
|
|
187
|
-
Directives are attributes with special behaviours
|
|
204
|
+
Directives are attributes with special behaviours.
|
|
205
|
+
|
|
206
|
+
You can see the list of available directives by hovering over any JSX element, like
|
|
207
|
+
a `div`
|
|
208
|
+
|
|
209
|
+
You will get more details by hovering on the directive itself, but unfortunetely the
|
|
210
|
+
tool tip won't display when you use a qualifier, like `class:danger`. To see it you can
|
|
211
|
+
temporarily change it to something `class x:danger`.
|
|
212
|
+
|
|
213
|
+
You can define your own directives in your babel config.
|
|
214
|
+
|
|
215
|
+
Each has more
|
|
188
216
|
detailed information on its JSDoc, which should display as a tool tip\* when you hover
|
|
189
217
|
over it in your IDE.
|
|
190
218
|
|
|
191
|
-
You can also
|
|
219
|
+
You can also
|
|
192
220
|
|
|
193
221
|
- `apply` runs a callback to modify an element.
|
|
194
222
|
- `bind` updates a value when an input is changed.
|
|
@@ -204,7 +232,6 @@ an array.
|
|
|
204
232
|
- `style:xyz` sets a specific style property.
|
|
205
233
|
- `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class `xyz`.
|
|
206
234
|
|
|
207
|
-
\* The tool tip won't display when you use a qualifier, like `class:danger`.
|
|
208
235
|
|
|
209
236
|
## 5. Controllers
|
|
210
237
|
|
|
@@ -245,16 +272,16 @@ const Task = (task) => (<div>{task.name}</div>);
|
|
|
245
272
|
|
|
246
273
|
const TaskList = (_, {ctrl}) => (
|
|
247
274
|
<div>
|
|
248
|
-
<Task.repeat
|
|
275
|
+
<Task.repeat items={ctrl.getTasks()} />
|
|
249
276
|
</div>
|
|
250
277
|
);
|
|
251
278
|
|
|
252
|
-
TaskList.methods
|
|
279
|
+
TaskList.methods = {
|
|
253
280
|
render(_, ctrl) {
|
|
254
281
|
this.ctrl = new TaskController(this, ctrl);
|
|
255
282
|
this.update();
|
|
256
283
|
}
|
|
257
|
-
}
|
|
284
|
+
};
|
|
258
285
|
```
|
|
259
286
|
|
|
260
287
|
## 6. Inheritance
|
|
@@ -330,8 +357,7 @@ const Task: Uses<null> = () => <div>Hello</div>;
|
|
|
330
357
|
|
|
331
358
|
### Props
|
|
332
359
|
|
|
333
|
-
TypeScript will ensure you pass correct props during mounting or nesting
|
|
334
|
-
including repeat, which expects an arry of the type:
|
|
360
|
+
TypeScript will ensure you pass correct props during mounting or nesting:
|
|
335
361
|
|
|
336
362
|
```
|
|
337
363
|
const TaskList: Uses<iTask[]> = (tasks) => (
|
|
@@ -339,7 +365,7 @@ const TaskList: Uses<iTask[]> = (tasks) => (
|
|
|
339
365
|
First task:
|
|
340
366
|
<Task.nest props={tasks[0]} />
|
|
341
367
|
<div>
|
|
342
|
-
<Task.repeat
|
|
368
|
+
<Task.repeat items={tasks.slice(1)} />
|
|
343
369
|
</div>
|
|
344
370
|
</div>
|
|
345
371
|
);
|
|
@@ -376,12 +402,12 @@ const Task: Uses<null, null, TaskMethods> = (_, { self }) => (
|
|
|
376
402
|
<div>{self.getName()}</div>
|
|
377
403
|
));
|
|
378
404
|
|
|
379
|
-
Task.methods
|
|
405
|
+
Task.methods = {
|
|
380
406
|
getName() { return 'wallace' },
|
|
381
407
|
render(props, ctrl) { // types are already known
|
|
382
408
|
this.props = { ...props, notallowed: 1 }; // type error
|
|
383
409
|
}
|
|
384
|
-
}
|
|
410
|
+
};
|
|
385
411
|
```
|
|
386
412
|
|
|
387
413
|
The type will pass into the object passed into `methods` so it recognises custom methods
|
|
@@ -429,7 +455,7 @@ Wallace defines some other types you may use:
|
|
|
429
455
|
constructor, not a class)
|
|
430
456
|
- `ComponentInstance<Props, Controller, Methods>` - a component instance.
|
|
431
457
|
|
|
432
|
-
##
|
|
458
|
+
## 9. Helpers
|
|
433
459
|
|
|
434
460
|
Each of these has their own JSDoc, we just lsit them here.
|
|
435
461
|
|
|
@@ -453,11 +479,24 @@ mount("elementId", MyComponent, props, ctrl);
|
|
|
453
479
|
|
|
454
480
|
### watch
|
|
455
481
|
|
|
456
|
-
Returns a Proxy of an object which calls `callback` when
|
|
482
|
+
Returns a Proxy of an object which calls `callback` when it, or its nested objects are
|
|
483
|
+
modified:
|
|
457
484
|
|
|
458
485
|
```
|
|
459
|
-
watch(
|
|
486
|
+
const watchedObj = watch([], () => console.log('obj modified));
|
|
487
|
+
watchedObj[0] = 'foo; // Calls callback.
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### protect
|
|
491
|
+
|
|
492
|
+
Returns a Proxy of an object which throws an error if it, or its nested objects are
|
|
493
|
+
modified.
|
|
494
|
+
|
|
460
495
|
```
|
|
496
|
+
const protectedObj = protect([]);
|
|
497
|
+
watchedObj[0] = 'foo'; // throws error.
|
|
498
|
+
```
|
|
499
|
+
|
|
461
500
|
---
|
|
462
501
|
Report any issues to https://github.com/wallace-js/wallace (and please give it a ★)
|
|
463
502
|
|
|
@@ -475,10 +514,11 @@ declare module "wallace" {
|
|
|
475
514
|
> {
|
|
476
515
|
(
|
|
477
516
|
props: Props,
|
|
478
|
-
|
|
517
|
+
xargs?: {
|
|
479
518
|
ctrl: Controller;
|
|
480
519
|
self: ComponentInstance<Props, Controller, Methods>;
|
|
481
520
|
event: Event;
|
|
521
|
+
element: HTMLElement;
|
|
482
522
|
}
|
|
483
523
|
): JSX.Element;
|
|
484
524
|
nest?({
|
|
@@ -491,18 +531,16 @@ declare module "wallace" {
|
|
|
491
531
|
hide?: boolean;
|
|
492
532
|
}): JSX.Element;
|
|
493
533
|
repeat?({
|
|
494
|
-
|
|
534
|
+
items,
|
|
495
535
|
show,
|
|
496
536
|
hide
|
|
497
537
|
}: {
|
|
498
|
-
|
|
538
|
+
items: Array<Props>;
|
|
499
539
|
show?: boolean;
|
|
500
540
|
hide?: boolean;
|
|
501
541
|
}): JSX.Element;
|
|
502
|
-
methods
|
|
503
|
-
|
|
504
|
-
ThisType<ComponentInstance<Props, Controller, Methods>>
|
|
505
|
-
): void;
|
|
542
|
+
// methods?: ComponenMethods<Props, Controller> &
|
|
543
|
+
// ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
506
544
|
readonly prototype?: ComponenMethods<Props, Controller> &
|
|
507
545
|
ThisType<ComponentInstance<Props, Controller, Methods>>;
|
|
508
546
|
// Methods will not be available on nested component, so omit.
|
|
@@ -516,7 +554,7 @@ declare module "wallace" {
|
|
|
516
554
|
};
|
|
517
555
|
|
|
518
556
|
/**
|
|
519
|
-
* A type which
|
|
557
|
+
* A type which must be placed as shown:
|
|
520
558
|
*
|
|
521
559
|
* ```tsx
|
|
522
560
|
* const Task: Uses<iTask> = ({text}) => <div>{text}</div>;
|
|
@@ -544,19 +582,53 @@ declare module "wallace" {
|
|
|
544
582
|
Controller = any,
|
|
545
583
|
Methods extends object = {}
|
|
546
584
|
> = {
|
|
547
|
-
update(): void;
|
|
548
|
-
render(props: Props, ctrl?: Controller): void;
|
|
549
585
|
el: HTMLElement;
|
|
550
586
|
props: Props;
|
|
551
587
|
ctrl: Controller;
|
|
552
|
-
|
|
588
|
+
ref: { [key: string]: HTMLElement };
|
|
589
|
+
base: Component<Props, Controller>;
|
|
590
|
+
} & Component<Props, Controller> &
|
|
591
|
+
Methods;
|
|
553
592
|
|
|
554
593
|
/**
|
|
555
594
|
* The component constructor function (typed as a class, but isn't).
|
|
556
595
|
*/
|
|
557
596
|
export class Component<Props = any, Controller = any> {
|
|
558
|
-
update(): void;
|
|
559
597
|
render(props: Props, ctrl?: Controller): void;
|
|
598
|
+
/**
|
|
599
|
+
* The base render method looks like this:
|
|
600
|
+
*
|
|
601
|
+
* ```
|
|
602
|
+
* render(props?: Props, ctrl?: Controller) {
|
|
603
|
+
* this.props = props;
|
|
604
|
+
* this.ctrl = ctrl;
|
|
605
|
+
* this.update();
|
|
606
|
+
* }
|
|
607
|
+
* ```
|
|
608
|
+
*
|
|
609
|
+
* You can override like so:
|
|
610
|
+
*
|
|
611
|
+
* ```
|
|
612
|
+
* render(props?: Props, ctrl?: Controller) {
|
|
613
|
+
* // do your thing
|
|
614
|
+
* this.base.render.call(this, props, ctrl);
|
|
615
|
+
* }
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
render(props?: Props, ctrl?: Controller): void;
|
|
619
|
+
/**
|
|
620
|
+
* Updates the DOM.
|
|
621
|
+
*
|
|
622
|
+
* You can override like so:
|
|
623
|
+
*
|
|
624
|
+
* ```
|
|
625
|
+
* update() {
|
|
626
|
+
* // do your thing
|
|
627
|
+
* this.base.update.call(this);
|
|
628
|
+
* }
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
update(): void;
|
|
560
632
|
}
|
|
561
633
|
|
|
562
634
|
/**
|
|
@@ -590,8 +662,21 @@ declare module "wallace" {
|
|
|
590
662
|
): ComponentInstance<Props, Controller, Methods>;
|
|
591
663
|
|
|
592
664
|
/**
|
|
593
|
-
* Returns a Proxy of an object which
|
|
594
|
-
*
|
|
665
|
+
* Returns a Proxy of an object which throws an error when it, or its nested objects
|
|
666
|
+
* are modified:
|
|
667
|
+
*
|
|
668
|
+
* ```js
|
|
669
|
+
* const protectedObj = protect([]);
|
|
670
|
+
* watchedObj[0] = 'foo'; // throws error.
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
export function protect<T>(target: T): T;
|
|
674
|
+
|
|
675
|
+
type WatchCallback = (target: any, key: string, value: any) => void;
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Returns a Proxy of an object which calls `callback` when it, or its nested objects
|
|
679
|
+
* are modified:
|
|
595
680
|
*
|
|
596
681
|
* ```js
|
|
597
682
|
* ar = watch([], callback)
|
|
@@ -603,18 +688,23 @@ declare module "wallace" {
|
|
|
603
688
|
* obj.y = {}
|
|
604
689
|
* obj.y.z = 1000
|
|
605
690
|
* ```
|
|
606
|
-
* The callback
|
|
691
|
+
* The callback accepts parameters:
|
|
692
|
+
*
|
|
693
|
+
* - `target` - the object which is being modified.
|
|
694
|
+
* - `key` - the key being set.
|
|
695
|
+
* - `value` - the value it is being set to.
|
|
696
|
+
*
|
|
697
|
+
* The callback is called after the modification has occured.
|
|
607
698
|
*
|
|
608
699
|
* Some methods like `Array.push` set the index and then the length immediately after,
|
|
609
700
|
* so we use a timeout period to avoid calling the callback twice for what is really a
|
|
610
701
|
* single operation.
|
|
611
702
|
*
|
|
612
703
|
* @param {*} target - Any object, including arrays.
|
|
613
|
-
* @param {*} timeout - Any value in ms. Defaults to 100.
|
|
614
704
|
* @param {*} callback - A callback function.
|
|
615
705
|
* @returns a Proxy of the object.
|
|
616
706
|
*/
|
|
617
|
-
export function watch<T>(target: T, callback:
|
|
707
|
+
export function watch<T>(target: T, callback: WatchCallback): T;
|
|
618
708
|
}
|
|
619
709
|
|
|
620
710
|
type MustBeExpression = Exclude<any, string>;
|
|
@@ -686,8 +776,6 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
686
776
|
* );
|
|
687
777
|
* ```
|
|
688
778
|
*
|
|
689
|
-
* Unfortunately you lose the tooltip in that format.
|
|
690
|
-
*
|
|
691
779
|
* Note that destructured props are converted to member expressions, so these examples
|
|
692
780
|
* work even though it looks like you're setting a local variable.
|
|
693
781
|
*/
|
|
@@ -707,11 +795,34 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
707
795
|
* ```
|
|
708
796
|
* <div class:danger="danger red" toggle:danger={expr}></div>
|
|
709
797
|
* ```
|
|
710
|
-
*
|
|
711
|
-
* Unfortunately you lose the tooltip in that format.
|
|
712
798
|
*/
|
|
713
799
|
class?: any;
|
|
714
800
|
|
|
801
|
+
/**
|
|
802
|
+
* ## Wallace directive: css
|
|
803
|
+
*
|
|
804
|
+
* Shorthand for `fixed:class`:
|
|
805
|
+
*
|
|
806
|
+
* ```
|
|
807
|
+
* <div css={foo} ></div>
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
css?: string;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* ## Wallace directive: fixed
|
|
814
|
+
*
|
|
815
|
+
* Sets the value of an attribute from an expression at point of component definition,
|
|
816
|
+
* as such the expression may not access props or xargs. See also `css` directive.
|
|
817
|
+
*
|
|
818
|
+
* Requires a qualifer, which is the name of the attribute to set.
|
|
819
|
+
*
|
|
820
|
+
* ```
|
|
821
|
+
* <div fixed:class={foo} ></div>
|
|
822
|
+
* ```
|
|
823
|
+
*/
|
|
824
|
+
fixed?: string;
|
|
825
|
+
|
|
715
826
|
/** ## Wallace directive: hide
|
|
716
827
|
*
|
|
717
828
|
* Set the element's `hidden` property and if true, does not render dynamic elements
|
|
@@ -725,17 +836,28 @@ interface DirectiveAttributes extends AllDomEvents {
|
|
|
725
836
|
*/
|
|
726
837
|
html?: MustBeExpression;
|
|
727
838
|
|
|
728
|
-
/** Wallace
|
|
729
|
-
*
|
|
839
|
+
/** ## Wallace directive: if
|
|
840
|
+
*
|
|
841
|
+
* Excludes this element from the DOM completely if the condition is false,
|
|
842
|
+
* and does not render dynamic elements underneath.
|
|
843
|
+
* When the condition becomes true, the element is reattached.
|
|
844
|
+
*/
|
|
730
845
|
if?: MustBeExpression;
|
|
731
846
|
|
|
847
|
+
/**
|
|
848
|
+
* ## Wallace directive: items
|
|
849
|
+
*
|
|
850
|
+
* Specifies items for repeated component. Must be an array of the props which the
|
|
851
|
+
* nested item accepts.
|
|
852
|
+
*
|
|
853
|
+
*/
|
|
854
|
+
items?: MustBeExpression;
|
|
855
|
+
|
|
732
856
|
/**
|
|
733
857
|
* ## Wallace directive: props
|
|
734
858
|
*
|
|
735
|
-
* Specifies props for a nested
|
|
859
|
+
* Specifies props for a nested component.
|
|
736
860
|
*
|
|
737
|
-
* If it is a repeated component, the props should be an array of whatever type it
|
|
738
|
-
* accepts.
|
|
739
861
|
*/
|
|
740
862
|
props?: MustBeExpression;
|
|
741
863
|
|
|
@@ -810,27 +932,32 @@ declare namespace JSX {
|
|
|
810
932
|
* Nesting syntax:
|
|
811
933
|
* ```
|
|
812
934
|
* <MyComponent.nest props={singleProps} />
|
|
813
|
-
* <MyComponent.repeat
|
|
935
|
+
* <MyComponent.repeat items={arrayOfProps} />
|
|
814
936
|
* ```
|
|
815
|
-
*
|
|
937
|
+
* Note that repeated components must not have siblings.
|
|
816
938
|
*
|
|
817
939
|
* Available Wallace directives:
|
|
818
940
|
*
|
|
819
941
|
* - `apply` runs a callback to modify an element.
|
|
820
942
|
* - `bind` updates a value when an input is changed.
|
|
821
943
|
* - `class:xyz` defines a set of classes to be toggled.
|
|
944
|
+
* - `css` shorthand for `fixed:class`.
|
|
945
|
+
* - `fixed:xyz` sets a attribute from an expression at definition.
|
|
822
946
|
* - `hide` sets an element or component's hidden property.
|
|
823
947
|
* - `html` Set the element's `innnerHTML` property.
|
|
824
948
|
* - `if` excludes an element from the DOM.
|
|
949
|
+
* - `items` set items for repeated component, must be an array of props.
|
|
825
950
|
* - `on[EventName]` creates an event handler (note the code is copied)
|
|
826
|
-
* - `props` specifes props for a nested
|
|
827
|
-
* an array.
|
|
951
|
+
* - `props` specifes props for a nested components.
|
|
828
952
|
* - `ref` saves a reference to an element or nested component.
|
|
829
953
|
* - `show` sets and element or component's hidden property.
|
|
830
954
|
* - `style:xyz` sets a specific style property.
|
|
831
|
-
* - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
|
|
955
|
+
* - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
|
|
956
|
+
* `xyz`.
|
|
832
957
|
*
|
|
833
|
-
*
|
|
958
|
+
* You will get more details by hovering on the directive itself, but unfortunetely
|
|
959
|
+
* the tool tip won't display when you use a qualifier, like `class:danger`. To see
|
|
960
|
+
* it you cantemporarily change it to something `class x:danger`.
|
|
834
961
|
*/
|
|
835
962
|
[elemName: string]: DirectiveAttributes & Record<string, any>;
|
|
836
963
|
}
|
package/lib/utils.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @param {object} props The props to pass to the component (optional)
|
|
7
7
|
*/
|
|
8
8
|
export function mount(elementOrId, componentDefinition, props, ctrl) {
|
|
9
|
-
const component =
|
|
9
|
+
const component = new componentDefinition();
|
|
10
10
|
component.render(props, ctrl);
|
|
11
11
|
replaceNode(getElement(elementOrId), component.el);
|
|
12
12
|
return component;
|
|
@@ -22,52 +22,45 @@ export function getElement(elementOrId) {
|
|
|
22
22
|
: elementOrId;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const MUTATING_METHODS = ["push", "pop", "shift", "unshift", "splice", "reverse", "sort"];
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// if "componentDefinition is not a constructor" then we're probably missing a stub.
|
|
31
|
-
const component = new componentDefinition();
|
|
32
|
-
const proto = componentDefinition.prototype;
|
|
33
|
-
const dom = proto._n.cloneNode(true);
|
|
34
|
-
component.el = dom;
|
|
35
|
-
proto._b(component, dom);
|
|
36
|
-
return component;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* See types for docs. Set grace to 0 for testing.
|
|
27
|
+
* Returns a proxy which calls a callback when the object or its nested objects are
|
|
28
|
+
* modified.
|
|
29
|
+
*
|
|
30
|
+
* Note that proxies have property `isProxy` set to true.
|
|
41
31
|
*/
|
|
42
|
-
export function watch(target, callback
|
|
43
|
-
let active = false;
|
|
44
|
-
if (grace === undefined) grace = 100;
|
|
32
|
+
export function watch(target, callback) {
|
|
45
33
|
const handler = {
|
|
46
34
|
get(target, key) {
|
|
47
35
|
if (key == "isProxy") return true;
|
|
48
36
|
const prop = target[key];
|
|
49
37
|
if (typeof prop == "undefined") return;
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
target
|
|
53
|
-
|
|
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;
|
|
54
51
|
},
|
|
55
|
-
|
|
56
52
|
set(target, key, value) {
|
|
57
53
|
target[key] = value;
|
|
58
|
-
|
|
59
|
-
if (!active) {
|
|
60
|
-
setTimeout(() => {
|
|
61
|
-
active = false;
|
|
62
|
-
}, grace);
|
|
63
|
-
active = true;
|
|
64
|
-
callback();
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
callback();
|
|
68
|
-
}
|
|
54
|
+
callback(target, key, value);
|
|
69
55
|
return true;
|
|
70
56
|
}
|
|
71
57
|
};
|
|
72
58
|
return new Proxy(target, handler);
|
|
73
59
|
}
|
|
60
|
+
|
|
61
|
+
export function protect(obj) {
|
|
62
|
+
return watch(obj, (target, key, value) => {
|
|
63
|
+
console.log("target", target, "key", key, "value", value);
|
|
64
|
+
throw new Error("Attempted to modify protected object");
|
|
65
|
+
});
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wallace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"author": "Andrew Buchan",
|
|
5
5
|
"description": "The framework that brings you FREEDOM!!",
|
|
6
6
|
"license": "ISC",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"test": "jest --clearCache && jest"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"babel-plugin-wallace": "^0.
|
|
16
|
+
"babel-plugin-wallace": "^0.5.0",
|
|
17
17
|
"browserify": "^17.0.1"
|
|
18
18
|
},
|
|
19
|
-
"gitHead": "
|
|
19
|
+
"gitHead": "12194040c835faf9d4f29e105dd908bacdbbb944"
|
|
20
20
|
}
|