brew-js-react 0.3.2 → 0.3.4

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/hooks.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createElement, useEffect, useRef, useState } from "react";
2
2
  import { ViewStateProvider } from "zeta-dom-react";
3
3
  import { definePrototype, extend, kv, setImmediateOnce, throwNotFunction, watch } from "./include/zeta-dom/util.js";
4
+ import { bind } from "./include/zeta-dom/domUtil.js";
4
5
  import { ZetaEventContainer } from "./include/zeta-dom/events.js";
5
6
  import { app } from "./app.js";
6
7
  import { useViewContainerState } from "./view.js";
@@ -77,13 +78,27 @@ export function useRouteParam(name, defaultValue) {
77
78
  return value;
78
79
  }
79
80
 
80
- export function useRouteState(key, defaultValue) {
81
- const container = useViewContainerState();
82
- const cur = getCurrentStates();
83
- const state = useState(key in cur ? cur[key] : defaultValue);
84
- if (container.active) {
81
+ export function useRouteState(key, defaultValue, snapshotOnUpdate) {
82
+ var container = useViewContainerState();
83
+ var cur = getCurrentStates();
84
+ var state = useState(key in cur ? cur[key] : defaultValue);
85
+ if (container.active && cur[key] !== state[0]) {
86
+ if (snapshotOnUpdate && key in cur) {
87
+ app.snapshot();
88
+ cur = getCurrentStates();
89
+ }
85
90
  cur[key] = state[0];
86
91
  }
92
+ useEffect(function () {
93
+ if (snapshotOnUpdate) {
94
+ return bind(window, 'popstate', function () {
95
+ if (container.active) {
96
+ var cur = getCurrentStates();
97
+ state[1](key in cur ? cur[key] : defaultValue);
98
+ }
99
+ });
100
+ }
101
+ }, [container, snapshotOnUpdate]);
87
102
  return state;
88
103
  }
89
104
 
package/mixin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from "react";
2
- import { extend } from "./include/zeta-dom/util.js";
2
+ import { extend, setImmediate } from "./include/zeta-dom/util.js";
3
3
  import Mixin from "./mixins/Mixin.js";
4
4
  import AnimateMixin from "./mixins/AnimateMixin.js";
5
5
  import AnimateSequenceItemMixin from "./mixins/AnimateSequenceItemMixin.js";
@@ -24,6 +24,15 @@ function createUseFunction(ctor) {
24
24
  };
25
25
  }
26
26
 
27
+ function disposeMixin(mixin) {
28
+ mixin.disposed = true;
29
+ setImmediate(function () {
30
+ if (mixin.disposed) {
31
+ mixin.dispose();
32
+ }
33
+ });
34
+ }
35
+
27
36
  export const useAnimateMixin = createUseFunction(AnimateMixin);
28
37
  export const useAnimateSequenceMixin = createUseFunction(AnimateSequenceMixin);
29
38
  export const useFlyoutMixin = createUseFunction(FlyoutMixin);
@@ -36,7 +45,8 @@ export function useMixin(ctor) {
36
45
  return new ctor();
37
46
  })[0].reset();
38
47
  useEffect(function () {
39
- return mixin.dispose.bind(mixin);
48
+ mixin.disposed = false;
49
+ return disposeMixin.bind(0, mixin);
40
50
  }, []);
41
51
  return mixin;
42
52
  }
@@ -79,14 +79,14 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
79
79
  if (!element.id) {
80
80
  element.id = 'flyout-' + (++flyoutMixinCounter);
81
81
  }
82
- app.on(element, {
82
+ self.onDispose(app.on(element, {
83
83
  animationstart: function () {
84
84
  self.animating = true;
85
85
  },
86
86
  animationcomplete: function () {
87
87
  self.animating = false;
88
88
  },
89
- }, true);
89
+ }, true));
90
90
  setImmediate(function () {
91
91
  each(self.toggle.elements(), function (i, v) {
92
92
  v.setAttribute('toggle', '#' + element.id);
@@ -12,7 +12,7 @@ export default function FocusStateMixin() {
12
12
  definePrototype(FocusStateMixin, StatefulMixin, {
13
13
  initElement: function (element, state) {
14
14
  FocusStateMixinSuper.initElement.call(this, element, state);
15
- dom.on(element, {
15
+ this.onDispose(dom.on(element, {
16
16
  focusin: function (e) {
17
17
  state.focused = true;
18
18
  setClass(element, 'focused', e.source);
@@ -21,7 +21,7 @@ definePrototype(FocusStateMixin, StatefulMixin, {
21
21
  state.focused = false;
22
22
  setClass(element, 'focused', false);
23
23
  }
24
- });
24
+ }));
25
25
  },
26
26
  getClassNames: function () {
27
27
  return [{ focused: !!this.state.focused }];
@@ -14,7 +14,7 @@ definePrototype(LoadingStateMixin, StatefulMixin, {
14
14
  initElement: function (element, state) {
15
15
  LoadingStateMixinSuper.initElement.call(this, element, state);
16
16
  lock(element);
17
- dom.on(element, {
17
+ this.onDispose(dom.on(element, {
18
18
  asyncStart: function () {
19
19
  state.loading = true;
20
20
  setClass(element, 'loading', true);
@@ -27,7 +27,7 @@ definePrototype(LoadingStateMixin, StatefulMixin, {
27
27
  state.loading = false;
28
28
  setClass(element, 'loading', false);
29
29
  }
30
- });
30
+ }));
31
31
  },
32
32
  getClassNames: function () {
33
33
  return [{ loading: !!this.state.loading }];
@@ -35,7 +35,7 @@ definePrototype(ScrollableMixin, ClassNameMixin, {
35
35
  },
36
36
  initElement: function (element, state) {
37
37
  var self = this;
38
- app.on(element, {
38
+ self.onDispose(app.on(element, {
39
39
  statechange: function (e) {
40
40
  if ('pageIndex' in e.newValues) {
41
41
  extend(self, { pageIndex: e.newValues.pageIndex });
@@ -47,7 +47,7 @@ definePrototype(ScrollableMixin, ClassNameMixin, {
47
47
  scrollStop: function() {
48
48
  self.scrolling = false;
49
49
  }
50
- }, true);
50
+ }, true));
51
51
  },
52
52
  clone: function () {
53
53
  var mixin = ScrollableMixinSuper.clone.call(this);
@@ -16,7 +16,7 @@ definePrototype(MixinRefImpl, {
16
16
  export default function StatefulMixin() {
17
17
  Mixin.call(this);
18
18
  _(this, {
19
- elements: new WeakSet(),
19
+ elements: new Set(),
20
20
  flush: watch(this, false),
21
21
  dispose: [],
22
22
  states: {},
@@ -84,6 +84,7 @@ definePrototype(StatefulMixin, Mixin, {
84
84
  var states = state.states;
85
85
  combineFn(state.dispose.splice(0))();
86
86
  state.flush();
87
+ state.elements.clear();
87
88
  each(states, function (i, v) {
88
89
  delete states[i];
89
90
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brew-js-react",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,7 +13,7 @@
13
13
  "repository": "github:misonou/brew-js-react",
14
14
  "dependencies": {
15
15
  "@misonou/react-dom-client": "^1.0.3",
16
- "brew-js": ">=0.4.7",
16
+ "brew-js": ">=0.4.10",
17
17
  "waterpipe": "^2.5.0",
18
18
  "zeta-dom": ">=0.3.1",
19
19
  "zeta-dom-react": ">=0.2.1"
@@ -43,6 +43,9 @@
43
43
  "webpack": "^5.3.0",
44
44
  "webpack-cli": "^4.1.0"
45
45
  },
46
+ "sideEffects": [
47
+ "./src/app.js"
48
+ ],
46
49
  "keywords": [
47
50
  "html",
48
51
  "dom",
package/view.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { useRouteState } from "./hooks";
2
+
1
3
  export type ViewComponentRootProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
2
- export type ViewComponent<P> = React.FC<P>;
4
+ export type ViewComponent<P> = React.FC<ViewProps<P>>;
3
5
 
4
6
  export interface ViewContainerState {
5
7
  readonly container: HTMLElement;
@@ -7,6 +9,16 @@ export interface ViewContainerState {
7
9
  readonly active: boolean;
8
10
  }
9
11
 
12
+ export interface ViewProps<S = {}> {
13
+ /**
14
+ * Gets the additional data passed from {@link navigateTo} or {@link redirectTo}.
15
+ *
16
+ * The data object is read-only, to use incoming data as mutable view states,
17
+ * pass individual data to {@link React.useState} or {@link useRouteState}.
18
+ */
19
+ viewData: { readonly [P in keyof S]?: S[P] };
20
+ }
21
+
10
22
  export interface ErrorViewProps {
11
23
  /**
12
24
  * Gets the original view component in which error has thrown.
@@ -30,7 +42,7 @@ export function useViewContainerState(): ViewContainerState;
30
42
  * @param factory A callback that returns a promise resolving a React component, typically using async `import`.
31
43
  * @param params A dictionary containing route parameters.
32
44
  */
33
- export function registerView<P>(factory: () => Promise<{ default: React.ComponentType<P> }>, params: Zeta.Dictionary<null | string | RegExp | ((value: string) => boolean)>): ViewComponent<P>;
45
+ export function registerView<P>(factory: () => Promise<{ default: React.ComponentType<P> }>, params: Zeta.Dictionary<null | string | RegExp | ((value: string) => boolean)>): ViewComponent<P extends ViewProps<infer S> ? S : {}>;
34
46
 
35
47
  /**
36
48
  * Registers view component with specific route paramters.
@@ -38,7 +50,7 @@ export function registerView<P>(factory: () => Promise<{ default: React.Componen
38
50
  * @param component A React component.
39
51
  * @param params A dictionary containing route parameters.
40
52
  */
41
- export function registerView<P>(component: React.ComponentType<P>, params: Zeta.Dictionary<null | string | RegExp | ((value: string) => boolean)>): ViewComponent<P>;
53
+ export function registerView<P>(component: React.ComponentType<P>, params: Zeta.Dictionary<null | string | RegExp | ((value: string) => boolean)>): ViewComponent<P extends ViewProps<infer S> ? S : {}>;
42
54
 
43
55
  /**
44
56
  * Registers a default error view to be displayed when view component failed to render.
@@ -94,12 +106,31 @@ export function renderView(...args: ViewComponent<any>[]): JSX.Element;
94
106
  export function renderView(props: ViewComponentRootProps, ...args: ViewComponent<any>[]): JSX.Element;
95
107
 
96
108
  /**
97
- * Returns pathname that will render the specified view.
109
+ * Returns the app path that will render the specified view.
110
+ *
98
111
  * The pathname is resolved using route parameters given to {@link registerView},
99
112
  * current route parameters, as well as any supplemeneted route parameters
100
113
  * against registered routes in {@link Brew.RouterOptions.routes}.
114
+ *
115
+ * This is used by methods such as {@link linkTo} and {@link navigateTo}.
116
+ *
117
+ * @param view A view component created by {@link registerView}.
118
+ * @param params Extra route parameters that supplements or overrides current route parameters.
119
+ */
120
+ export function resolvePath(view: ViewComponent<any>, params?: Zeta.Dictionary<string>): string;
121
+
122
+ /**
123
+ * Returns a link usable in `href` attribute or method such as `window.open`
124
+ * that when visiting the link through browser the specifed view will be rendered.
125
+ *
126
+ * This function wraps {@link resolvePath} with {@link Brew.WithRouter.toHref}.
127
+ * The returned link will depend on `urlMode` and related options initializing the router.
128
+ * When `urlMode` is set to `none`, {@link navigateTo} should always be used instead as
129
+ * navigation through link is disabled in such case.
130
+ *
101
131
  * @param view A view component created by {@link registerView}.
102
132
  * @param params Extra route parameters that supplements or overrides current route parameters.
133
+ * @see {@link resolvePath}.
103
134
  */
104
135
  export function linkTo(view: ViewComponent<any>, params?: Zeta.Dictionary<string>): string;
105
136
 
@@ -107,14 +138,16 @@ export function linkTo(view: ViewComponent<any>, params?: Zeta.Dictionary<string
107
138
  * Navigates to path that will render the specified view.
108
139
  * @param view A view component created by {@link registerView}.
109
140
  * @param params Extra route parameters that supplements or overrides current route parameters.
110
- * @see {@link linkTo}.
141
+ * @param data Additional data to be passed to view component, analogous to making form post to server rendering page.
142
+ * @see {@link resolvePath}.
111
143
  */
112
- export function navigateTo(view: ViewComponent<any>, params?: Zeta.Dictionary<string>): Promise<Brew.NavigateResult>;
144
+ export function navigateTo<T>(view: ViewComponent<T>, params?: Zeta.Dictionary<string> | null, data?: T): Promise<Brew.NavigateResult>;
113
145
 
114
146
  /**
115
147
  * Navigates to path that will render the specified view, replacing current state in browser history.
116
148
  * @param view A view component created by {@link registerView}.
117
149
  * @param params Extra route parameters that supplements or overrides current route parameters.
118
- * @see {@link linkTo}.
150
+ * @param data Additional data to be passed to view component, analogous to making form post to server rendering page.
151
+ * @see {@link resolvePath}.
119
152
  */
120
- export function redirectTo(view: ViewComponent<any>, params?: Zeta.Dictionary<string>): Promise<Brew.NavigateResult>;
153
+ export function redirectTo<T>(view: ViewComponent<T>, params?: Zeta.Dictionary<string> | null, data?: T): Promise<Brew.NavigateResult>;
package/view.js CHANGED
@@ -2,11 +2,11 @@ import React from "react";
2
2
  import { useAsync } from "zeta-dom-react";
3
3
  import dom from "./include/zeta-dom/dom.js";
4
4
  import { notifyAsync } from "./include/zeta-dom/domLock.js";
5
- import { any, combineFn, defineObservableProperty, definePrototype, each, exclude, executeOnce, extend, grep, isFunction, isThenable, isUndefinedOrNull, keys, makeArray, map, noop, pick, randomId, single, throwNotFunction, watch } from "./include/zeta-dom/util.js";
5
+ import { any, combineFn, defineObservableProperty, definePrototype, each, exclude, executeOnce, extend, freeze, grep, isFunction, isThenable, isUndefinedOrNull, keys, makeArray, map, noop, pick, randomId, setImmediate, single, throwNotFunction, watch } from "./include/zeta-dom/util.js";
6
6
  import { animateIn, animateOut } from "./include/brew-js/anim.js";
7
7
  import { removeQueryAndHash } from "./include/brew-js/util/path.js";
8
8
  import { app } from "./app.js";
9
- import { ViewStateContainer } from "./hooks.js";
9
+ import { ViewStateContainer, useRouteState } from "./hooks.js";
10
10
 
11
11
  const root = dom.root;
12
12
  const routeMap = new Map();
@@ -28,7 +28,11 @@ definePrototype(ErrorBoundary, React.Component, {
28
28
  if (errorView && !self.state.error) {
29
29
  self.setState({ error });
30
30
  } else {
31
- dom.emit('error', self.context.container, { error }, true);
31
+ // emit error in next tick as ref callback may yet to be invoked
32
+ // if error is thrown synchronously in first render
33
+ setImmediate(function () {
34
+ dom.emit('error', self.context.container, { error }, true);
35
+ });
32
36
  }
33
37
  },
34
38
  render: function () {
@@ -42,7 +46,7 @@ definePrototype(ErrorBoundary, React.Component, {
42
46
  if (props.error) {
43
47
  return React.createElement(errorView, { onComponentLoaded, viewProps: props });
44
48
  }
45
- return React.createElement(props.view, { onComponentLoaded });
49
+ return React.createElement(props.view, { onComponentLoaded, viewData: self.props.viewData });
46
50
  },
47
51
  reset: function () {
48
52
  this.setState({ error: null });
@@ -65,6 +69,7 @@ definePrototype(ViewContainer, React.Component, {
65
69
  app.on('beforepageload', function (e) {
66
70
  self.waitFor = e.waitFor;
67
71
  self.stateId = history.state;
72
+ self.updateView(e.data);
68
73
  self.forceUpdate();
69
74
  })
70
75
  );
@@ -72,14 +77,18 @@ definePrototype(ViewContainer, React.Component, {
72
77
  render: function () {
73
78
  /** @type {any} */
74
79
  var self = this;
75
- if (history.state !== self.stateId) {
76
- return self.lastChild || null;
80
+ if (history.state === self.stateId) {
81
+ self.updateView();
77
82
  }
83
+ return React.createElement(React.Fragment, null, self.prevView, self.currentView);
84
+ },
85
+ updateView: function (viewData) {
86
+ var self = this;
78
87
  var V = self.getViewComponent();
79
88
  if (V) {
80
89
  // ensure the current path actually corresponds to the matched view
81
90
  // when some views are not included in the list of allowed views
82
- var targetPath = linkTo(V, getCurrentParams(V, true));
91
+ var targetPath = resolvePath(V, getCurrentParams(V, true));
83
92
  if (targetPath !== removeQueryAndHash(app.path)) {
84
93
  app.navigate(targetPath, true);
85
94
  }
@@ -113,7 +122,7 @@ definePrototype(ViewContainer, React.Component, {
113
122
  var view = React.createElement(StateContext.Provider, { key: routeMap.get(V).id, value: state },
114
123
  React.createElement(ViewStateContainer, null,
115
124
  React.createElement('div', extend({}, self.props.rootProps, { ref: initElement }),
116
- React.createElement(ErrorBoundary, { onComponentLoaded }))));
125
+ React.createElement(ErrorBoundary, { onComponentLoaded, viewData }))));
117
126
  extend(self, {
118
127
  currentPath: app.path,
119
128
  currentView: view,
@@ -122,9 +131,6 @@ definePrototype(ViewContainer, React.Component, {
122
131
  });
123
132
  (self.waitFor || noop)(promise);
124
133
  }
125
- var child = React.createElement(React.Fragment, null, self.prevView, self.currentView);
126
- self.lastChild = child;
127
- return child;
128
134
  },
129
135
  getViewComponent: function () {
130
136
  var props = this.props;
@@ -182,8 +188,10 @@ function createViewComponent(factory) {
182
188
  if (factory.prototype instanceof React.Component) {
183
189
  factory = React.createElement.bind(null, factory);
184
190
  }
185
- return function (props) {
186
- var viewProps = Object.freeze(props.viewProps || {});
191
+ return function fn(props) {
192
+ var viewProps = props.viewProps || {
193
+ viewData: useRouteState('_d_' + routeMap.get(fn).id, props.viewData || {})[0]
194
+ };
187
195
  var children = !promise && factory(viewProps);
188
196
  if (isThenable(children)) {
189
197
  promise = children;
@@ -259,7 +267,7 @@ export function renderView() {
259
267
  return React.createElement(ViewContainer, { rootProps, views, defaultView });
260
268
  }
261
269
 
262
- export function linkTo(view, params) {
270
+ export function resolvePath(view, params) {
263
271
  var state = routeMap.get(view);
264
272
  if (!state) {
265
273
  return '/';
@@ -268,10 +276,14 @@ export function linkTo(view, params) {
268
276
  return app.route.getPath(newParams);
269
277
  }
270
278
 
271
- export function navigateTo(view, params) {
272
- return app.navigate(linkTo(view, params));
279
+ export function linkTo(view, params) {
280
+ return app.toHref(resolvePath(view, params));
281
+ }
282
+
283
+ export function navigateTo(view, params, data, replace) {
284
+ return app.navigate(resolvePath(view, params), replace, data && freeze(extend({}, data)));
273
285
  }
274
286
 
275
- export function redirectTo(view, params) {
276
- return app.navigate(linkTo(view, params), true);
287
+ export function redirectTo(view, params, data) {
288
+ return navigateTo(view, params, data, true);
277
289
  }