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 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
@@ -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 { buildComponent, replaceNode } from "./utils";
3
- import { KeyedRepeater, SequentialRepeater } from "./repeaters";
5
+ import { replaceNode } from "./utils";
6
+
4
7
  const throwAway = document.createElement("template");
5
8
 
6
9
  /**
7
- * Create an element from html string.
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 makeEl(html) {
10
- throwAway.innerHTML = html;
11
- return throwAway.content.firstChild;
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, cls) {
39
+ export function nestComponent(rootElement, path, componentDefinition) {
19
40
  const el = findElement(rootElement, path),
20
- child = buildComponent(cls);
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 = makeEl(html);
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
- ComponentDefinition.stubs = {};
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 = 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();
@@ -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
- You can override these methods, and add new ones:
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 access the instance as `this` in methods, but `self` in the JSX.
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 props={tasks} />
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, 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
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 display this list by hovering over any JSX element, like `div`.
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 props={ctrl.getTasks()} />
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 props={tasks.slice(1)} />
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
- ## Utility functions
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 keys are set:
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(obj, () => console.log('obj modified);
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
- other?: {
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
- props,
534
+ items,
495
535
  show,
496
536
  hide
497
537
  }: {
498
- props: Array<Props>;
538
+ items: Array<Props>;
499
539
  show?: boolean;
500
540
  hide?: boolean;
501
541
  }): JSX.Element;
502
- methods?(
503
- object: ComponenMethods<Props, Controller> &
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 ust be placed as shown:
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
- } & Methods;
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 calls `callback` when keys are set, and this
594
- * 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:
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 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.
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: CallableFunction, timeout?: number): T;
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 excludes this element from the DOM if the condition is false,
729
- * and does not render dynamic elements underneath. */
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 or repeated component.
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 props={arrayOfProps} />
935
+ * <MyComponent.repeat items={arrayOfProps} />
814
936
  * ```
815
- * But note that repeat must not have siblings.
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 or repeated component, in which case it must be
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 `xyz`.
955
+ * - `toggle:xyz` toggles `xyz` as defined by `class:xyz` on same element, or class
956
+ * `xyz`.
832
957
  *
833
- * 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`.
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 = 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.3.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.3.0",
16
+ "babel-plugin-wallace": "^0.5.0",
17
17
  "browserify": "^17.0.1"
18
18
  },
19
- "gitHead": "d5f2ea2912b6ec729775d2ccf17973a9e791115f"
19
+ "gitHead": "12194040c835faf9d4f29e105dd908bacdbbb944"
20
20
  }