jsbox-cview 1.0.0

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/components/alert/input-alert.ts +73 -0
  4. package/components/alert/login-alert.ts +75 -0
  5. package/components/alert/plain-alert.ts +49 -0
  6. package/components/alert/uialert.ts +110 -0
  7. package/components/artificial-flowlayout.ts +321 -0
  8. package/components/base.ts +47 -0
  9. package/components/custom-navigation-bar.ts +570 -0
  10. package/components/dialogs/dialog-sheet.ts +87 -0
  11. package/components/dialogs/form-dialog.ts +23 -0
  12. package/components/dialogs/list-dialog.ts +87 -0
  13. package/components/dialogs/text-dialog.ts +34 -0
  14. package/components/dynamic-itemsize-matrix.ts +190 -0
  15. package/components/dynamic-preference-listview.ts +691 -0
  16. package/components/dynamic-rowheight-list.ts +62 -0
  17. package/components/enhanced-imageview.ts +128 -0
  18. package/components/image-pager.ts +177 -0
  19. package/components/page-control.ts +91 -0
  20. package/components/pageviewer-titlebar.ts +170 -0
  21. package/components/pageviewer.ts +124 -0
  22. package/components/rotating-view.ts +126 -0
  23. package/components/searchbar.ts +373 -0
  24. package/components/sheet.ts +113 -0
  25. package/components/single-views.ts +828 -0
  26. package/components/spinners/loading-double-rings.ts +121 -0
  27. package/components/spinners/loading-dual-ring.ts +90 -0
  28. package/components/spinners/loading-wedges.ts +112 -0
  29. package/components/spinners/spinner-androidstyle.ts +264 -0
  30. package/components/static-preference-listview.ts +991 -0
  31. package/components/symbol-button.ts +105 -0
  32. package/components/tabbar.ts +451 -0
  33. package/controller/base-controller.ts +216 -0
  34. package/controller/controller-router.ts +74 -0
  35. package/controller/pageviewer-controller.ts +86 -0
  36. package/controller/presented-page-controller.ts +57 -0
  37. package/controller/splitview-controller.ts +323 -0
  38. package/controller/tabbar-controller.ts +99 -0
  39. package/package.json +23 -0
  40. package/test.ts +0 -0
  41. package/tsconfig.json +121 -0
  42. package/utils/colors.ts +13 -0
  43. package/utils/cvid.ts +34 -0
  44. package/utils/l10n.ts +42 -0
  45. package/utils/path.ts +100 -0
  46. package/utils/rect.ts +67 -0
  47. package/utils/uitools.ts +117 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * # CView PageViewer
3
+ *
4
+ * props:
5
+ * - 读写 page: number
6
+ * - 只写 cviews: cview[] cview的布局会被自动指定为占用一整页
7
+ *
8
+ * events:
9
+ * - changed: (cview, page) => void 页面改变时回调
10
+ * - floatPageChanged: (cview, floatPage) => void 滚动时回调(用于绑定其他联合滚动的控件)
11
+ *
12
+ * methods:
13
+ * - scrollToPage(page: number) 滚动到某一页(带有动画效果)
14
+ */
15
+
16
+ import { Base } from './base';
17
+ import { ContentView, Scroll } from "./single-views";
18
+
19
+ export class PageViewer extends Base<UIView, UiTypes.ViewOptions> {
20
+ private _props: {
21
+ page: number;
22
+ cviews: Base<any, any>[];
23
+ };
24
+ private _events: {
25
+ changed?: (cview: PageViewer, page: number) => void;
26
+ floatPageChanged?: (cview: PageViewer, floatPage: number) => void;
27
+ };
28
+ private _pageWidth: number;
29
+ private _floatPage: number;
30
+ scroll: Scroll;
31
+ _defineView: () => UiTypes.ViewOptions;
32
+
33
+ constructor({ props, layout, events = {} }: {
34
+ props: {
35
+ page?: number;
36
+ cviews: Base<any, any>[];
37
+ };
38
+ layout: (make: MASConstraintMaker, view: UIView) => void;
39
+ events: {
40
+ changed?: (cview: PageViewer, page: number) => void;
41
+ floatPageChanged?: (cview: PageViewer, floatPage: number) => void;
42
+ };
43
+ }) {
44
+ super();
45
+ this._props = {
46
+ page: 0,
47
+ ...props
48
+ };
49
+ this._events = events;
50
+ this._pageWidth = 0;
51
+ this._floatPage = this._props.page;
52
+ const contentViews = this._props.cviews.map(n => {
53
+ n._layout = $layout.fill;
54
+ return new ContentView({
55
+ views: [n.definition],
56
+ layout: (make, view) => {
57
+ make.height.width.equalTo(view.super);
58
+ make.left.equalTo(view.prev ? view.prev.right : view.super);
59
+ make.top.equalTo(view.super);
60
+ }
61
+ });
62
+ });
63
+ this.scroll = new Scroll({
64
+ props: {
65
+ alwaysBounceVertical: false,
66
+ alwaysBounceHorizontal: true,
67
+ showsHorizontalIndicator: false,
68
+ pagingEnabled: true
69
+ },
70
+ events: {
71
+ layoutSubviews: sender => {
72
+ this._pageWidth = sender.frame.width;
73
+ if (this._pageWidth)
74
+ sender.contentSize = $size(this._pageWidth * this._props.cviews.length, 0);
75
+ },
76
+ willEndDragging: (sender, velocity, target) => {
77
+ const oldPage = this.page;
78
+ this._props.page = Math.round(target.x / this._pageWidth);
79
+ if (oldPage !== this.page && this._events.changed)
80
+ this._events.changed(this, this.page);
81
+ },
82
+ didScroll: sender => {
83
+ const rawPage = sender.contentOffset.x / this._pageWidth;
84
+ this._floatPage = Math.min(Math.max(0, rawPage), this._props.cviews.length - 1);
85
+ if (this._events.floatPageChanged)
86
+ this._events.floatPageChanged(this, this._floatPage);
87
+ }
88
+ },
89
+ layout: $layout.fill,
90
+ views: [...contentViews.map(n => n.definition)]
91
+ });
92
+ this._defineView = () => {
93
+ return {
94
+ type: "view",
95
+ props: { id: this.id },
96
+ layout: this._layout,
97
+ views: [this.scroll.definition],
98
+ events: {
99
+ layoutSubviews: sender => {
100
+ sender.relayout();
101
+ this.page = this.page
102
+ $delay(0.2, () => (this.page = this.page))
103
+
104
+ }
105
+ }
106
+ };
107
+ }
108
+ }
109
+
110
+ get page() {
111
+ return this._props.page;
112
+ }
113
+
114
+ set page(page) {
115
+ this._props.page = page;
116
+ if (this.scroll.view.contentOffset.x !== page * this._pageWidth)
117
+ this.scroll.view.contentOffset = $point(page * this._pageWidth, 0);
118
+ }
119
+
120
+ scrollToPage(page: number) {
121
+ this.scroll.view.scrollToOffset($point(page * this._pageWidth, 0));
122
+ this._props.page = page;
123
+ }
124
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 创建一个可以旋转的视图。理论上来说,这个视图的布局必须是方形的。
3
+ *
4
+ * props:
5
+ * - image 图片
6
+ * - tintColor
7
+ * - contentMode = 1
8
+ * - cview 使用自定义的cview,上面两项将失效
9
+ * - rps = 0.5 每秒转多少圈
10
+ * - clockwise = true 是否顺时针旋转
11
+ *
12
+ * events:
13
+ * - ready: cview => void 可以在ready事件中启动旋转
14
+ *
15
+ * methods:
16
+ * - startRotating() 开始旋转
17
+ * - stopRotating() 结束旋转,请注意旋转是不能立即结束的,必须等到动画归位
18
+ */
19
+
20
+ import { Base } from "./base";
21
+ import { Image } from "./single-views";
22
+
23
+ interface RotatingViewProps {
24
+ image?: UIImage;
25
+ tintColor?: UIColor;
26
+ contentMode?: number;
27
+ cview?: Base<any, any>;
28
+ rps?: number;
29
+ clockwise?: boolean;
30
+ }
31
+
32
+ export class RotatingView extends Base<UIView, UiTypes.ViewOptions>{
33
+ private _props: RotatingViewProps;
34
+ private _rotatingFlag: boolean;
35
+ private _innerView: Base<any, any>
36
+ _defineView: () => UiTypes.ViewOptions;
37
+ constructor({ props, layout, events = {} }: {
38
+ props: RotatingViewProps;
39
+ layout: (make: MASConstraintMaker, view: UIView) => void;
40
+ events?: {
41
+ ready?: (cview: RotatingView) => void;
42
+ }
43
+ }) {
44
+ super();
45
+ this._props = {
46
+ contentMode: 1,
47
+ rps: 0.5,
48
+ clockwise: true,
49
+ ...props
50
+ };
51
+ this._rotatingFlag = false;
52
+ if (this._props.cview) {
53
+ this._innerView = this._props.cview;
54
+ } else {
55
+ if (!this._props.image) throw new Error("image is required");
56
+ this._innerView = new Image({
57
+ props: {
58
+ image: this._props.tintColor
59
+ ? this._props.image.alwaysTemplate
60
+ : this._props.image,
61
+ tintColor: this._props.tintColor,
62
+ contentMode: this._props.contentMode
63
+ },
64
+ layout: $layout.fill
65
+ });
66
+ }
67
+ this._defineView = () => {
68
+ return {
69
+ type: "view",
70
+ props: {
71
+ ...this._props,
72
+ id: this.id
73
+ },
74
+ layout,
75
+ events: {
76
+ ready: sender => {
77
+ if (events.ready) events.ready(this);
78
+ }
79
+ },
80
+ views: [this._innerView.definition]
81
+ };
82
+ }
83
+ }
84
+
85
+ startRotating() {
86
+ this._rotatingFlag = true;
87
+ this._rotateView(this._innerView.view);
88
+ }
89
+
90
+ stopRotating() {
91
+ this._rotatingFlag = false;
92
+ }
93
+
94
+ _rotateView(view: AllUIView) {
95
+ const clockwiseMultiplier = this._props.clockwise ? 1 : -1
96
+ const duration = 1 / 3 / (this._props.rps || 0.5);
97
+ $ui.animate({
98
+ duration,
99
+ options: 3 << 16,
100
+ animation: () => {
101
+ view.rotate(Math.PI * 2 / 3 * clockwiseMultiplier);
102
+ },
103
+ completion: () => {
104
+ $ui.animate({
105
+ duration,
106
+ options: 3 << 16,
107
+ animation: () => {
108
+ view.rotate(Math.PI * 4 / 3 * clockwiseMultiplier);
109
+ },
110
+ completion: () => {
111
+ $ui.animate({
112
+ duration,
113
+ options: 3 << 16,
114
+ animation: () => {
115
+ view.rotate(Math.PI * 2 * clockwiseMultiplier);
116
+ },
117
+ completion: () => {
118
+ if (this._rotatingFlag) this._rotateView(view);
119
+ }
120
+ })
121
+ }
122
+ });
123
+ }
124
+ });
125
+ }
126
+ }
@@ -0,0 +1,373 @@
1
+ /**
2
+ * # CView SearchBar
3
+ *
4
+ * props
5
+ *
6
+ * - 读写 text: string
7
+ * - style: number 搜索框的样式
8
+ * - 0: 取消按钮在输入框内,聚焦时显示取消按钮
9
+ * - 1: 取消按钮在输入框右侧,聚焦时会有左右移动的动画
10
+ * - 2: 取消按钮布局同 1,但是 placeholder 平时显示在中间,聚焦时才会移动到左边。
11
+ * 如果使用此样式,建议每次 blur 的时候都清除 text
12
+ * - accessoryCview: cview 请通过下面的事件来和 SearchBar 互相操作
13
+ * - placeholder: string
14
+ * - cancelText: string
15
+ * - tintColor: \$color("systemLink")
16
+ * - bgcolor: colors.searchBarBgcolor
17
+ *
18
+ * events
19
+ *
20
+ * - didBeginEditing: cview => void
21
+ * - didEndEditing: cview => void
22
+ * - changed: cview => void
23
+ * - returned: cview => void
24
+ */
25
+
26
+ import { Base } from "./base";
27
+ import { Input, Label, ContentView } from "./single-views";
28
+ import { searchBarBgcolor } from "../utils/colors";
29
+ import { l10n } from "../utils/l10n";
30
+ import { getTextWidth } from "../utils/uitools";
31
+
32
+ interface SearchBarProps {
33
+ placeholder: string;
34
+ cancelText: string;
35
+ tintColor: UIColor;
36
+ bgcolor: UIColor;
37
+ style: 0 | 1 | 2;
38
+ accessoryCview?: Base<UIView, UiTypes.ViewOptions>;
39
+ }
40
+
41
+ export class SearchBar extends Base<UIView, UiTypes.ViewOptions> {
42
+ _props: SearchBarProps;
43
+ _defineView: () => UiTypes.ViewOptions;
44
+ cviews: {
45
+ input: Input;
46
+ iconInput: ContentView;
47
+ cancelButton: Label;
48
+ bgview: ContentView;
49
+ };
50
+ _layouts: {
51
+ iconInput: {
52
+ normal: (make: MASConstraintMaker, view: AllUIView) => void;
53
+ focused?: (make: MASConstraintMaker, view: AllUIView) => void;
54
+ };
55
+ cancelButton: {
56
+ normal: (make: MASConstraintMaker, view: AllUIView) => void;
57
+ };
58
+ bgview: {
59
+ normal: (make: MASConstraintMaker, view: AllUIView) => void;
60
+ focused?: (make: MASConstraintMaker, view: AllUIView) => void;
61
+ };
62
+ };
63
+ _focused: boolean;
64
+ constructor({ props, layout, events = {} }: {
65
+ props: Partial<SearchBarProps>;
66
+ layout: (make: MASConstraintMaker, view: UIView) => void;
67
+ events?: {
68
+ didBeginEditing?: (cview: SearchBar) => void;
69
+ didEndEditing?: (cview: SearchBar) => void;
70
+ changed?: (cview: SearchBar) => void;
71
+ returned?: (cview: SearchBar) => void;
72
+ };
73
+ }) {
74
+ super();
75
+ this._props = {
76
+ placeholder: l10n("SEARCH"),
77
+ cancelText: l10n("CANCEL"),
78
+ tintColor: $color("systemLink"),
79
+ bgcolor: searchBarBgcolor,
80
+ style: 0,
81
+ ...props
82
+ };
83
+ const cancelButtonWidth = getTextWidth(this._props.cancelText, {
84
+ inset: 20
85
+ });
86
+ const placeholderWidth = getTextWidth(this._props.cancelText, {
87
+ inset: 20
88
+ });
89
+ this._focused = false;
90
+ this._layouts = this._defineLayouts(cancelButtonWidth, placeholderWidth);
91
+ this.cviews = {} as {
92
+ input: Input;
93
+ iconInput: ContentView;
94
+ cancelButton: Label;
95
+ bgview: ContentView;
96
+ };
97
+ this.cviews.input = new Input({
98
+ props: {
99
+ type: $kbType.search,
100
+ placeholder: this._props.placeholder,
101
+ bgcolor: $color("clear"),
102
+ radius: 0,
103
+ accessoryView:
104
+ this._props.accessoryCview && this._props.accessoryCview.definition
105
+ },
106
+ layout: (make, view) => {
107
+ make.left.equalTo(view.prev.right);
108
+ make.top.bottom.right.inset(0);
109
+ },
110
+ events: {
111
+ changed: sender => {
112
+ if (events.changed) events.changed(this);
113
+ },
114
+ didBeginEditing: sender => {
115
+ this._onFocused();
116
+ if (events.didBeginEditing) events.didBeginEditing(this);
117
+ },
118
+ didEndEditing: sender => {
119
+ if (events.didEndEditing) events.didEndEditing(this);
120
+ },
121
+ returned: sender => {
122
+ this.blur();
123
+ if (events.returned) events.returned(this);
124
+ }
125
+ }
126
+ })
127
+ this.cviews.iconInput = new ContentView({
128
+ props: {
129
+ bgcolor: undefined
130
+ },
131
+ layout: this._layouts.iconInput.normal,
132
+ views: [
133
+ {
134
+ type: "view",
135
+ props: {},
136
+ views: [
137
+ {
138
+ type: "image",
139
+ props: {
140
+ //tintColor: searchBarSymbolColor,
141
+ tintColor: $color("systemPlaceholderText"),
142
+ symbol: "magnifyingglass"
143
+ },
144
+ layout: (make, view) => {
145
+ make.size.equalTo($size(20, 20));
146
+ make.center.equalTo(view.super);
147
+ }
148
+ }
149
+ ],
150
+ layout: (make, view) => {
151
+ make.top.bottom.inset(0);
152
+ make.width.equalTo(20);
153
+ make.left.inset(6);
154
+ }
155
+ },
156
+ this.cviews.input.definition
157
+ ]
158
+ })
159
+ this.cviews.cancelButton = new Label({
160
+ props: {
161
+ text: this._props.cancelText,
162
+ textColor: this._props.tintColor,
163
+ font: $font(17),
164
+ align: $align.center,
165
+ userInteractionEnabled: true,
166
+ alpha: 0
167
+ },
168
+ layout: this._layouts.cancelButton.normal,
169
+ events: {
170
+ tapped: sender => this.blur()
171
+ }
172
+ })
173
+ this.cviews.bgview = new ContentView({
174
+ props: {
175
+ bgcolor: this._props.bgcolor,
176
+ radius: 8,
177
+ userInteractionEnabled: true
178
+ },
179
+ layout: this._layouts.bgview.normal,
180
+ events: {
181
+ tapped: sender => {
182
+ if (!this._focused) this.focus();
183
+ }
184
+ }
185
+ })
186
+ this._defineView = () => {
187
+ return {
188
+ type: "view",
189
+ props: {
190
+ id: this.id,
191
+ clipsToBounds: true
192
+ },
193
+ layout,
194
+ views: [
195
+ this.cviews.bgview.definition,
196
+ this.cviews.iconInput.definition,
197
+ this.cviews.cancelButton.definition
198
+ ]
199
+ };
200
+ }
201
+ }
202
+
203
+
204
+ _defineLayouts(cancelButtonWidth: number, placeholderWidth: number) {
205
+ switch (this._props.style) {
206
+ case 0: {
207
+ const IconInputLayout = $layout.fill;
208
+ const cancelButtonLayout = (make: MASConstraintMaker, view: AllUIView) => {
209
+ make.right.top.bottom.inset(0);
210
+ make.width.equalTo(cancelButtonWidth);
211
+ };
212
+ const bgviewLayout = $layout.fill;
213
+ return {
214
+ iconInput: { normal: IconInputLayout },
215
+ cancelButton: { normal: cancelButtonLayout },
216
+ bgview: { normal: bgviewLayout }
217
+ };
218
+ }
219
+ case 1: {
220
+ const IconInputLayout = (make: MASConstraintMaker, view: AllUIView) => {
221
+ make.left.top.bottom.inset(0);
222
+ make.right.inset(cancelButtonWidth);
223
+ };
224
+ const cancelButtonLayout = (make: MASConstraintMaker, view: AllUIView) => {
225
+ make.top.bottom.inset(0);
226
+ make.left.equalTo(view.prev.prev.right);
227
+ make.width.equalTo(cancelButtonWidth);
228
+ };
229
+ const bgviewLayoutNormal = $layout.fill;
230
+ const bgviewLayoutFocused = (make: MASConstraintMaker, view: AllUIView) => {
231
+ make.left.top.bottom.inset(0);
232
+ make.right.inset(cancelButtonWidth);
233
+ };
234
+ return {
235
+ iconInput: { normal: IconInputLayout },
236
+ cancelButton: { normal: cancelButtonLayout },
237
+ bgview: { normal: bgviewLayoutNormal, focused: bgviewLayoutFocused }
238
+ };
239
+ }
240
+ case 2: {
241
+ const IconInputLayoutNormal = (make: MASConstraintMaker, view: AllUIView) => {
242
+ make.center.equalTo(view.super);
243
+ make.top.bottom.inset(0);
244
+ make.width.equalTo(placeholderWidth + 50);
245
+ };
246
+ const IconInputLayoutFocused = (make: MASConstraintMaker, view: AllUIView) => {
247
+ make.left.top.bottom.inset(0);
248
+ make.right.inset(cancelButtonWidth);
249
+ };
250
+ const cancelButtonLayout = (make: MASConstraintMaker, view: AllUIView) => {
251
+ make.right.top.bottom.inset(0);
252
+ make.left.equalTo(view.prev.prev.right);
253
+ make.width.equalTo(cancelButtonWidth);
254
+ };
255
+ const bgviewLayoutNormal = $layout.fill;
256
+ const bgviewLayoutFocused = (make: MASConstraintMaker, view: AllUIView) => {
257
+ make.left.top.bottom.inset(0);
258
+ make.right.inset(cancelButtonWidth);
259
+ };
260
+ return {
261
+ iconInput: {
262
+ normal: IconInputLayoutNormal,
263
+ focused: IconInputLayoutFocused
264
+ },
265
+ cancelButton: { normal: cancelButtonLayout },
266
+ bgview: { normal: bgviewLayoutNormal, focused: bgviewLayoutFocused }
267
+ };
268
+ }
269
+ default:
270
+ throw new Error("style not supported");
271
+ }
272
+ }
273
+
274
+ _onFocused() {
275
+ this._focused = true;
276
+ switch (this._props.style) {
277
+ case 0: {
278
+ $ui.animate({
279
+ duration: 0.2,
280
+ animation: () => {
281
+ this.cviews.cancelButton.view.alpha = 1;
282
+ }
283
+ });
284
+ break;
285
+ }
286
+ case 1: {
287
+ if (this._layouts.bgview.focused) this.cviews.bgview.view.remakeLayout(this._layouts.bgview.focused);
288
+ $ui.animate({
289
+ duration: 0.2,
290
+ animation: () => {
291
+ this.cviews.bgview.view.relayout();
292
+ this.cviews.cancelButton.view.alpha = 1;
293
+ }
294
+ });
295
+ break;
296
+ }
297
+ case 2: {
298
+ if (this._layouts.iconInput.focused) this.cviews.iconInput.view.remakeLayout(this._layouts.iconInput.focused);
299
+ if (this._layouts.bgview.focused) this.cviews.bgview.view.remakeLayout(this._layouts.bgview.focused);
300
+ $ui.animate({
301
+ duration: 0.2,
302
+ animation: () => {
303
+ this.cviews.iconInput.view.relayout();
304
+ this.cviews.bgview.view.relayout();
305
+ this.cviews.cancelButton.view.alpha = 1;
306
+ }
307
+ });
308
+ break;
309
+ }
310
+ default:
311
+ break;
312
+ }
313
+ }
314
+
315
+ _onBlurred() {
316
+ this._focused = false;
317
+ switch (this._props.style) {
318
+ case 0: {
319
+ $ui.animate({
320
+ duration: 0.2,
321
+ animation: () => {
322
+ this.cviews.cancelButton.view.alpha = 0;
323
+ }
324
+ });
325
+ break;
326
+ }
327
+ case 1: {
328
+ this.cviews.bgview.view.remakeLayout(this._layouts.bgview.normal);
329
+ $ui.animate({
330
+ duration: 0.2,
331
+ animation: () => {
332
+ this.cviews.bgview.view.relayout();
333
+ this.cviews.cancelButton.view.alpha = 0;
334
+ }
335
+ });
336
+ break;
337
+ }
338
+ case 2: {
339
+ this.cviews.iconInput.view.remakeLayout(this._layouts.iconInput.normal);
340
+ this.cviews.bgview.view.remakeLayout(this._layouts.bgview.normal);
341
+ $ui.animate({
342
+ duration: 0.2,
343
+ animation: () => {
344
+ this.cviews.iconInput.view.relayout();
345
+ this.cviews.bgview.view.relayout();
346
+ this.cviews.cancelButton.view.alpha = 0;
347
+ }
348
+ });
349
+ break;
350
+ }
351
+ default:
352
+ break;
353
+ }
354
+ }
355
+
356
+ focus() {
357
+ this.cviews.input.view.focus();
358
+ this._onFocused();
359
+ }
360
+
361
+ blur() {
362
+ this._onBlurred();
363
+ this.cviews.input.view.blur();
364
+ }
365
+
366
+ set text(text) {
367
+ this.cviews.input.view.text = text;
368
+ }
369
+
370
+ get text() {
371
+ return this.cviews.input.view.text;
372
+ }
373
+ }