brew-js-react 0.2.0 → 0.2.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.
package/i18n.d.ts CHANGED
@@ -63,6 +63,13 @@ export function makeTranslation<T extends Zeta.Dictionary<object>, K extends key
63
63
  type ResourceKey<T> = { [P in ResourcePrefix]: StringKeysOf<ResourceObject[P]> }[T];
64
64
  type ResourceGlobalKey = { [P in ResourcePrefix]: `${P}.${StringKeysOf<ResourceObject[P]>}` }[ResourcePrefix];
65
65
 
66
+ interface GetKeys {
67
+ /**
68
+ * Gets the list of translation key under the given prefix.
69
+ */
70
+ <T extends ResourcePrefix>(prefix: T): ResourceKey<T>[];
71
+ }
72
+
66
73
  interface GetTranslation {
67
74
  /**
68
75
  * Create translation callback which only looks up all prefixes.
@@ -89,8 +96,9 @@ export function makeTranslation<T extends Zeta.Dictionary<object>, K extends key
89
96
  <T extends readonly ResourcePrefix[]>(...args: T): Translation<ResourceKey<ArrayMember<T>>>;
90
97
  }
91
98
 
99
+ declare const keys: GetKeys;
92
100
  declare const translate: Translate<ResourceGlobalKey>;
93
101
  declare const getTranslation: GetTranslation;
94
102
  declare const useTranslation: UseTranslationHook;
95
- return { translate, getTranslation, useTranslation };
103
+ return { keys, translate, getTranslation, useTranslation };
96
104
  }
package/i18n.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useObservableProperty } from "zeta-dom-react";
2
2
  import waterpipe from "./include/external/waterpipe.js"
3
- import { extend, makeArray, single } from "./include/zeta-dom/util.js";
3
+ import { extend, keys, makeArray, single } from "./include/zeta-dom/util.js";
4
4
  import { app } from "./app.js";
5
5
 
6
6
  const empty = Object.create(null);
@@ -18,7 +18,8 @@ if (toPrimitive) {
18
18
 
19
19
  function createCallback(translate) {
20
20
  var callback = function (key, data) {
21
- return translate(key, data, true);
21
+ var result = translate(key, data, true);
22
+ return result !== undefined ? result : key;
22
23
  };
23
24
  return extend(callback, {
24
25
  html: function (id, data) {
@@ -38,27 +39,33 @@ export function makeTranslation(resources, defaultLang) {
38
39
  const re = new RegExp('^(' + Object.keys(resources[defaultLang]).join('|') + ')\\.');
39
40
  const cache = {};
40
41
 
41
- function getTranslation(prefix, name, data, noEncode) {
42
- var str = ((resources[app.language] || empty)[prefix] || empty)[name] || ((resources[defaultLang] || empty)[prefix] || empty)[name] || '';
43
- if (str && (!noEncode || data !== undefined)) {
44
- return waterpipe(str, data, { noEncode });
42
+ function getTranslation(prefix, name, data, noEncode, lang) {
43
+ var str = ((resources[lang] || empty)[prefix] || empty)[name];
44
+ if (typeof str === 'string') {
45
+ if (str && (!noEncode || data !== undefined)) {
46
+ return waterpipe(str, data, { noEncode });
47
+ }
48
+ return str;
49
+ }
50
+ if (lang !== defaultLang) {
51
+ return getTranslation(prefix, name, data, noEncode, defaultLang);
45
52
  }
46
- return str;
47
53
  }
48
54
 
49
55
  function translate(key, data, noEncode) {
50
56
  var prefix = re.test(key) ? RegExp.$1 : '';
51
57
  var name = prefix ? key.slice(RegExp.lastMatch.length) : key;
52
- return getTranslation(prefix, name, data, noEncode) || key;
58
+ return getTranslation(prefix, name, data, noEncode, app.language);
53
59
  }
54
60
 
55
61
  function getTranslationCallback() {
56
62
  var prefix = makeArray(arguments);
57
63
  var key = prefix.join(' ');
58
64
  return cache[key] || (cache[key] = createCallback(function (key, data, noEncode) {
65
+ var lang = app.language;
59
66
  return single(prefix, function (v) {
60
- return getTranslation(v, key, data, noEncode);
61
- }) || key;
67
+ return getTranslation(v, key, data, noEncode, lang);
68
+ });
62
69
  }));
63
70
  }
64
71
 
@@ -72,6 +79,9 @@ export function makeTranslation(resources, defaultLang) {
72
79
  return {
73
80
  translate: cache[''],
74
81
  getTranslation: getTranslationCallback,
75
- useTranslation
82
+ useTranslation: useTranslation,
83
+ keys: function (prefix) {
84
+ return keys(resources[defaultLang][prefix] || empty);
85
+ }
76
86
  };
77
87
  }
@@ -0,0 +1 @@
1
+ export * from "brew-js/var";
@@ -0,0 +1 @@
1
+ export * from "zeta-dom/domLock";
package/mixin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
  import { extend } from "./include/zeta-dom/util.js";
3
3
  import Mixin from "./mixins/Mixin.js";
4
4
  import AnimateMixin from "./mixins/AnimateMixin.js";
@@ -34,9 +34,13 @@ export const useLoadingStateMixin = createUseFunction(LoadingStateMixin);
34
34
  export const useScrollableMixin = createUseFunction(ScrollableMixin);
35
35
 
36
36
  export function useMixin(ctor) {
37
- return useState(function () {
37
+ var mixin = useState(function () {
38
38
  return new ctor();
39
39
  })[0].reset();
40
+ useEffect(function () {
41
+ return mixin.dispose.bind(mixin);
42
+ }, []);
43
+ return mixin;
40
44
  }
41
45
 
42
46
  export function useMixinRef(mixin) {
@@ -52,9 +52,6 @@ definePrototype(ClassNameMixin, StatefulMixin, {
52
52
  }
53
53
  });
54
54
  },
55
- clone: function () {
56
- return extend(ClassNameMixinSuper.clone.call(this), { classNames: this.classNames });
57
- },
58
55
  onClassNameUpdated: function (element, prevState, state) {
59
56
  }
60
57
  });
@@ -4,6 +4,7 @@ import { AnimationEffect } from "./AnimateMixin";
4
4
 
5
5
  export interface FlyoutMixinOptions {
6
6
  modal?: boolean;
7
+ tabThrough?: boolean;
7
8
  swipeToDismiss?: Zeta.Direction;
8
9
  }
9
10
 
@@ -11,11 +12,13 @@ export default class FlyoutMixin extends ClassNameMixin {
11
12
  readonly isFlyoutOpened: boolean;
12
13
  readonly animating: boolean;
13
14
  readonly visible: boolean;
14
- readonly toggle: ClassNameMixin;
15
+ readonly toggle: FlyoutToggleMixin;
15
16
  modal: boolean;
16
17
 
17
18
  withEffects(...effects: AnimationEffect[]): this;
18
- onOpen(callback: () => void): Zeta.UnregisterCallback;
19
+ onOpen(callback: (state: any) => void): Zeta.UnregisterCallback;
19
20
  onToggleState(callback: (state: boolean) => void): Zeta.UnregisterCallback;
20
21
  onVisibilityChanged(callback: (state: boolean) => void): Zeta.UnregisterCallback;
22
+ open(state?: any): Promise<any>;
23
+ close(state?: any): Promise<void>;
21
24
  }
@@ -1,15 +1,19 @@
1
- import { defineAliasProperty, definePrototype, each, extend, makeArray } from "../include/zeta-dom/util.js";
1
+ import { defineAliasProperty, definePrototype, each, extend, kv, makeArray, randomId } from "../include/zeta-dom/util.js";
2
+ import { closeFlyout, openFlyout } from "../include/brew-js/domAction.js";
3
+ import { declareVar, getVar } from "../include/brew-js/var.js";
2
4
  import { app } from "../app.js";
3
5
  import ClassNameMixin from "./ClassNameMixin.js";
4
6
  import FlyoutToggleMixin from "./FlyoutToggleMixin.js";
5
7
 
6
8
  const FlyoutMixinSuper = ClassNameMixin.prototype;
9
+ const varname = '__flyout' + randomId();
7
10
  var flyoutMixinCounter = 0;
8
11
 
9
12
  export default function FlyoutMixin() {
10
13
  var self = this;
11
- ClassNameMixin.call(self, ['open', 'closing', 'tweening-in', 'tweening-out']);
14
+ ClassNameMixin.call(self, ['open', 'closing', 'visible', 'tweening-in', 'tweening-out']);
12
15
  self.modal = false;
16
+ self.tabThrough = false;
13
17
  self.isFlyoutOpened = false;
14
18
  self.animating = false;
15
19
  self.visible = false;
@@ -36,16 +40,25 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
36
40
  'swipe-dismiss': self.swipeToDismiss
37
41
  }, self.modal && {
38
42
  'is-modal': ''
43
+ }, self.tabThrough && {
44
+ 'tab-through': ''
39
45
  }, self.effects && {
40
46
  'animate-on': 'open',
41
47
  'animate-in': self.effects.join(' '),
42
48
  'animate-out': ''
43
49
  });
44
50
  },
51
+ open: function (value) {
52
+ return openFlyout(this.elements()[0], kv(varname, value));
53
+ },
54
+ close: function (value) {
55
+ return closeFlyout(this.elements()[0], value);
56
+ },
45
57
  onOpen: function (callback) {
58
+ var element = this.elements()[0];
46
59
  return this.onToggleState(function (opened) {
47
60
  if (opened) {
48
- return callback();
61
+ return callback(getVar(element, varname));
49
62
  }
50
63
  });
51
64
  },
@@ -60,6 +73,7 @@ definePrototype(FlyoutMixin, ClassNameMixin, {
60
73
  FlyoutMixinSuper.initElement.call(self, element, state);
61
74
  if (!element.id) {
62
75
  element.id = 'flyout-' + (++flyoutMixinCounter);
76
+ declareVar(element, varname, undefined);
63
77
  }
64
78
  app.on(element, {
65
79
  animationstart: function () {
@@ -1,3 +1,6 @@
1
1
  import ClassNameMixin from "./ClassNameMixin";
2
2
 
3
- export default class FlyoutToggleMixin extends ClassNameMixin { }
3
+ export default class FlyoutToggleMixin extends ClassNameMixin {
4
+ open(state?: any): Promise<any>;
5
+ close(state?: any): Promise<void>;
6
+ }
@@ -9,13 +9,16 @@ export default function FlyoutToggleMixin(mixin) {
9
9
  }
10
10
 
11
11
  definePrototype(FlyoutToggleMixin, ClassNameMixin, {
12
+ open: function (value) {
13
+ return this.flyoutMixin.open(value);
14
+ },
15
+ close: function (value) {
16
+ return this.flyoutMixin.close(value);
17
+ },
12
18
  getCustomAttributes: function () {
13
19
  var element = this.flyoutMixin.elements()[0];
14
20
  return extend({}, FlyoutToggleMixinSuper.getCustomAttributes.call(this), {
15
21
  'toggle': element && ('#' + element.id)
16
22
  });
17
- },
18
- clone: function () {
19
- return extend(FlyoutToggleMixinSuper.clone.call(this), { flyoutMixin: this.flyoutMixin });
20
23
  }
21
24
  });
@@ -13,9 +13,9 @@ definePrototype(FocusStateMixin, StatefulMixin, {
13
13
  initElement: function (element, state) {
14
14
  FocusStateMixinSuper.initElement.call(this, element, state);
15
15
  dom.on(element, {
16
- focusin: function () {
16
+ focusin: function (e) {
17
17
  state.focused = true;
18
- setClass(element, 'focused', true);
18
+ setClass(element, 'focused', e.source);
19
19
  },
20
20
  focusout: function () {
21
21
  state.focused = false;
package/mixins/Mixin.d.ts CHANGED
@@ -30,6 +30,10 @@ export default abstract class Mixin implements ClassNameProvider {
30
30
  * @private Internal use.
31
31
  */
32
32
  getCustomAttributes(): Zeta.Dictionary<string>;
33
+ /**
34
+ * @private Internal use.
35
+ */
36
+ dispose(): void;
33
37
 
34
38
  /**
35
39
  * Watches a property on the object.
package/mixins/Mixin.js CHANGED
@@ -16,6 +16,8 @@ definePrototype(Mixin, {
16
16
  },
17
17
  getCustomAttributes: function () {
18
18
  return {};
19
+ },
20
+ dispose: function () {
19
21
  }
20
22
  });
21
23
  watchable(Mixin.prototype);
@@ -1,4 +1,4 @@
1
- import { createPrivateStore, definePrototype, inherit, randomId, values } from "../include/zeta-dom/util.js";
1
+ import { createPrivateStore, definePrototype, each, inherit, randomId, values } from "../include/zeta-dom/util.js";
2
2
  import Mixin from "./Mixin.js";
3
3
 
4
4
  const _ = createPrivateStore();
@@ -65,12 +65,19 @@ definePrototype(StatefulMixin, Mixin, {
65
65
  initElement: function (element, state) {
66
66
  },
67
67
  clone: function () {
68
- const clone = inherit(Object.getPrototypeOf(this));
68
+ const self = this;
69
+ const clone = inherit(Object.getPrototypeOf(self), self);
69
70
  _(clone, {
70
- states: _(this).states,
71
+ states: _(self).states,
71
72
  prefix: randomId() + '.',
72
73
  counter: 0
73
74
  });
74
75
  return clone;
76
+ },
77
+ dispose: function () {
78
+ var states = _(this).states;
79
+ each(states, function (i, v) {
80
+ delete states[i];
81
+ });
75
82
  }
76
83
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brew-js-react",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "brew-js": ">=0.3.2",
21
21
  "waterpipe": "^2.5.0",
22
- "zeta-dom": ">=0.1.13",
22
+ "zeta-dom": ">=0.2.3",
23
23
  "zeta-dom-react": ">=0.2.1"
24
24
  },
25
25
  "peerDependencies": {
@@ -40,6 +40,7 @@
40
40
  "cross-env": "^7.0.2",
41
41
  "glob": "^7.2.0",
42
42
  "jest": "^27.0.6",
43
+ "jest-environment-jsdom": "^27.4.6",
43
44
  "ncp": "^2.0.0",
44
45
  "regenerator-runtime": "^0.13.9",
45
46
  "webpack": "^5.3.0",
package/view.js CHANGED
@@ -1,11 +1,13 @@
1
- import React from "react";
1
+ import React, { useRef } from "react";
2
2
  import { useAsync } from "zeta-dom-react";
3
3
  import dom from "./include/zeta-dom/dom.js";
4
- import { any, defineGetterProperty, definePrototype, each, extend, isFunction, keys, makeArray, noop, pick, randomId, setImmediate } from "./include/zeta-dom/util.js";
4
+ import { notifyAsync } from "./include/zeta-dom/domLock.js";
5
+ import { any, defineGetterProperty, definePrototype, each, either, extend, grep, isFunction, keys, makeArray, map, noop, pick, randomId, setImmediate } from "./include/zeta-dom/util.js";
5
6
  import { animateIn, animateOut } from "./include/brew-js/anim.js";
6
7
  import { app } from "./app.js";
7
8
  import { ViewStateContainer } from "./hooks.js";
8
9
 
10
+ const root = dom.root;
9
11
  const routeMap = new Map();
10
12
  const usedParams = {};
11
13
  const StateContext = React.createContext(Object.freeze({ active: true }));
@@ -25,6 +27,7 @@ function ViewContainer() {
25
27
  }
26
28
  self.componentWillUnmount = app.on('navigate', function () {
27
29
  if (self.mounted && self.getViewComponent()) {
30
+ self.isForceUpdate = true;
28
31
  self.forceUpdate();
29
32
  }
30
33
  });
@@ -35,20 +38,25 @@ definePrototype(ViewContainer, React.Component, {
35
38
  this.mounted = true;
36
39
  },
37
40
  componentDidCatch: function (error) {
38
- dom.emit('error', this.parentElement || dom.root, { error }, true);
41
+ dom.emit('error', this.parentElement || root, { error }, true);
39
42
  },
40
43
  render: function () {
41
44
  /** @type {any} */
42
45
  var self = this;
46
+ var resolve;
47
+ var promise = new Promise(function (_resolve) {
48
+ resolve = _resolve;
49
+ });
43
50
  var V = self.getViewComponent();
44
51
  if (V && V !== self.currentViewComponent) {
45
52
  self.currentViewComponent = V;
46
53
  if (self.currentView && self.currentElement) {
54
+ var prevPath = self.currentPath;
55
+ var prevElement = self.currentElement;
47
56
  self.prevView = self.currentView;
48
- self.prevElement = self.currentElement;
49
57
  self.currentElement = undefined;
50
- animateOut(self.prevElement, 'show').then(function () {
51
- self.prevElement = undefined;
58
+ app.emit('pageleave', prevElement, { pathname: prevPath }, true);
59
+ animateOut(prevElement, 'show').then(function () {
52
60
  self.prevView = undefined;
53
61
  self.forceUpdate();
54
62
  });
@@ -65,23 +73,74 @@ definePrototype(ViewContainer, React.Component, {
65
73
  self.currentElement = element;
66
74
  self.parentElement = element.parentElement;
67
75
  setImmediate(function () {
68
- return animateIn(element, 'show');
76
+ resolve();
77
+ animateIn(element, 'show');
78
+ app.emit('pageenter', element, { pathname: app.path }, true);
69
79
  });
70
80
  }
71
81
  })));
72
82
  defineGetterProperty(providerProps.value, 'active', function () {
73
83
  return self.currentView === view;
74
84
  });
85
+ self.currentPath = app.path;
75
86
  self.currentView = view;
87
+ } else {
88
+ if (self.isForceUpdate) {
89
+ self.isForceUpdate = false;
90
+ app.emit('pageenter', self.currentElement, { pathname: app.path }, true);
91
+ }
92
+ resolve();
76
93
  }
94
+ notifyAsync(self.parentElement || root, promise);
77
95
  return React.createElement(React.Fragment, null, self.prevView, self.currentView);
78
96
  },
79
97
  getViewComponent: function () {
80
98
  var props = this.props;
81
- return any(props.views, isViewMatched) || (history.state === stateId && void redirectTo(props.defaultView));
99
+ var matched = any(props.views, isViewMatched) || props.defaultView;
100
+ if (history.state === stateId) {
101
+ // ensure the current path actually corresponds to the matched view
102
+ // when some views are not included in the list of allowed views
103
+ var targetPath = linkTo(matched, getCurrentParams(matched, true));
104
+ if (targetPath !== app.path) {
105
+ app.navigate(targetPath, true);
106
+ return;
107
+ }
108
+ }
109
+ return matched;
82
110
  }
83
111
  });
84
112
 
113
+ function getCurrentParams(view, includeAll) {
114
+ var state = routeMap.get(view);
115
+ if (!state.maxParams) {
116
+ var matched = map(app.routes, function (v) {
117
+ var route = app.parseRoute(v);
118
+ var matched = route.length && !any(state.matchers, function (v, i) {
119
+ var pos = route.params[i];
120
+ return (v ? !(pos >= 0) : pos < route.minLength) || (!isFunction(v) && !route.match(i, v));
121
+ });
122
+ return matched ? route : null;
123
+ });
124
+ if (matched[1]) {
125
+ matched = grep(matched, function (v) {
126
+ return !any(v.params, function (v, i) {
127
+ return usedParams[i] && !state.matchers[i];
128
+ });
129
+ });
130
+ }
131
+ if (matched[0]) {
132
+ var last = matched.slice(-1)[0];
133
+ state.maxParams = keys(extend.apply(0, [{}].concat(matched.map(function (v) {
134
+ return v.params;
135
+ }))));
136
+ state.minParams = map(last.params, function (v, i) {
137
+ return state.params[i] || v >= last.minLength ? null : i;
138
+ });
139
+ }
140
+ }
141
+ return pick(app.route, includeAll ? state.maxParams : state.minParams);
142
+ }
143
+
85
144
  export function useViewContainerState() {
86
145
  return React.useContext(StateContext);
87
146
  }
@@ -96,14 +155,14 @@ export function isViewMatched(view) {
96
155
 
97
156
  export function registerView(factory, routeParams) {
98
157
  var Component = function (props) {
99
- var Component = useAsync(factory)[0];
158
+ var state = useAsync(factory);
159
+ var ref = useRef();
160
+ if (state[0] || state[1].error) {
161
+ (props.onComponentLoaded || noop)(ref.current);
162
+ }
100
163
  return React.createElement('div', extend({}, props.rootProps, {
101
- ref: function (element) {
102
- if (element && Component) {
103
- (props.onComponentLoaded || noop)(element);
104
- }
105
- },
106
- children: Component && React.createElement(Component.default)
164
+ ref: ref,
165
+ children: state[0] && React.createElement(state[0].default)
107
166
  }));
108
167
  };
109
168
  routeParams = extend({}, routeParams);
@@ -135,17 +194,11 @@ export function renderView() {
135
194
  }
136
195
 
137
196
  export function linkTo(view, params) {
138
- var viewParams = (routeMap.get(view) || {}).params;
139
- var newParams = {};
140
- for (var i in app.route) {
141
- if (viewParams && i in viewParams) {
142
- newParams[i] = viewParams[i];
143
- } else if (params && i in params) {
144
- newParams[i] = params[i];
145
- } else if (!usedParams[i]) {
146
- newParams[i] = app.route[i];
147
- }
197
+ var state = routeMap.get(view);
198
+ if (!state) {
199
+ return '/';
148
200
  }
201
+ var newParams = extend(getCurrentParams(view), params, state.params);
149
202
  return app.route.getPath(newParams);
150
203
  }
151
204