wallace 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/component.js CHANGED
@@ -151,7 +151,11 @@ export function createConstructor(baseComponent) {
151
151
  return proto;
152
152
  }
153
153
  });
154
-
154
+ Component.create = function (props, ctrl) {
155
+ const component = new Component();
156
+ component.render(props, ctrl);
157
+ return component;
158
+ };
155
159
  Component.stubs = {} && baseComponent.stubs;
156
160
  return Component;
157
161
  }
package/lib/index.js CHANGED
@@ -5,6 +5,7 @@ export { nestComponent } from "./nest";
5
5
  export { saveRef } from "./refs";
6
6
  export { KeyedRepeater } from "./repeaters/keyed";
7
7
  export { SequentialRepeater } from "./repeaters/sequential";
8
+ export { Router, route } from "./router";
8
9
  export { getStub } from "./stubs";
9
10
  export { findElement, onEvent, stashMisc } from "./utils";
10
11
  export { watch, protect } from "./watch";
package/lib/router.jsx ADDED
@@ -0,0 +1,120 @@
1
+ /*
2
+ The Router is a component which mounts other components based on hash (URL bit after #).
3
+
4
+ It currently expects the route to be made of chunks separated by / which are either
5
+ text or placeholders:
6
+
7
+ /todos/detail/{id:int}/notes
8
+
9
+ It performs no validation yet, and may change to use regex in future.
10
+ */
11
+
12
+ const events = ["load", "hashchange"];
13
+ const noMod = x => x;
14
+ const converters = {
15
+ int: v => parseInt(v),
16
+ float: v => parseFloat(v),
17
+ date: v => new Date(v)
18
+ };
19
+
20
+ export function route(path, componentDef, converter, cleanup) {
21
+ return new Route(path, componentDef, converter, cleanup);
22
+ }
23
+
24
+ export const Router = () => <div></div>;
25
+
26
+ Router.methods = {
27
+ render(props, ctrl) {
28
+ const defaultError = (error, router) => (router.el.innerHTML = error.message);
29
+ this.error = props.error || defaultError;
30
+ this.current = null;
31
+ events.forEach(e => window.addEventListener(e, () => this.onHashChange()));
32
+ if (props.atts) {
33
+ Object.keys(props.atts).forEach(k => {
34
+ this.el.setAttribute(k, props.atts[k]);
35
+ });
36
+ }
37
+ this.base.render.call(this, props, ctrl);
38
+ },
39
+ async onHashChange() {
40
+ const path = location.hash.slice(1) || "",
41
+ routes = this.props.routes,
42
+ len = routes.length;
43
+ let i = 0,
44
+ routeData;
45
+ try {
46
+ while (i < len) {
47
+ let route = routes[i];
48
+ if ((routeData = route.match(path))) {
49
+ const component = await route.getComponent(routeData, this.ctrl);
50
+ this.current && this.current.cleanup();
51
+ this.mount(component);
52
+ this.current = route;
53
+ return;
54
+ }
55
+ i++;
56
+ }
57
+ throw new Error(`Router unable to match path "${path}"`);
58
+ } catch (error) {
59
+ this.error(error, this);
60
+ }
61
+ },
62
+ mount(component) {
63
+ this.el.innerHTML = "";
64
+ this.el.appendChild(component.el);
65
+ }
66
+ };
67
+
68
+ export function Route(path, def, convert, cleanup) {
69
+ this.chunks = path
70
+ .split("/")
71
+ .map(s => (s.startsWith("{") ? new RouteArg(s.slice(1, -1)) : s));
72
+ this.def = def;
73
+ this._convert = convert || noMod;
74
+ this._cleanup = cleanup || noMod;
75
+ this.component = null;
76
+ }
77
+
78
+ Route.prototype = {
79
+ match(url) {
80
+ const parts = url.split("?", 2),
81
+ hash = parts[0],
82
+ query = parts[1],
83
+ args = {},
84
+ definedChunksCount = this.chunks.length,
85
+ foundChunks = hash.split("/");
86
+ if (definedChunksCount !== foundChunks.length) return;
87
+ let i = 0;
88
+ while (i < definedChunksCount) {
89
+ let definedChunk = this.chunks[i];
90
+ let foundChunk = foundChunks[i];
91
+ if (definedChunk instanceof RouteArg) {
92
+ args[definedChunk.name] = definedChunk.convert(foundChunk);
93
+ } else if (definedChunk != foundChunk) {
94
+ return;
95
+ }
96
+ i++;
97
+ }
98
+ return { args, params: new URLSearchParams(query), url };
99
+ },
100
+ async getComponent(routeData, ctrl) {
101
+ if (!this.component) {
102
+ this.component = new this.def();
103
+ }
104
+ const props = await this._convert(routeData);
105
+ this.component.render(props, ctrl);
106
+ return this.component;
107
+ },
108
+ /**
109
+ * Allows user to delete component or perform other cleanup.
110
+ */
111
+ cleanup() {
112
+ this._cleanup(this);
113
+ }
114
+ };
115
+
116
+ function RouteArg(str) {
117
+ let chunks = str.split(":");
118
+ this.name = chunks[0];
119
+ this.convert = converters[chunks[1]] || noMod;
120
+ }
package/lib/types.d.ts CHANGED
@@ -526,6 +526,7 @@ declare module "wallace" {
526
526
  ThisType<ComponentInstance<Props, Controller, Methods>>;
527
527
  // Methods will not be available on nested component, so omit.
528
528
  readonly stubs?: Record<string, ComponentFunction<Props, Controller>>;
529
+ create?(props: Props): ComponentInstance<Props, Controller, Methods>;
529
530
  }
530
531
 
531
532
  type ComponenMethods<Props, Controller> = {
@@ -580,7 +581,6 @@ declare module "wallace" {
580
581
  * The component constructor function (typed as a class, but isn't).
581
582
  */
582
583
  export class Component<Props = any, Controller = any> {
583
- render(props: Props, ctrl?: Controller): void;
584
584
  /**
585
585
  * The base render method looks like this:
586
586
  *
@@ -691,6 +691,32 @@ declare module "wallace" {
691
691
  * @returns a Proxy of the object.
692
692
  */
693
693
  export function watch<T>(target: T, callback: WatchCallback): T;
694
+
695
+ export type RouteData = {
696
+ args: { [key: string]: any };
697
+ params: URLSearchParams;
698
+ url: string;
699
+ };
700
+
701
+ export function route<Props>(
702
+ path: string,
703
+ componentDef: ComponentFunction<Props>,
704
+ converter: RouteConverter<Props>
705
+ ): Route<Props>;
706
+
707
+ type RouteConverter<Props> = (routedata: RouteData) => Props;
708
+
709
+ export type Route<Props> = [string, ComponentFunction<Props>, RouteConverter<Props>?];
710
+ export type RouterProps = {
711
+ routes: readonly Route<unknown>[];
712
+ atts?: Record<string, unknown>;
713
+ error?: (error: Error, router: Router) => void;
714
+ };
715
+
716
+ export class Router extends Component {
717
+ static nest?({ props }: { props?: RouterProps }): JSX.Element;
718
+ mount(component: Component<any>): void;
719
+ }
694
720
  }
695
721
 
696
722
  type MustBeExpression = Exclude<any, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallace",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "author": "Andrew Buchan",
5
5
  "description": "The framework that brings you FREEDOM!!",
6
6
  "license": "ISC",
@@ -14,8 +14,8 @@
14
14
  "test": "jest --clearCache && jest"
15
15
  },
16
16
  "dependencies": {
17
- "babel-plugin-wallace": "^0.6.0",
17
+ "babel-plugin-wallace": "^0.7.0",
18
18
  "browserify": "^17.0.1"
19
19
  },
20
- "gitHead": "451efab81055751fbda64eb80252ec150460e375"
20
+ "gitHead": "6cd84a9e6135e31d01880a82e6294ae0034bca59"
21
21
  }