wallace 0.4.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 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; // The controller.
8
- this.props = undefined; // The props passed to the component. May be changed.
9
- this.el = null; // the element - will be set during build.
10
- this.ref = {}; // user set references to elements or components.
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
@@ -2,8 +2,7 @@
2
2
  * Everything in here is used by or modified by the Babel plugin.
3
3
  */
4
4
  import { Component } from "./component";
5
- import { buildComponent, replaceNode } from "./utils";
6
- import { KeyedRepeater, SequentialRepeater } from "./repeaters";
5
+ import { replaceNode } from "./utils";
7
6
 
8
7
  const throwAway = document.createElement("template");
9
8
 
@@ -37,9 +36,9 @@ export function findElement(rootElement, path) {
37
36
  return path.reduce((acc, index) => acc.childNodes[index], rootElement);
38
37
  }
39
38
 
40
- export function nestComponent(rootElement, path, cls) {
39
+ export function nestComponent(rootElement, path, componentDefinition) {
41
40
  const el = findElement(rootElement, path),
42
- child = buildComponent(cls);
41
+ child = new componentDefinition();
43
42
  replaceNode(el, child.el);
44
43
  return child;
45
44
  }
@@ -66,14 +65,6 @@ export function onEvent(element, eventName, callback) {
66
65
  return element;
67
66
  }
68
67
 
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
68
  export function defineComponent(html, watches, queries, buildFunction, inheritFrom) {
78
69
  const ComponentDefinition = _createConstructor(inheritFrom || Component);
79
70
  const prototype = ComponentDefinition.prototype;
@@ -86,23 +77,40 @@ export function defineComponent(html, watches, queries, buildFunction, inheritFr
86
77
  return ComponentDefinition;
87
78
  }
88
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
+ */
89
86
  function _createConstructor(base) {
90
87
  const ComponentDefinition = function () {
91
88
  base.call(this);
92
89
  };
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, {
90
+ const proto = Object.create(base && base.prototype, {
100
91
  constructor: {
101
92
  value: ComponentDefinition,
102
93
  writable: true,
103
94
  configurable: true
104
95
  }
105
96
  });
106
- ComponentDefinition.prototype.base = Component.prototype;
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;
107
115
  return ComponentDefinition;
108
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 = buildComponent(componentDefinition);
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 = buildComponent(componentDefinition);
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. Utility functions
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 it methods:
74
+ `mount` returns the component instance, allowing you to call its methods:
75
75
 
76
76
  ```tsx
77
77
  root.update();
@@ -100,11 +100,11 @@ places.
100
100
 
101
101
  #### Overriding
102
102
 
103
- You can override these methods, and add new ones using `methods` directly on the
103
+ You can override these methods, and add new ones using `methods` property of the
104
104
  component definition:
105
105
 
106
106
  ```tsx
107
- MyComponent.methods({
107
+ MyComponent.methods = {
108
108
  render(props) {
109
109
  this.ctrl = new MyController(this, props);
110
110
  this.update();
@@ -112,7 +112,7 @@ MyComponent.methods({
112
112
  getName() {
113
113
  return 'wallace';
114
114
  }
115
- });
115
+ };
116
116
  ```
117
117
 
118
118
  This has the same effect as setting them on the prototype:
@@ -124,11 +124,11 @@ MyComponent.prototype.render = function () {};
124
124
  You can use `this.base` to access methods on the base `Component` class:
125
125
 
126
126
  ```tsx
127
- MyComponent.methods({
127
+ MyComponent.methods = {
128
128
  render(props) {
129
129
  this.base.render.call(this, props, ctrl);
130
130
  }
131
- });
131
+ };
132
132
  ```
133
133
 
134
134
  Note that `base` is not the same as `super` in classes which access the lowest override.
@@ -201,11 +201,22 @@ Notes:
201
201
 
202
202
  ## 4. Directives
203
203
 
204
- Directives are attributes with special behaviours, as listed below. Each has more
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
205
216
  detailed information on its JSDoc, which should display as a tool tip\* when you hover
206
217
  over it in your IDE.
207
218
 
208
- You can also display this list by hovering over any JSX element, like `div`.
219
+ You can also
209
220
 
210
221
  - `apply` runs a callback to modify an element.
211
222
  - `bind` updates a value when an input is changed.
@@ -221,7 +232,6 @@ an array.
221
232
  - `style:xyz` sets a specific style property.
222
233
  - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class `xyz`.
223
234
 
224
- \* The tool tip won't display when you use a qualifier, like `class:danger`.
225
235
 
226
236
  ## 5. Controllers
227
237
 
@@ -266,12 +276,12 @@ const TaskList = (_, {ctrl}) => (
266
276
  </div>
267
277
  );
268
278
 
269
- TaskList.methods({
279
+ TaskList.methods = {
270
280
  render(_, ctrl) {
271
281
  this.ctrl = new TaskController(this, ctrl);
272
282
  this.update();
273
283
  }
274
- });
284
+ };
275
285
  ```
276
286
 
277
287
  ## 6. Inheritance
@@ -392,12 +402,12 @@ const Task: Uses<null, null, TaskMethods> = (_, { self }) => (
392
402
  <div>{self.getName()}</div>
393
403
  ));
394
404
 
395
- Task.methods({
405
+ Task.methods = {
396
406
  getName() { return 'wallace' },
397
407
  render(props, ctrl) { // types are already known
398
408
  this.props = { ...props, notallowed: 1 }; // type error
399
409
  }
400
- });
410
+ };
401
411
  ```
402
412
 
403
413
  The type will pass into the object passed into `methods` so it recognises custom methods
@@ -445,7 +455,7 @@ Wallace defines some other types you may use:
445
455
  constructor, not a class)
446
456
  - `ComponentInstance<Props, Controller, Methods>` - a component instance.
447
457
 
448
- ## Utility functions
458
+ ## 9. Helpers
449
459
 
450
460
  Each of these has their own JSDoc, we just lsit them here.
451
461
 
@@ -469,11 +479,24 @@ mount("elementId", MyComponent, props, ctrl);
469
479
 
470
480
  ### watch
471
481
 
472
- Returns a Proxy of an object which calls `callback` when keys are set:
482
+ Returns a Proxy of an object which calls `callback` when it, or its nested objects are
483
+ modified:
484
+
485
+ ```
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.
473
494
 
474
495
  ```
475
- watch(obj, () => console.log('obj modified);
496
+ const protectedObj = protect([]);
497
+ watchedObj[0] = 'foo'; // throws error.
476
498
  ```
499
+
477
500
  ---
478
501
  Report any issues to https://github.com/wallace-js/wallace (and please give it a ★)
479
502
 
@@ -495,9 +518,7 @@ declare module "wallace" {
495
518
  ctrl: Controller;
496
519
  self: ComponentInstance<Props, Controller, Methods>;
497
520
  event: Event;
498
- ev: Event;
499
521
  element: HTMLElement;
500
- el: HTMLElement;
501
522
  }
502
523
  ): JSX.Element;
503
524
  nest?({
@@ -518,10 +539,8 @@ declare module "wallace" {
518
539
  show?: boolean;
519
540
  hide?: boolean;
520
541
  }): JSX.Element;
521
- methods?(
522
- object: ComponenMethods<Props, Controller> &
523
- ThisType<ComponentInstance<Props, Controller, Methods>>
524
- ): void;
542
+ // methods?: ComponenMethods<Props, Controller> &
543
+ // ThisType<ComponentInstance<Props, Controller, Methods>>;
525
544
  readonly prototype?: ComponenMethods<Props, Controller> &
526
545
  ThisType<ComponentInstance<Props, Controller, Methods>>;
527
546
  // Methods will not be available on nested component, so omit.
@@ -643,8 +662,21 @@ declare module "wallace" {
643
662
  ): ComponentInstance<Props, Controller, Methods>;
644
663
 
645
664
  /**
646
- * Returns a Proxy of an object which calls `callback` when keys are set, and this
647
- * extends to nested objects:
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:
648
680
  *
649
681
  * ```js
650
682
  * ar = watch([], callback)
@@ -656,18 +688,23 @@ declare module "wallace" {
656
688
  * obj.y = {}
657
689
  * obj.y.z = 1000
658
690
  * ```
659
- * The callback does not indicate the data has changed, only that a key was set.
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.
660
698
  *
661
699
  * Some methods like `Array.push` set the index and then the length immediately after,
662
700
  * so we use a timeout period to avoid calling the callback twice for what is really a
663
701
  * single operation.
664
702
  *
665
703
  * @param {*} target - Any object, including arrays.
666
- * @param {*} timeout - Any value in ms. Defaults to 100.
667
704
  * @param {*} callback - A callback function.
668
705
  * @returns a Proxy of the object.
669
706
  */
670
- export function watch<T>(target: T, callback: CallableFunction, timeout?: number): T;
707
+ export function watch<T>(target: T, callback: WatchCallback): T;
671
708
  }
672
709
 
673
710
  type MustBeExpression = Exclude<any, string>;
@@ -739,8 +776,6 @@ interface DirectiveAttributes extends AllDomEvents {
739
776
  * );
740
777
  * ```
741
778
  *
742
- * Unfortunately you lose the tooltip in that format.
743
- *
744
779
  * Note that destructured props are converted to member expressions, so these examples
745
780
  * work even though it looks like you're setting a local variable.
746
781
  */
@@ -760,11 +795,34 @@ interface DirectiveAttributes extends AllDomEvents {
760
795
  * ```
761
796
  * <div class:danger="danger red" toggle:danger={expr}></div>
762
797
  * ```
763
- *
764
- * Unfortunately you lose the tooltip in that format.
765
798
  */
766
799
  class?: any;
767
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
+
768
826
  /** ## Wallace directive: hide
769
827
  *
770
828
  * Set the element's `hidden` property and if true, does not render dynamic elements
@@ -876,13 +934,15 @@ declare namespace JSX {
876
934
  * <MyComponent.nest props={singleProps} />
877
935
  * <MyComponent.repeat items={arrayOfProps} />
878
936
  * ```
879
- * But note that repeat must not have siblings.
937
+ * Note that repeated components must not have siblings.
880
938
  *
881
939
  * Available Wallace directives:
882
940
  *
883
941
  * - `apply` runs a callback to modify an element.
884
942
  * - `bind` updates a value when an input is changed.
885
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.
886
946
  * - `hide` sets an element or component's hidden property.
887
947
  * - `html` Set the element's `innnerHTML` property.
888
948
  * - `if` excludes an element from the DOM.
@@ -892,9 +952,12 @@ declare namespace JSX {
892
952
  * - `ref` saves a reference to an element or nested component.
893
953
  * - `show` sets and element or component's hidden property.
894
954
  * - `style:xyz` sets a specific style property.
895
- * - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class `xyz`.
955
+ * - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
956
+ * `xyz`.
896
957
  *
897
- * Note the tool tip won't display when you use a qualifier, like `class:danger`.
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`.
898
961
  */
899
962
  [elemName: string]: DirectiveAttributes & Record<string, any>;
900
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 = buildComponent(componentDefinition);
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
- * Builds a component's initial DOM.
27
- */
28
- export function buildComponent(componentDefinition) {
29
- // TODO: add a dev warning here:
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, grace) {
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
- // set value as proxy if object
51
- if (!prop.isProxy && typeof prop === "object")
52
- target[key] = new Proxy(prop, handler);
53
- return target[key];
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
- if (grace) {
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.4.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.4.0",
16
+ "babel-plugin-wallace": "^0.5.0",
17
17
  "browserify": "^17.0.1"
18
18
  },
19
- "gitHead": "286a00b51777c4b7fed9a778b93e9727ed905e2a"
19
+ "gitHead": "12194040c835faf9d4f29e105dd908bacdbbb944"
20
20
  }