brew-js-react 0.5.5 → 0.5.7

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.
@@ -99,4 +99,10 @@ export default class FlyoutMixin extends ClassNameMixin {
99
99
  * @returns A promise that resolves after closing animation completes.
100
100
  */
101
101
  close(state?: any): Promise<void>;
102
+ /**
103
+ * Toggles the flyout.
104
+ * @param source Source element which triggered the flyout.
105
+ * @returns A promise that resolves when the flyout is being closed.
106
+ */
107
+ toggleSelf(source?: Element): Promise<any>;
102
108
  }
@@ -1,5 +1,5 @@
1
1
  import { definePrototype, extend, makeArray, pick } from "zeta-dom/util";
2
- import { closeFlyout, isFlyoutOpen, openFlyout } from "brew-js/domAction";
2
+ import { closeFlyout, openFlyout, toggleFlyout } from "brew-js/domAction";
3
3
  import { app } from "../app.js";
4
4
  import ClassNameMixin from "./ClassNameMixin.js";
5
5
  import FlyoutToggleMixin from "./FlyoutToggleMixin.js";
@@ -57,15 +57,14 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
57
57
  });
58
58
  },
59
59
  open: function (value, source) {
60
- var element = this.elements()[0];
61
- if (!isFlyoutOpen(element)) {
62
- valueMap.set(element, value);
63
- }
64
- return openFlyout(element, source, this.getOptions());
60
+ return openFlyout(this.elements()[0], value, source, this.getOptions());
65
61
  },
66
62
  close: function (value) {
67
63
  return closeFlyout(this.elements()[0], value);
68
64
  },
65
+ toggleSelf: function (source) {
66
+ return toggleFlyout(this.elements()[0], source, this.getOptions());
67
+ },
69
68
  onOpen: function (callback) {
70
69
  var element = this.elements()[0];
71
70
  return this.onToggleState(function (opened) {
@@ -84,6 +83,17 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
84
83
  var self = this;
85
84
  FlyoutMixinSuper.initElement.call(self, element, state);
86
85
  self.onDispose(app.on(element, {
86
+ flyoutshow: function (e) {
87
+ valueMap.set(element, e.data);
88
+ self.isFlyoutOpened = true;
89
+ self.visible = true;
90
+ },
91
+ flyoutclose: function () {
92
+ self.isFlyoutOpened = false;
93
+ },
94
+ flyouthide: function () {
95
+ self.visible = false;
96
+ },
87
97
  animationstart: function () {
88
98
  self.animating = true;
89
99
  },
@@ -91,14 +101,5 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
91
101
  self.animating = false;
92
102
  },
93
103
  }, true));
94
- },
95
- onClassNameUpdated: function (element, prevState, state) {
96
- var self = this;
97
- var isFlyoutOpened = isFlyoutOpen(element);
98
- if (!isFlyoutOpened) {
99
- valueMap.delete(element);
100
- }
101
- self.visible = state.open;
102
- self.isFlyoutOpened = isFlyoutOpened;
103
104
  }
104
105
  });
@@ -7,6 +7,17 @@ import FlyoutMixin from "./FlyoutMixin";
7
7
  * Instances of this mixin is exposed by {@link FlyoutMixin.toggle}.
8
8
  */
9
9
  export default class FlyoutToggleMixin extends ClassNameMixin {
10
+ /**
11
+ * Specifies the condition on when the flyout should toggle. It can be either:
12
+ *
13
+ * - `click`: flyout is toggled when applied element is clicked;
14
+ * - `focus`: flyout is opened or closed when applied element has gained or lost focus.
15
+ *
16
+ * Default behavior is `click`.
17
+ *
18
+ * @param trigger Condition of trigger.
19
+ */
20
+ on(trigger: 'focus' | 'click'): this;
10
21
  /**
11
22
  * Opens the associated flyout.
12
23
  * @param state Value to be sent to listener added by {@link FlyoutMixin.onOpen}.
@@ -20,4 +31,10 @@ export default class FlyoutToggleMixin extends ClassNameMixin {
20
31
  * @returns A promise that resolves after closing animation completes.
21
32
  */
22
33
  close(state?: any): Promise<void>;
34
+ /**
35
+ * Toggles the associated flyout.
36
+ * @param source Source element which triggered the flyout.
37
+ * @returns A promise that resolves when the flyout is being closed.
38
+ */
39
+ toggle(source?: Element): Promise<any>;
23
40
  }
@@ -1,6 +1,5 @@
1
1
  import dom from "zeta-dom/dom";
2
2
  import { definePrototype } from "zeta-dom/util";
3
- import { toggleFlyout } from "brew-js/domAction";
4
3
  import ClassNameMixin from "./ClassNameMixin.js";
5
4
 
6
5
  const FlyoutToggleMixinSuper = ClassNameMixin.prototype;
@@ -10,18 +9,39 @@ export default function FlyoutToggleMixin(mixin) {
10
9
  this.flyoutMixin = mixin;
11
10
  }
12
11
 
12
+ function triggerFlyoutAction(self, state, trigger, action, args) {
13
+ if ((state.trigger || 'click') === trigger) {
14
+ action.apply(self, args);
15
+ }
16
+ }
17
+
13
18
  definePrototype(FlyoutToggleMixin, ClassNameMixin, {
19
+ on: function (trigger) {
20
+ this.state.trigger = trigger;
21
+ return this;
22
+ },
14
23
  open: function (value, source) {
15
24
  return this.flyoutMixin.open(value, source);
16
25
  },
17
26
  close: function (value) {
18
27
  return this.flyoutMixin.close(value);
19
28
  },
29
+ toggle: function (source) {
30
+ return this.flyoutMixin.toggleSelf(source);
31
+ },
20
32
  initElement: function (element, state) {
21
33
  var self = this;
22
34
  FlyoutToggleMixinSuper.initElement.call(self, element, state);
23
- self.onDispose(dom.on(element, 'click', function () {
24
- toggleFlyout(self.flyoutMixin.elements()[0], element, self.flyoutMixin.getOptions());
35
+ self.onDispose(dom.on(element, {
36
+ focusin: function () {
37
+ triggerFlyoutAction(self, state, 'focus', self.open, [null, dom.activeElement]);
38
+ },
39
+ focusout: function () {
40
+ triggerFlyoutAction(self, state, 'focus', self.close, []);
41
+ },
42
+ click: function () {
43
+ triggerFlyoutAction(self, state, 'click', self.toggle, [element]);
44
+ }
25
45
  }));
26
46
  }
27
47
  });
@@ -16,6 +16,7 @@ definePrototype(MixinRefImpl, {
16
16
  export default function StatefulMixin() {
17
17
  Mixin.call(this);
18
18
  _(this, {
19
+ pending: {},
19
20
  elements: new Set(),
20
21
  states: new WeakMap(),
21
22
  flush: watch(this, false),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brew-js-react",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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.1.1",
16
- "brew-js": ">=0.6.2",
16
+ "brew-js": ">=0.6.5",
17
17
  "waterpipe": "^2.5.0",
18
18
  "zeta-dom": ">=0.4.10",
19
19
  "zeta-dom-react": ">=0.4.14"
@@ -27,7 +27,7 @@
27
27
  "@babel/preset-env": "^7.16.11",
28
28
  "@babel/preset-react": "^7.16.7",
29
29
  "@jest/globals": "^26.6.2",
30
- "@misonou/build-utils": "^1.1.3",
30
+ "@misonou/build-utils": "^1.3.1",
31
31
  "@misonou/test-utils": "^1.0.3",
32
32
  "@testing-library/dom": "^8.11.3",
33
33
  "@testing-library/react": "^12.1.2",
package/view.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /// <reference path="./brew.d.ts" />
2
+
1
3
  import { useRouteState } from "./hooks";
2
4
 
3
5
  export type ViewComponentRootProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
@@ -8,7 +10,25 @@ export type ViewComponent<P> = React.FC<ViewProps<P>>;
8
10
  export type ViewContainerState = ViewContext;
9
11
 
10
12
  export interface PageChangeEvent extends Zeta.ZetaEventBase {
13
+ /**
14
+ * Gets information of the current page.
15
+ */
16
+ readonly page: Brew.PageInfo;
17
+ /**
18
+ * Gets information of the previous page.
19
+ */
11
20
  readonly previousPage: Brew.PageInfo;
21
+ /**
22
+ * Gets how user has triggered navigation.
23
+ * @see {Brew.RouterEvent.navigationType}
24
+ */
25
+ readonly navigationType: Brew.NavigationType;
26
+ /**
27
+ * Defers completion of navigation.
28
+ * @param args One or more promises.
29
+ * @returns Whether the supplied promises will be awaited.
30
+ */
31
+ waitFor(...args: Promise<any>[]): boolean;
12
32
  }
13
33
 
14
34
  export interface ViewContextEventMap {
package/view.js CHANGED
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { Component, Fragment, createContext, createElement, useContext, useEffect } from "react";
2
2
  import { useAsync } from "zeta-dom-react";
3
3
  import dom from "zeta-dom/dom";
4
4
  import { notifyAsync } from "zeta-dom/domLock";
@@ -17,7 +17,7 @@ const sortedViews = [];
17
17
  const emitter = new ZetaEventContainer();
18
18
  const rootContext = freeze(extend(new ViewContext(), { container: root }));
19
19
  const rootState = _(rootContext);
20
- const StateContext = React.createContext(rootContext);
20
+ const StateContext = createContext(rootContext);
21
21
 
22
22
  var errorView;
23
23
  /** @type {Partial<Zeta.ZetaEventType<"beforepageload", Brew.RouterEventMap, Element>>} */
@@ -58,7 +58,12 @@ function ViewContext(view, page, parent) {
58
58
  setActive: defineObservableProperty(self, 'active', !!page, true)
59
59
  });
60
60
  watch(self, 'page', function (page, previousPage) {
61
- emitter.emit('pagechange', self, { previousPage });
61
+ emitter.emit('pagechange', self, {
62
+ page: page,
63
+ previousPage: previousPage,
64
+ navigationType: event.navigationType,
65
+ waitFor: event.waitFor
66
+ });
62
67
  });
63
68
  }
64
69
 
@@ -74,12 +79,12 @@ definePrototype(ViewContext, {
74
79
  });
75
80
 
76
81
  function ErrorBoundary() {
77
- React.Component.apply(this, arguments);
82
+ Component.apply(this, arguments);
78
83
  this.state = {};
79
84
  }
80
85
  ErrorBoundary.contextType = StateContext;
81
86
 
82
- definePrototype(ErrorBoundary, React.Component, {
87
+ definePrototype(ErrorBoundary, Component, {
83
88
  componentDidCatch: function (error) {
84
89
  var self = this;
85
90
  if (errorView && !self.state.error) {
@@ -90,6 +95,8 @@ definePrototype(ErrorBoundary, React.Component, {
90
95
  setImmediate(function () {
91
96
  dom.emit('error', self.context.container, { error }, true);
92
97
  });
98
+ // ensure promise sent to beforepageload event is resolved
99
+ self.props.onComponentLoaded();
93
100
  }
94
101
  },
95
102
  render: function () {
@@ -101,9 +108,9 @@ definePrototype(ErrorBoundary, React.Component, {
101
108
  };
102
109
  var onComponentLoaded = self.props.onComponentLoaded;
103
110
  if (props.error) {
104
- return React.createElement(errorView, { onComponentLoaded, viewProps: props });
111
+ return createElement(errorView, { onComponentLoaded, viewProps: props });
105
112
  }
106
- return React.createElement(props.view, { onComponentLoaded, viewProps: self.props.viewProps });
113
+ return createElement(props.view, { onComponentLoaded, viewProps: self.props.viewProps });
107
114
  },
108
115
  reset: function () {
109
116
  this.setState({ error: null });
@@ -111,11 +118,11 @@ definePrototype(ErrorBoundary, React.Component, {
111
118
  });
112
119
 
113
120
  function ViewContainer() {
114
- React.Component.apply(this, arguments);
121
+ Component.apply(this, arguments);
115
122
  }
116
123
  ViewContainer.contextType = StateContext;
117
124
 
118
- definePrototype(ViewContainer, React.Component, {
125
+ definePrototype(ViewContainer, Component, {
119
126
  setActive: noop,
120
127
  componentDidMount: function () {
121
128
  var self = this;
@@ -143,7 +150,7 @@ definePrototype(ViewContainer, React.Component, {
143
150
  self.updateView();
144
151
  }
145
152
  (self.onRender || noop)();
146
- return React.createElement(React.Fragment, null, self.prevView, self.currentView);
153
+ return createElement(Fragment, null, self.prevView, self.currentView);
147
154
  },
148
155
  updateView: function () {
149
156
  var self = this;
@@ -152,7 +159,7 @@ definePrototype(ViewContainer, React.Component, {
152
159
  if (V && (viewChanged || !(self.children || '')[0])) {
153
160
  // ensure the current path actually corresponds to the matched view
154
161
  // when some views are not included in the list of allowed views
155
- var targetPath = resolvePath(V, getCurrentParams(V, true));
162
+ var targetPath = resolvePath(V, app.route);
156
163
  if (targetPath !== removeQueryAndHash(app.path)) {
157
164
  app.navigate(targetPath, true);
158
165
  return;
@@ -173,14 +180,14 @@ definePrototype(ViewContainer, React.Component, {
173
180
  if (self.currentState === state) {
174
181
  unmountView = function () {
175
182
  self.prevView = self.currentView;
176
- app.emit('pageleave', element, { pathname: state.page.path }, true);
183
+ app.emit('pageleave', element, { pathname: state.page.path, view: V }, true);
177
184
  animateOut(element, 'show').then(function () {
178
185
  self.prevView = undefined;
179
186
  self.forceUpdate();
180
187
  });
181
188
  };
182
189
  animateIn(element, 'show', '[brew-view]', true);
183
- app.emit('pageenter', element, { pathname: state.page.path }, true);
190
+ app.emit('pageenter', element, { pathname: state.page.path, view: V }, true);
184
191
  }
185
192
  });
186
193
  notifyAsync(element, promise);
@@ -190,10 +197,10 @@ definePrototype(ViewContainer, React.Component, {
190
197
  viewContext: state,
191
198
  viewData: event.data || {}
192
199
  });
193
- var view = React.createElement(StateContext.Provider, { key: routeMap.get(V).id, value: state },
194
- React.createElement(ViewStateContainer, null,
195
- React.createElement('div', extend({}, self.props.rootProps, { ref: initElement, 'brew-view': '' }),
196
- React.createElement(ErrorBoundary, { onComponentLoaded, viewProps }))));
200
+ var view = createElement(StateContext.Provider, { key: routeMap.get(V).id, value: state },
201
+ createElement(ViewStateContainer, null,
202
+ createElement('div', extend({}, self.props.rootProps, { ref: initElement, 'brew-view': '' }),
203
+ createElement(ErrorBoundary, { onComponentLoaded, viewProps }))));
197
204
  extend(self, _(state), {
198
205
  currentState: state,
199
206
  currentView: view,
@@ -215,7 +222,7 @@ definePrototype(ViewContainer, React.Component, {
215
222
  }
216
223
  });
217
224
 
218
- function getCurrentParams(view, includeAll, params) {
225
+ function getCurrentParams(view, params) {
219
226
  var state = routeMap.get(view);
220
227
  if (!state.maxParams) {
221
228
  var matchers = exclude(state.matchers, ['remainingSegments']);
@@ -244,7 +251,7 @@ function getCurrentParams(view, includeAll, params) {
244
251
  });
245
252
  }
246
253
  }
247
- return pick(params || app.route, includeAll ? state.maxParams : state.minParams);
254
+ return extend(pick(app.route, state.minParams), params && pick(params, state.maxParams), state.params);
248
255
  }
249
256
 
250
257
  function sortViews(a, b) {
@@ -262,8 +269,8 @@ function matchViewParams(view, route) {
262
269
  function createViewComponent(factory) {
263
270
  var promise;
264
271
  throwNotFunction(factory);
265
- if (factory.prototype instanceof React.Component) {
266
- factory = React.createElement.bind(null, factory);
272
+ if (factory.prototype instanceof Component) {
273
+ factory = createElement.bind(null, factory);
267
274
  }
268
275
  return function fn(props) {
269
276
  var viewProps = props.viewProps;
@@ -275,21 +282,24 @@ function createViewComponent(factory) {
275
282
  }
276
283
  var state = useAsync(function () {
277
284
  return promise.then(function (s) {
278
- return React.createElement(s.default, viewProps);
285
+ return createElement(s.default, viewProps);
279
286
  });
280
287
  }, !!promise)[1];
281
- if (!promise || !state.loading) {
282
- props.onComponentLoaded();
283
- if (state.error) {
284
- throw state.error;
288
+ var loaded = !promise || !state.loading;
289
+ useEffect(function () {
290
+ if (loaded) {
291
+ setImmediate(props.onComponentLoaded);
285
292
  }
293
+ }, [loaded]);
294
+ if (state.error) {
295
+ throw state.error;
286
296
  }
287
- return children || state.value || React.createElement(React.Fragment);
297
+ return children || state.value || createElement(Fragment);
288
298
  };
289
299
  }
290
300
 
291
301
  export function useViewContext() {
292
- return React.useContext(StateContext);
302
+ return useContext(StateContext);
293
303
  }
294
304
 
295
305
  export function isViewMatched(view) {
@@ -349,16 +359,14 @@ export function renderView() {
349
359
  }
350
360
  });
351
361
  views.sort(sortViews);
352
- return React.createElement(ViewContainer, { rootProps, views, defaultView });
362
+ return createElement(ViewContainer, { rootProps, views, defaultView });
353
363
  }
354
364
 
355
365
  export function resolvePath(view, params) {
356
- var state = routeMap.get(view);
357
- if (!state) {
366
+ if (!routeMap.has(view)) {
358
367
  return '/';
359
368
  }
360
- var newParams = extend(getCurrentParams(view), getCurrentParams(view, true, params || {}), state.params);
361
- return app.route.getPath(newParams);
369
+ return app.route.getPath(getCurrentParams(view, params));
362
370
  }
363
371
 
364
372
  export function linkTo(view, params) {