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.
- package/dist/brew-js-react.js +113 -37
- package/dist/brew-js-react.js.map +1 -1
- package/dist/brew-js-react.min.js +2 -2
- package/dist/brew-js-react.min.js.map +1 -1
- package/hooks.js +7 -6
- package/mixin.d.ts +5 -1
- package/mixin.js +4 -1
- package/mixins/ClassNameMixin.js +56 -56
- package/mixins/ScrollIntoViewMixin.d.ts +5 -0
- package/mixins/ScrollIntoViewMixin.js +21 -0
- package/mixins/ScrollableMixin.d.ts +3 -2
- package/mixins/ScrollableMixin.js +2 -0
- package/package.json +1 -1
- package/view.d.ts +27 -1
- package/view.js +53 -11
package/mixins/ClassNameMixin.js
CHANGED
|
@@ -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,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
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
|
|
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 {
|
|
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
|
|
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.
|
|
80
|
-
|
|
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
|
|
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
|
+
}
|