brew-js-react 0.4.1 → 0.4.3

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.
@@ -1,56 +1,56 @@
1
- import { definePrototype, each, equal, extend, setImmediate } from "../include/zeta-dom/util.js";
2
- import { containsOrEquals } from "../include/zeta-dom/domUtil.js";
3
- import dom from "../include/zeta-dom/dom.js";
4
- import { watchOwnAttributes } from "../include/zeta-dom/observe.js";
5
- import StatefulMixin from "./StatefulMixin.js";
6
-
7
- const ClassNameMixinSuper = StatefulMixin.prototype;
8
-
9
- function checkState(self, element, state, isAsync) {
10
- var classNames = state.classNames;
11
- var prev = extend({}, classNames);
12
- each(self.classNames, function (i, v) {
13
- classNames[v] = element.classList.contains(v);
14
- });
15
- if (!equal(prev, classNames)) {
16
- var cb = self.onClassNameUpdated.bind(self, element, prev, extend({}, classNames));
17
- if (isAsync) {
18
- setImmediate(cb);
19
- } else {
20
- cb();
21
- }
22
- }
23
- }
24
-
25
- export default function ClassNameMixin(classNames) {
26
- StatefulMixin.call(this);
27
- this.classNames = classNames || [];
28
- }
29
-
30
- definePrototype(ClassNameMixin, StatefulMixin, {
31
- getClassNames: function () {
32
- return [this.state.classNames];
33
- },
34
- getRef: function () {
35
- var self = this;
36
- var element = self.state.element;
37
- if (element && containsOrEquals(dom.root, element)) {
38
- checkState(self, element, self.state, true);
39
- }
40
- return ClassNameMixinSuper.getRef.call(this);
41
- },
42
- initState: function () {
43
- return {
44
- element: null,
45
- classNames: {}
46
- };
47
- },
48
- initElement: function (element, state) {
49
- var self = this;
50
- watchOwnAttributes(element, 'class', function () {
51
- checkState(self, element, state);
52
- });
53
- },
54
- onClassNameUpdated: function (element, prevState, state) {
55
- }
56
- });
1
+ import { definePrototype, each, equal, extend, setImmediate } from "../include/zeta-dom/util.js";
2
+ import { containsOrEquals } from "../include/zeta-dom/domUtil.js";
3
+ import dom from "../include/zeta-dom/dom.js";
4
+ import { watchOwnAttributes } from "../include/zeta-dom/observe.js";
5
+ import StatefulMixin from "./StatefulMixin.js";
6
+
7
+ const ClassNameMixinSuper = StatefulMixin.prototype;
8
+
9
+ function checkState(self, element, state, isAsync) {
10
+ var classNames = state.classNames;
11
+ var prev = extend({}, classNames);
12
+ each(self.classNames, function (i, v) {
13
+ classNames[v] = element.classList.contains(v);
14
+ });
15
+ if (!equal(prev, classNames)) {
16
+ var cb = self.onClassNameUpdated.bind(self, element, prev, extend({}, classNames));
17
+ if (isAsync) {
18
+ setImmediate(cb);
19
+ } else {
20
+ cb();
21
+ }
22
+ }
23
+ }
24
+
25
+ export default function ClassNameMixin(classNames) {
26
+ StatefulMixin.call(this);
27
+ this.classNames = classNames || [];
28
+ }
29
+
30
+ definePrototype(ClassNameMixin, StatefulMixin, {
31
+ getClassNames: function () {
32
+ return [this.state.classNames];
33
+ },
34
+ getRef: function () {
35
+ var self = this;
36
+ var element = self.state.element;
37
+ if (element && containsOrEquals(dom.root, element)) {
38
+ checkState(self, element, self.state, true);
39
+ }
40
+ return ClassNameMixinSuper.getRef.call(this);
41
+ },
42
+ initState: function () {
43
+ return {
44
+ element: null,
45
+ classNames: {}
46
+ };
47
+ },
48
+ initElement: function (element, state) {
49
+ var self = this;
50
+ watchOwnAttributes(element, 'class', function () {
51
+ checkState(self, element, state);
52
+ });
53
+ },
54
+ onClassNameUpdated: function (element, prevState, state) {
55
+ }
56
+ });
@@ -0,0 +1,5 @@
1
+ import StatefulMixin from "./StatefulMixin";
2
+
3
+ export default class ScrollIntoViewMixin extends StatefulMixin {
4
+ when(deps: React.DependencyList): this;
5
+ }
@@ -0,0 +1,21 @@
1
+ import { definePrototype, equal, makeArray, setImmediateOnce } from "../include/zeta-dom/util.js";
2
+ import { scrollIntoView } from "../include/zeta-dom/domUtil.js";
3
+ import StatefulMixin from "./StatefulMixin.js";
4
+
5
+ export default function ScrollIntoViewMixin() {
6
+ StatefulMixin.call(this);
7
+ }
8
+
9
+ definePrototype(ScrollIntoViewMixin, StatefulMixin, {
10
+ when: function (deps) {
11
+ var state = this.state;
12
+ var callback = state.callback || (state.callback = function () {
13
+ scrollIntoView(state.element);
14
+ });
15
+ if (state.deps && !equal(deps, state.deps)) {
16
+ setImmediateOnce(callback);
17
+ }
18
+ state.deps = makeArray(deps);
19
+ return this;
20
+ }
21
+ });
@@ -8,6 +8,7 @@ export interface ScrollableMixinOptions {
8
8
  handle?: 'auto' | 'scrollbar' | 'content';
9
9
  paged?: 'always' | 'landscape' | 'portrait';
10
10
  pagedItemSelector?: string;
11
+ persistScroll?: boolean;
11
12
  }
12
13
 
13
14
  export default class ScrollableMixin extends ClassNameMixin implements JQueryScrollable {
@@ -31,6 +32,6 @@ export default class ScrollableMixin extends ClassNameMixin implements JQueryScr
31
32
  scrollTo(x: number, y: number, duration?: number, callback?: () => void): Promise<void>;
32
33
  scrollByPage(x: number, y: number, duration?: number, callback?: () => void): Promise<void>;
33
34
  scrollToPage(x: number, y: number, duration?: number, callback?: () => void): Promise<void>;
34
- scrollToElement(target: Element, targetOrigin: string, duration: number, callback?: () => void): Promise<void>;
35
- scrollToElement(target: Element, targetOrigin?: string, wrapperOrigin?: string, duration?: number, callback?: () => void): Promise<void>;
35
+ scrollToElement(target: Element | string, targetOrigin: string, duration: number, callback?: () => void): Promise<void>;
36
+ scrollToElement(target: Element | string, targetOrigin?: string, wrapperOrigin?: string, duration?: number, callback?: () => void): Promise<void>;
36
37
  }
@@ -28,6 +28,8 @@ definePrototype(ScrollableMixin, ClassNameMixin, {
28
28
  'scroller-snap-page': options.paged,
29
29
  'scroller-page': options.pagedItemSelector,
30
30
  'scroller-state': 'pageIndex'
31
+ }, options.persistScroll && {
32
+ 'persist-scroll': ''
31
33
  });
32
34
  },
33
35
  onPageIndexChanged: function (callback) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brew-js-react",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/view.d.ts CHANGED
@@ -2,11 +2,28 @@ import { useRouteState } from "./hooks";
2
2
 
3
3
  export type ViewComponentRootProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
4
4
  export type ViewComponent<P> = React.FC<ViewProps<P>>;
5
+ /**
6
+ * @deprecated Alias of {@link ViewContext}
7
+ */
8
+ export type ViewContainerState = ViewContext;
9
+
10
+ export interface PageChangeEvent extends Zeta.ZetaEventBase {
11
+ readonly previousPage: Brew.PageInfo;
12
+ }
5
13
 
6
- export interface ViewContainerState {
14
+ export interface ViewContextEventMap {
15
+ pagechange: PageChangeEvent;
16
+ }
17
+
18
+ export interface ViewContext extends Zeta.ZetaEventDispatcher<ViewContextEventMap, ViewContext> {
7
19
  readonly container: HTMLElement;
8
20
  readonly view: ViewComponent<any>;
9
21
  readonly active: boolean;
22
+ /**
23
+ * Gets information of current page that rendered the view.
24
+ * Unlike {@link Brew.WithRouter.page}, it will not change when user has navigated away and the current view is going to unmount.
25
+ */
26
+ readonly page: Brew.PageInfo;
10
27
  }
11
28
 
12
29
  export interface ViewProps<S = {}> {
@@ -17,6 +34,10 @@ export interface ViewProps<S = {}> {
17
34
  * pass individual data to {@link React.useState} or {@link useRouteState}.
18
35
  */
19
36
  readonly viewData: { readonly [P in keyof S]?: S[P] };
37
+ /**
38
+ * Gets information scoped to the rendered view.
39
+ */
40
+ readonly viewContext: ViewContext;
20
41
  /**
21
42
  * Gets how user landed on this view component.
22
43
  * @see {@link Brew.RouterEvent.navigationType}
@@ -39,6 +60,11 @@ export interface ErrorViewProps {
39
60
  reset(): void;
40
61
  }
41
62
 
63
+ export function useViewContext(): ViewContext;
64
+
65
+ /**
66
+ * @deprecated Alias of {@link useViewContext}
67
+ */
42
68
  export function useViewContainerState(): ViewContainerState;
43
69
 
44
70
  /**
package/view.js CHANGED
@@ -2,17 +2,21 @@ 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, freeze, grep, isFunction, isThenable, isUndefinedOrNull, keys, makeArray, map, noop, pick, randomId, setImmediate, single, throwNotFunction, watch } from "./include/zeta-dom/util.js";
5
+ import { ZetaEventContainer } from "./include/zeta-dom/events.js";
6
+ import { any, catchAsync, combineFn, createPrivateStore, defineObservableProperty, defineOwnProperty, 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
7
  import { animateIn, animateOut } from "./include/brew-js/anim.js";
7
8
  import { removeQueryAndHash } from "./include/brew-js/util/path.js";
8
9
  import { app, onAppInit } from "./app.js";
9
10
  import { ViewStateContainer } from "./hooks.js";
10
11
 
12
+ const _ = createPrivateStore();
11
13
  const root = dom.root;
12
14
  const routeMap = new Map();
13
15
  const usedParams = {};
14
16
  const sortedViews = [];
15
- const StateContext = React.createContext(Object.freeze({ container: root, active: true }));
17
+ const emitter = new ZetaEventContainer();
18
+ const rootContext = freeze(extend(new ViewContext(null, null), { container: root }));
19
+ const StateContext = React.createContext(rootContext);
16
20
 
17
21
  var errorView;
18
22
  /** @type {Partial<Zeta.ZetaEventType<"beforepageload", Brew.RouterEventMap, Element>>} */
@@ -21,9 +25,28 @@ var event = {};
21
25
  onAppInit(function () {
22
26
  app.on('beforepageload', function (e) {
23
27
  event = e;
28
+ _(rootContext).setPage(app.page);
24
29
  });
25
30
  });
26
31
 
32
+ function ViewContext(view, page) {
33
+ var self = this;
34
+ defineOwnProperty(self, 'view', view, true);
35
+ _(self, {
36
+ setPage: defineObservableProperty(self, 'page', page, true),
37
+ setActive: defineObservableProperty(self, 'active', true, true)
38
+ });
39
+ watch(self, 'page', function (page, previousPage) {
40
+ emitter.emit('pagechange', self, { previousPage });
41
+ });
42
+ }
43
+
44
+ definePrototype(ViewContext, {
45
+ on: function (event, handler) {
46
+ return emitter.add(this, event, handler);
47
+ }
48
+ });
49
+
27
50
  function ErrorBoundary() {
28
51
  React.Component.apply(this, arguments);
29
52
  this.state = {};
@@ -65,6 +88,7 @@ function ViewContainer() {
65
88
  React.Component.apply(this, arguments);
66
89
  this.stateId = history.state;
67
90
  }
91
+ ViewContainer.contextType = StateContext;
68
92
 
69
93
  definePrototype(ViewContainer, React.Component, {
70
94
  componentDidMount: function () {
@@ -72,32 +96,41 @@ definePrototype(ViewContainer, React.Component, {
72
96
  var self = this;
73
97
  self.componentWillUnmount = combineFn(
74
98
  watch(app.route, function () {
75
- self.setActive(self.getViewComponent() === self.currentViewComponent);
99
+ (self.setActive || noop)(self.getViewComponent() === self.currentViewComponent);
76
100
  }),
77
101
  app.on('beforepageload', function () {
78
102
  self.stateId = history.state;
79
- self.updateView();
80
- self.forceUpdate();
103
+ if (self.context === rootContext || self.updateOnNext) {
104
+ event.waitFor(new Promise(function (resolve) {
105
+ self.onRender = resolve;
106
+ }));
107
+ self.updateView();
108
+ self.forceUpdate();
109
+ }
81
110
  })
82
111
  );
83
112
  },
84
113
  render: function () {
85
114
  /** @type {any} */
86
115
  var self = this;
87
- if (history.state === self.stateId) {
116
+ if (history.state === self.stateId && self.context.active) {
88
117
  self.updateView();
89
118
  }
119
+ (self.onRender || noop)();
90
120
  return React.createElement(React.Fragment, null, self.prevView, self.currentView);
91
121
  },
92
122
  updateView: function () {
93
123
  var self = this;
94
124
  var V = self.getViewComponent();
125
+ self.updateOnNext = false;
95
126
  if (V) {
96
127
  // ensure the current path actually corresponds to the matched view
97
128
  // when some views are not included in the list of allowed views
98
129
  var targetPath = resolvePath(V, getCurrentParams(V, true));
99
130
  if (targetPath !== removeQueryAndHash(app.path)) {
100
131
  app.navigate(targetPath, true);
132
+ self.updateOnNext = true;
133
+ return;
101
134
  }
102
135
  }
103
136
  if (V && V !== self.currentViewComponent) {
@@ -112,6 +145,8 @@ definePrototype(ViewContainer, React.Component, {
112
145
  self.forceUpdate();
113
146
  });
114
147
  }
148
+ (self.cancelPrevious || noop)();
149
+
115
150
  var onComponentLoaded;
116
151
  var promise = new Promise(function (resolve) {
117
152
  onComponentLoaded = resolve;
@@ -125,23 +160,25 @@ definePrototype(ViewContainer, React.Component, {
125
160
  });
126
161
  notifyAsync(element, promise);
127
162
  });
163
+ var state = new ViewContext(V, app.page);
128
164
  var viewProps = freeze({
129
165
  navigationType: event.navigationType,
166
+ viewContext: state,
130
167
  viewData: event.data || {}
131
168
  });
132
- var state = { view: V };
133
169
  var view = React.createElement(StateContext.Provider, { key: routeMap.get(V).id, value: state },
134
170
  React.createElement(ViewStateContainer, null,
135
171
  React.createElement('div', extend({}, self.props.rootProps, { ref: initElement }),
136
172
  React.createElement(ErrorBoundary, { onComponentLoaded, viewProps }))));
137
- extend(self, {
173
+ extend(self, _(state), {
174
+ cancelPrevious: onComponentLoaded,
138
175
  currentPath: app.path,
139
176
  currentView: view,
140
- currentViewComponent: V,
141
- setActive: defineObservableProperty(state, 'active', true, true)
177
+ currentViewComponent: V
142
178
  });
143
179
  (event.waitFor || noop)(promise);
144
180
  }
181
+ (self.setPage || noop)(app.page);
145
182
  },
146
183
  getViewComponent: function () {
147
184
  var props = this.props;
@@ -205,6 +242,7 @@ function createViewComponent(factory) {
205
242
  if (isThenable(children)) {
206
243
  promise = children;
207
244
  children = null;
245
+ catchAsync(promise);
208
246
  }
209
247
  var state = useAsync(function () {
210
248
  return promise.then(function (s) {
@@ -221,7 +259,7 @@ function createViewComponent(factory) {
221
259
  };
222
260
  }
223
261
 
224
- export function useViewContainerState() {
262
+ export function useViewContext() {
225
263
  return React.useContext(StateContext);
226
264
  }
227
265
 
@@ -301,3 +339,7 @@ export function navigateTo(view, params, data, replace) {
301
339
  export function redirectTo(view, params, data) {
302
340
  return navigateTo(view, params, data, true);
303
341
  }
342
+
343
+ export {
344
+ useViewContext as useViewContainerState
345
+ }