lecodes-cli 0.1.5 → 0.1.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.
Files changed (81) hide show
  1. package/dist/index.js +300 -149
  2. package/package.json +5 -3
  3. package/runtime/sdk/animate/animate.ts +238 -0
  4. package/runtime/sdk/animate/bezier.ts +138 -0
  5. package/runtime/sdk/animate/easings.ts +126 -0
  6. package/runtime/sdk/core/color.ts +66 -0
  7. package/runtime/sdk/core/events.ts +38 -0
  8. package/runtime/sdk/core/registry.ts +23 -0
  9. package/runtime/sdk/g2/Camera2D.ts +73 -0
  10. package/runtime/sdk/g2/Node2D.ts +104 -0
  11. package/runtime/sdk/g2/Scene2D.ts +73 -0
  12. package/runtime/sdk/g2/Sprite.ts +164 -0
  13. package/runtime/sdk/g2/Texture2D.ts +37 -0
  14. package/runtime/sdk/g2/Tilemap.ts +41 -0
  15. package/runtime/sdk/g2/loop.ts +22 -0
  16. package/runtime/sdk/gl/Body.ts +101 -0
  17. package/runtime/sdk/gl/Camera.ts +40 -0
  18. package/runtime/sdk/gl/Geometry.ts +218 -0
  19. package/runtime/sdk/gl/Light.ts +37 -0
  20. package/runtime/sdk/gl/Material.ts +124 -0
  21. package/runtime/sdk/gl/Mesh.ts +123 -0
  22. package/runtime/sdk/gl/Node.ts +307 -0
  23. package/runtime/sdk/gl/Noise.ts +30 -0
  24. package/runtime/sdk/gl/Particles.ts +185 -0
  25. package/runtime/sdk/gl/Plane.ts +53 -0
  26. package/runtime/sdk/gl/Ray.ts +16 -0
  27. package/runtime/sdk/gl/Scene.ts +179 -0
  28. package/runtime/sdk/gl/Texture.ts +35 -0
  29. package/runtime/sdk/gl/controls.ts +95 -0
  30. package/runtime/sdk/gl/state.ts +6 -0
  31. package/runtime/sdk/gl/touch.ts +56 -0
  32. package/runtime/sdk/inject.ts +72 -0
  33. package/runtime/sdk/math/Mathf.ts +118 -0
  34. package/runtime/sdk/math/mat4.ts +278 -0
  35. package/runtime/sdk/math/quat.ts +232 -0
  36. package/runtime/sdk/math/vec.ts +255 -0
  37. package/runtime/sdk/plugins/qr.ts +36 -0
  38. package/runtime/sdk/runtime/device.ts +33 -0
  39. package/runtime/sdk/runtime/fetch.ts +71 -0
  40. package/runtime/sdk/runtime/files.ts +23 -0
  41. package/runtime/sdk/runtime/input.ts +12 -0
  42. package/runtime/sdk/runtime/media.ts +111 -0
  43. package/runtime/sdk/runtime/misc.ts +12 -0
  44. package/runtime/sdk/runtime/net.ts +36 -0
  45. package/runtime/sdk/runtime/storage.ts +13 -0
  46. package/runtime/sdk/runtime/touch.ts +44 -0
  47. package/runtime/sdk/ui/UI.ts +19 -0
  48. package/runtime/sdk/ui/UIButton.ts +84 -0
  49. package/runtime/sdk/ui/UIContainer.ts +100 -0
  50. package/runtime/sdk/ui/UIImage.ts +68 -0
  51. package/runtime/sdk/ui/UIInput.ts +78 -0
  52. package/runtime/sdk/ui/UINode.ts +342 -0
  53. package/runtime/sdk/ui/UIScreen.ts +122 -0
  54. package/runtime/sdk/ui/UIScrollable.ts +70 -0
  55. package/runtime/sdk/ui/UISpacer.ts +14 -0
  56. package/runtime/sdk/ui/UIText.ts +40 -0
  57. package/runtime/sdk/ui/UIVideo.ts +38 -0
  58. package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
  59. package/runtime/sdk/ui/UIWidget.ts +97 -0
  60. package/runtime/sdk/ui/fonts.ts +10 -0
  61. package/runtime/sdk/ui/router.ts +73 -0
  62. package/runtime/gl-matrix/common.js +0 -54
  63. package/runtime/gl-matrix/header.js +0 -12
  64. package/runtime/gl-matrix/index.js +0 -11
  65. package/runtime/gl-matrix/mat2.js +0 -425
  66. package/runtime/gl-matrix/mat2d.js +0 -476
  67. package/runtime/gl-matrix/mat3.js +0 -765
  68. package/runtime/gl-matrix/mat4.js +0 -1947
  69. package/runtime/gl-matrix/quat.js +0 -737
  70. package/runtime/gl-matrix/quat2.js +0 -822
  71. package/runtime/gl-matrix/vec2.js +0 -620
  72. package/runtime/gl-matrix/vec3.js +0 -791
  73. package/runtime/gl-matrix/vec4.js +0 -652
  74. package/runtime/worker/animate.js +0 -304
  75. package/runtime/worker/chunk-4ZN32GYS.js +0 -34
  76. package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
  77. package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
  78. package/runtime/worker/creator.js +0 -930
  79. package/runtime/worker/globals.js +0 -208
  80. package/runtime/worker/ui.js +0 -763
  81. package/runtime/worker/utils.js +0 -16
@@ -0,0 +1,122 @@
1
+ import { TouchStartEvent } from "../runtime/touch"
2
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type ScrollStyle, type Style, type UINodeChild, } from "./UINode"
3
+
4
+ export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & ScrollStyle & BaseStyle & ScrollStyle & { refreshControlColor?: Color }
5
+
6
+ export interface UIScreen <Scrollable extends boolean = boolean> {
7
+ readonly type: "screen",
8
+ scrollable: Scrollable,
9
+ style: Style<this, UIScreenStyle>,
10
+ animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
11
+ animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
12
+
13
+ open(): void,
14
+ close(): void
15
+
16
+ append(...nodes: UINodeChild[]): this
17
+ insert(index: number, ...nodes: UINodeChild[]): this
18
+ remove(...nodes: UINodeChild[]): this
19
+ setContent(nodes: UINodeChild[]): this
20
+
21
+ readonly children: UINodeChild[],
22
+
23
+ makeScrollable(): UIScreen<true>,
24
+ onScroll: Scrollable extends true? (callback: (scrollPosition: number) => void) => void: never,
25
+ onOverscroll: Scrollable extends true? (callback: (scrollPosition: number) => void) => void: never,
26
+ onBackPressed(callback: () => void): this
27
+ onRefresh: Scrollable extends true? ((callback: () => void | Promise<void>) => this): never,
28
+
29
+ onOpen(callback: () => void): this
30
+ onClose(callback: () => void): this
31
+
32
+ onLayout(onLayout: OnLayoutCallback): this
33
+
34
+ onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
35
+ }
36
+
37
+ export class ScreenElement extends ContainerElement<"screen"> {
38
+ scrollable = false as const
39
+ open(): void {
40
+ _creatorUI.openScreen(this)
41
+ }
42
+ close(): void {
43
+ _creatorUI.closeScreen()
44
+ }
45
+ protected ol: (() => void)[] = []
46
+ onOpen(callback: () => void): this {
47
+ this.ol.push(callback)
48
+ return this
49
+ }
50
+ protected cl: (() => void)[] = []
51
+ onClose(callback: () => void): this {
52
+ this.cl.push(callback)
53
+ return this
54
+ }
55
+ readonly touchStartListeners: any[] = []
56
+ _interactive = false
57
+ onTouchStart (callback: any): this {
58
+ this._interactive = true
59
+ this.touchStartListeners.push(callback)
60
+ return this
61
+ }
62
+ _emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
63
+ const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
64
+ touchEvent.target = this
65
+ for (let callback of this.touchStartListeners) {
66
+ callback(touchEvent)
67
+ if ((touchEvent as any)._trackHandler) {
68
+ return (touchEvent as any)._trackHandler
69
+ }
70
+ }
71
+ return null
72
+ }
73
+ makeScrollable(): UIScreen<true> {
74
+ this.scrollable = true as any
75
+
76
+ Object.assign(this, {
77
+ sl: [] as any[],
78
+ osl: [] as any[],
79
+ onScroll(callback: any) {
80
+ this.sl.push(callback)
81
+ return this
82
+ },
83
+ onOverscroll(callback: any) {
84
+ this.osl.push(callback)
85
+ return this
86
+ },
87
+ onRefresh(callback: any) {
88
+ (this as any)._refreshCallback = callback
89
+ return this
90
+ }
91
+ })
92
+
93
+ return this as any as UIScreen<true>
94
+ }
95
+ _backButtonCallback?: () => void
96
+ onBackPressed(callback: any): this {
97
+ this._backButtonCallback = callback
98
+ return this
99
+ }
100
+ declare _refreshCallback: any
101
+ declare onRefresh: never
102
+ declare onScroll: never
103
+ declare onOverscroll: never
104
+ }
105
+
106
+ export function UIScreen(): UIScreen<false>;
107
+ export function UIScreen(children: UINodeChild[]): UIScreen<false>;
108
+ export function UIScreen(style: UIScreenStyle): UIScreen<false>;
109
+ export function UIScreen(style: UIScreenStyle, children: UINodeChild[]): UIScreen<false>;
110
+ export function UIScreen(...args: [] | [UINodeChild[] | UIScreenStyle] | [UIScreenStyle, children: UINodeChild[]]): UIScreen<false> {
111
+ if (args.length === 0) {
112
+ return new ScreenElement("screen", {}, [])
113
+ } else if (args.length === 1) {
114
+ if (Array.isArray(args[0])) {
115
+ return new ScreenElement("screen", {}, args[0])
116
+ } else {
117
+ return new ScreenElement("screen", args[0], [])
118
+ }
119
+ } else {
120
+ return new ScreenElement("screen", args[0], args[1])
121
+ }
122
+ }
@@ -0,0 +1,70 @@
1
+ import type { UIContainerStyle } from "./UIContainer"
2
+ import { type Style, type AnimateStyle, type UINodeChild, type OnLayoutCallback, ContainerElement } from "./UINode"
3
+
4
+ export type ScrollStyle = { showScrollbar?: boolean, overscrollMode?: "none" | "absorb" | "default" }
5
+
6
+ export type UIScrollableStyle = UIContainerStyle & ScrollStyle & { scrollDirection?: "horizontal" | "vertical" }
7
+
8
+ export interface UIScrollable {
9
+ readonly type: "scrollable",
10
+ style: Style<this, UIScrollableStyle>,
11
+ animateTo: AnimateStyle<this, UIScrollableStyle>,
12
+ animateFrom: AnimateStyle<this, UIScrollableStyle>,
13
+
14
+ append(...nodes: UINodeChild[]): UIScrollable
15
+ insert(index: number, ...nodes: UINodeChild[]): UIScrollable
16
+ remove(...nodes: UINodeChild[]): UIScrollable
17
+ setContent(nodes: UINodeChild[]): this
18
+
19
+ onLayout(onLayout: OnLayoutCallback): this
20
+
21
+ onScroll(callback: (scrollPosition: number) => void): UIScrollable,
22
+ onScrollRelease(callback: () => void): UIScrollable,
23
+ onOverscroll(callback: (delta: number) => void): UIScrollable
24
+
25
+ readonly children: UINodeChild[]
26
+ }
27
+
28
+ class ScrollableElement extends ContainerElement<"scrollable"> {
29
+
30
+ readonly osl: any[] = []
31
+ readonly sl: any[] = []
32
+ readonly srl: any[] = []
33
+
34
+ get scrollListeners() {
35
+ return this.sl
36
+ }
37
+
38
+ onScroll (callback: any): this {
39
+ this.sl.push(callback)
40
+ return this
41
+ }
42
+
43
+ onScrollRelease (callback: any): this {
44
+ this.srl.push(callback)
45
+ return this
46
+ }
47
+
48
+ onOverscroll (callback: any): this {
49
+ this.osl.push(callback)
50
+ return this
51
+ }
52
+ }
53
+
54
+ export function UIScrollable(): UIScrollable
55
+ export function UIScrollable(children: UINodeChild[]): UIScrollable
56
+ export function UIScrollable(style: UIScrollableStyle): UIScrollable
57
+ export function UIScrollable(style: UIScrollableStyle, children: UINodeChild[]): UIScrollable
58
+ export function UIScrollable(...args: [] | [UINodeChild[] | UIScrollableStyle] | [UIScrollableStyle, children: UINodeChild[]]): UIScrollable {
59
+ if (args.length === 0) {
60
+ return new ScrollableElement("scrollable", {}, [])
61
+ } else if (args.length === 1) {
62
+ if (Array.isArray(args[0])) {
63
+ return new ScrollableElement("scrollable", {}, args[0])
64
+ } else {
65
+ return new ScrollableElement("scrollable", args[0], [])
66
+ }
67
+ } else {
68
+ return new ScrollableElement("scrollable", args[0], args[1])
69
+ }
70
+ }
@@ -0,0 +1,14 @@
1
+ import { Element, type ElementStyle, type Style, type StyleFn } from "./UINode"
2
+
3
+ export type UISpacerStyle = ElementStyle
4
+
5
+ export interface UISpacer {
6
+ readonly type: "spacer",
7
+ style: Style<this, UISpacerStyle>,
8
+
9
+ }
10
+ export function UISpacer(): UISpacer
11
+ export function UISpacer(style: UISpacerStyle): UISpacer
12
+ export function UISpacer(style?: UISpacerStyle): UISpacer {
13
+ return new Element("spacer", style ?? {})
14
+ }
@@ -0,0 +1,40 @@
1
+ import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type TextStyle } from "./UINode"
2
+
3
+ export type UITextStyle = ElementStyle & TextStyle
4
+
5
+ export interface UIText {
6
+ readonly type: "text",
7
+ text: string,
8
+ style: Style<this, UITextStyle>,
9
+ animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
10
+ animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
11
+
12
+ onLayout(onLayout: OnLayoutCallback): this
13
+ }
14
+
15
+ export class TextElement extends Element<"text"> {
16
+ private _text: string
17
+ constructor(style: any, text: string) {
18
+ super("text", style)
19
+ this._text = text
20
+ }
21
+ get text(): string {
22
+ return this._text
23
+ }
24
+ set text(value) {
25
+ this._text = value
26
+ if (this._id !== 0) {
27
+ _creatorUI.updateNode(this._id, "text", value)
28
+ }
29
+ }
30
+ }
31
+
32
+ export function UIText(text: string): UIText;
33
+ export function UIText(style: UITextStyle, text: string): UIText;
34
+ export function UIText(...args: [string] | [UITextStyle, string]): UIText {
35
+ if (args.length === 1) {
36
+ return new TextElement({}, args[0])
37
+ } else {
38
+ return new TextElement(args[0], args[1])
39
+ }
40
+ }
@@ -0,0 +1,38 @@
1
+ import type { VideoPlayer } from "../runtime/media"
2
+ import { Element, type AnimateStyle, type AppearStyle, type BaseStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type StyleFn } from "./UINode"
3
+
4
+ export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
5
+
6
+ export interface UIVideo {
7
+ readonly type: "video",
8
+ player: VideoPlayer
9
+ style: Style<this, UIVideoStyle>
10
+ animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
11
+ animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
12
+
13
+ onLayout(onLayout: OnLayoutCallback): this
14
+ }
15
+
16
+ class VideoElement extends Element<"video"> {
17
+ private _playerId: number
18
+ private _player: VideoPlayer
19
+ constructor(style: any, player: VideoPlayer) {
20
+ super("video", style)
21
+ this._player = player
22
+ this._playerId = (player as any)._id
23
+ }
24
+
25
+ get player() {
26
+ return this._player
27
+ }
28
+ }
29
+
30
+ export function UIVideo(player: VideoPlayer): UIVideo
31
+ export function UIVideo(style: UIVideoStyle, player: VideoPlayer): UIVideo
32
+ export function UIVideo(...args: [VideoPlayer] | [UIVideoStyle, VideoPlayer]): UIVideo {
33
+ if (args.length === 1) {
34
+ return new VideoElement({}, args[0])
35
+ } else {
36
+ return new VideoElement(args[0], args[1])
37
+ }
38
+ }
@@ -0,0 +1,222 @@
1
+ import { Element, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type PaddingStyle, type Style, type UINodeChild } from "./UINode"
2
+
3
+ export type UIVirtualizedListStyle = DrawableStyle & ElementStyle
4
+
5
+ export interface VListConfig<T> {
6
+ /** Стабильный уникальный ключ айтема */
7
+ keyOf: (item: T) => string
8
+ /** Фабрика поддерева. Вызывается при входе айтема в окно; поддерево живёт до выхода из окна */
9
+ render: (item: T) => UINodeChild
10
+ /**
11
+ * Оценка высоты (px) до первого измерения. Число — или функция от айтема,
12
+ * например для чата: msg => 40 + Math.ceil(msg.text.length / 35) * 20
13
+ * Вычисляется один раз при добавлении айтема, в нативку уходит готовое число.
14
+ */
15
+ estimatedHeight: number | ((item: T) => number)
16
+ /** px вокруг вьюпорта, которые держим смонтированными. Default: высота вьюпорта */
17
+ overscan?: number
18
+ /** Чат-режим: первый layout проскроллен в конец, append при положении внизу автоскроллит */
19
+ inverted?: boolean
20
+ }
21
+
22
+ export interface UIVirtualizedList<T = unknown> {
23
+ readonly type: "vlist"
24
+ style: Style<this, UIVirtualizedListStyle>
25
+
26
+ /** Полная замена данных. Нативка сама размонтирует исчезнувшие ключи через _syncWindow */
27
+ setData(items: T[]): this
28
+ append(...items: T[]): this
29
+ /** Вставка в начало БЕЗ прыжка скролла (компенсация на стороне нативки) */
30
+ prepend(...items: T[]): this
31
+ /** Обновить айтемы с теми же ключами: re-render, если смонтированы. Высота в кэше сохраняется до повторного измерения */
32
+ update(...items: T[]): this
33
+ removeByKey(...keys: string[]): this
34
+
35
+ readonly itemCount: number
36
+ getItem(key: string): T | undefined
37
+
38
+ scrollTo(offset: number, animated?: boolean): void
39
+ scrollToKey(key: string, animated?: boolean): void
40
+ scrollToEnd(animated?: boolean): void
41
+
42
+ onScroll(callback: (scrollPosition: number) => void): this
43
+ /** Порог в px от нижнего края контента. Срабатывает один раз до ухода от края (защёлка в нативке) */
44
+ onEndReached(thresholdPx: number, callback: () => void): this
45
+ /** Порог от верхнего края — подгрузка истории в чате */
46
+ onStartReached(thresholdPx: number, callback: () => void): this
47
+ onLayout(onLayout: OnLayoutCallback): this
48
+ }
49
+
50
+ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedList<T> {
51
+
52
+ // --- читается нативкой в createUINode (copyProperty / copyNodePtr) ---
53
+ _overscan: number // -1 = "высота вьюпорта", резолвится в нативке
54
+ _inverted: boolean
55
+ _keys: string[] = [] // начальное состояние до маунта ноды
56
+ _estimates: number[] = [] // параллельно _keys
57
+ _endThreshold = 0
58
+ _startThreshold = 0
59
+
60
+ // --- списки слушателей, нативка забирает через copyPropertyListPtr ---
61
+ protected sl: ((scrollPosition: number) => void)[] = [] // scroll
62
+ protected erl: (() => void)[] = [] // endReached
63
+ protected strl: (() => void)[] = [] // startReached
64
+
65
+ // --- приватное состояние JS-стороны ---
66
+ private items = new Map<string, T>()
67
+ private mounted = new Map<string, UINodeChild>()
68
+ private keyOf: (item: T) => string
69
+ private renderItem: (item: T) => UINodeChild
70
+ private estimate: (item: T) => number
71
+
72
+ constructor(config: VListConfig<T>) {
73
+ super("vlist", {})
74
+ this.keyOf = config.keyOf
75
+ this.renderItem = config.render
76
+ this.estimate = typeof config.estimatedHeight === "function"
77
+ ? config.estimatedHeight
78
+ : () => config.estimatedHeight as number
79
+ this._overscan = config.overscan ?? -1
80
+ this._inverted = config.inverted ?? false
81
+ }
82
+
83
+ private get isAttached(): boolean {
84
+ return !!this._id && this._id !== 0
85
+ }
86
+
87
+ private register(items: T[]): [string[], number[]] {
88
+ const keys: string[] = []
89
+ const estimates: number[] = []
90
+ for (const item of items) {
91
+ const k = this.keyOf(item)
92
+ this.items.set(k, item)
93
+ keys.push(k)
94
+ estimates.push(this.estimate(item))
95
+ }
96
+ return [keys, estimates]
97
+ }
98
+
99
+ setData(items: T[]): this {
100
+ const prevItems = new Map(this.items)
101
+ const prevMounted = [...this.mounted.keys()]
102
+ this.items.clear()
103
+ const [keys, estimates] = this.register(items)
104
+ this._keys = keys
105
+ this._estimates = estimates
106
+ if (this.isAttached) {
107
+ // нативка диффает по ключам: исчезнувшие смонтированные ключи придут в _syncWindow как unmount
108
+ _creatorUI.vlistSetKeys(this, keys, estimates)
109
+ // setKeys диффает ТОЛЬКО по ключам — пережившие replace смонтированные айтемы сохраняют
110
+ // старое поддерево. Если объект данных изменился, перерисовываем их (как update), иначе
111
+ // строки остаются со старым содержимым. На вебе экран в истории держит айтемы
112
+ // смонтированными, поэтому без этого повторный setData их не обновлял.
113
+ for (const k of prevMounted) {
114
+ if (this.mounted.has(k) && this.items.get(k) !== prevItems.get(k)) {
115
+ _creatorUI.vlistInvalidate(this, k)
116
+ }
117
+ }
118
+ }
119
+ return this
120
+ }
121
+
122
+ append(...items: T[]): this {
123
+ const [keys, estimates] = this.register(items)
124
+ this._keys.push(...keys)
125
+ this._estimates.push(...estimates)
126
+ if (this.isAttached) {
127
+ // inverted + позиция внизу → нативка автоскроллит к концу
128
+ _creatorUI.vlistInsertKeys(this, -1, keys, estimates)
129
+ }
130
+ return this
131
+ }
132
+
133
+ prepend(...items: T[]): this {
134
+ const [keys, estimates] = this.register(items)
135
+ this._keys.unshift(...keys)
136
+ this._estimates.unshift(...estimates)
137
+ if (this.isAttached) {
138
+ // нативка компенсирует scrollY на сумму estimates — позиция не прыгает
139
+ _creatorUI.vlistInsertKeys(this, 0, keys, estimates)
140
+ }
141
+ return this
142
+ }
143
+
144
+ update(...items: T[]): this {
145
+ for (const item of items) {
146
+ const k = this.keyOf(item)
147
+ if (!this.items.has(k)) continue
148
+ this.items.set(k, item)
149
+ if (this.isAttached && this.mounted.has(k)) {
150
+ // unmount+mount, кэш высоты сохраняется до повторного измерения.
151
+ // Несмонтированный айтем перерендерится сам при входе в окно
152
+ _creatorUI.vlistInvalidate(this, k)
153
+ }
154
+ }
155
+ return this
156
+ }
157
+
158
+ removeByKey(...keys: string[]): this {
159
+ for (const k of keys) {
160
+ if (!this.items.delete(k)) continue
161
+ const i = this._keys.indexOf(k)
162
+ if (i >= 0) { this._keys.splice(i, 1); this._estimates.splice(i, 1) }
163
+ }
164
+ if (this.isAttached) {
165
+ _creatorUI.vlistRemoveKeys(this, keys)
166
+ }
167
+ return this
168
+ }
169
+
170
+ get itemCount(): number { return this.items.size }
171
+ getItem(key: string): T | undefined { return this.items.get(key) }
172
+
173
+ // --- команды: офсеты и позиции знает только нативка (префикс-суммы) ---
174
+ scrollTo(offset: number, animated = true): void {
175
+ _creatorUI.command(this, "scrollTo", offset, animated)
176
+ }
177
+ scrollToKey(key: string, animated = true): void {
178
+ _creatorUI.command(this, "scrollToKey", key, animated)
179
+ }
180
+ scrollToEnd(animated = true): void {
181
+ _creatorUI.command(this, "scrollToEnd", animated)
182
+ }
183
+
184
+ onScroll(callback: (scrollPosition: number) => void): this {
185
+ this.sl.push(callback)
186
+ return this
187
+ }
188
+ onEndReached(thresholdPx: number, callback: () => void): this {
189
+ this._endThreshold = thresholdPx
190
+ this.erl.push(callback)
191
+ return this
192
+ }
193
+ onStartReached(thresholdPx: number, callback: () => void): this {
194
+ this._startThreshold = thresholdPx
195
+ this.strl.push(callback)
196
+ return this
197
+ }
198
+
199
+ /**
200
+ * Единственный колбэк из нативки (через jsPtr, как у button).
201
+ * Вызывается ТОЛЬКО когда окно пересекло границу — не на каждый кадр скролла.
202
+ * Порядок: сначала unmount (нативка уже снесла поддеревья и обнулила _id,
203
+ * здесь только чистим JS-ссылки), потом mount.
204
+ */
205
+ _syncWindow(unmountKeys: string[], mountKeys: string[]): void {
206
+ for (const k of unmountKeys) {
207
+ this.mounted.delete(k)
208
+ }
209
+ for (const k of mountKeys) {
210
+ const item = this.items.get(k)
211
+ if (item === undefined) continue
212
+ const node = this.renderItem(item)
213
+ this.mounted.set(k, node)
214
+ // createUINode в detached yoga root + измерение (width, NaN) + кэш высоты
215
+ _creatorUI.vlistMount(this, k, node)
216
+ }
217
+ }
218
+ }
219
+
220
+ export function UIVirtualizedList<T>(config: VListConfig<T>): UIVirtualizedList<T> {
221
+ return new VListElement<T>(config)
222
+ }
@@ -0,0 +1,97 @@
1
+ import { TouchStartEvent } from "../runtime/touch"
2
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type PositionStyle, type Style, type UINodeChild } from "./UINode"
3
+
4
+
5
+ export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
6
+ // A layer behind the widget that intercepts clicks. "transparent" still intercepts;
7
+ // null removes the layer. A tap on it fires onOverlayTap.
8
+ overlayColor?: Color | null
9
+ }
10
+
11
+
12
+ export interface UIWidget {
13
+ readonly type: "widget",
14
+ style: Style<this, UIWidgetStyle>,
15
+ animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
16
+ animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
17
+
18
+ show(): void,
19
+ hide(): void,
20
+
21
+ readonly isShow: boolean,
22
+
23
+ append(...nodes: UINodeChild[]): UIWidget
24
+ insert(index: number, ...nodes: UINodeChild[]): UIWidget
25
+ remove(...nodes: UINodeChild[]): UIWidget
26
+ setContent(nodes: UINodeChild[]): this
27
+
28
+ readonly children: UINodeChild[],
29
+
30
+ onLayout(onLayout: OnLayoutCallback): this
31
+ onTouchStart(callback: (ev: TouchStartEvent<UIWidget>) => void): this,
32
+ onOverlayTap(callback: () => void): this,
33
+ onBackPressed(callback: () => void): this
34
+ }
35
+
36
+
37
+ export class WidgetElement extends ContainerElement<"widget"> {
38
+ scrollable = false as const
39
+ show(): void {
40
+ _creatorUI.showWidget(this)
41
+ }
42
+ hide(): void {
43
+ _creatorUI.hideWidget(this)
44
+ }
45
+ get isShow(): boolean {
46
+ return this._id !== 0
47
+ }
48
+
49
+ _interactive = false
50
+ readonly touchStartListeners: any[] = []
51
+ onTouchStart (callback: any): this {
52
+ this._interactive = true
53
+ this.touchStartListeners.push(callback)
54
+ return this
55
+ }
56
+ _emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
57
+ const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
58
+ touchEvent.target = this
59
+ for (let callback of this.touchStartListeners) {
60
+ callback(touchEvent)
61
+ if ((touchEvent as any)._trackHandler) {
62
+ return (touchEvent as any)._trackHandler
63
+ }
64
+ }
65
+ return null
66
+ }
67
+ readonly otl: (() => void)[] = []
68
+ onOverlayTap(callback: () => void): this {
69
+ this.otl.push(callback)
70
+ return this
71
+ }
72
+
73
+ _backButtonCallback?: () => void
74
+ onBackPressed(callback: any): this {
75
+ this._backButtonCallback = callback
76
+ return this
77
+ }
78
+ }
79
+
80
+
81
+ export function UIWidget(): UIWidget;
82
+ export function UIWidget(children: UINodeChild[]): UIWidget;
83
+ export function UIWidget(style: UIWidgetStyle): UIWidget;
84
+ export function UIWidget(style: UIWidgetStyle, children: UINodeChild[]): UIWidget;
85
+ export function UIWidget(...args: [] | [UINodeChild[] | UIWidgetStyle] | [UIWidgetStyle, children: UINodeChild[]]): UIWidget {
86
+ if (args.length === 0) {
87
+ return new WidgetElement("widget", {}, [])
88
+ } else if (args.length === 1) {
89
+ if (Array.isArray(args[0])) {
90
+ return new WidgetElement("widget", {}, args[0])
91
+ } else {
92
+ return new WidgetElement("widget", args[0], [])
93
+ }
94
+ } else {
95
+ return new WidgetElement("widget", args[0], args[1])
96
+ }
97
+ }
@@ -0,0 +1,10 @@
1
+ type FontOptions = {
2
+ weight?: number,
3
+ style?: "normal" | "italic"
4
+ }
5
+
6
+ export async function registerFont (fontFamily: string, url: string, options: FontOptions = {}): Promise<void> {
7
+ return new Promise<void>((res, rej) => {
8
+ _creatorUI.registerFont(fontFamily, url, options, res, rej)
9
+ })
10
+ }
@@ -0,0 +1,73 @@
1
+ import { UIScreen } from "./UIScreen";
2
+
3
+ type RouterOptions = {
4
+ showDefaultBackButton?: boolean
5
+ }
6
+
7
+ type Router = {
8
+ get current(): UIScreen<boolean>
9
+ init<T extends boolean>(homePage: UIScreen<T>, opts?: RouterOptions): void
10
+ push<T extends boolean>(screen: UIScreen<T>): void
11
+ replace<T extends boolean>(screen: UIScreen<T>, options?: ReplaceOptions): void
12
+ pop(to?: number): void
13
+ hide(): void
14
+ restore(): void
15
+
16
+ addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
17
+ removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
18
+ }
19
+
20
+ let currentScreen: UIScreen<any> | null = null
21
+ let onChange = <T extends boolean>(screen: UIScreen<T>) => {
22
+ currentScreen = screen;
23
+ for (let callback of (Router as any)._changeListeners) {
24
+ callback(screen)
25
+ }
26
+ }
27
+
28
+ type ReplaceOptions = {
29
+ transition: "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
30
+ "zoom" | "zoom-out" | "zoom-in" | "fade" | "none"
31
+ }
32
+
33
+ export const Router: Router = /*#__PURE__*/ Object.freeze({
34
+
35
+ init(homePage: UIScreen<any>, opts: RouterOptions = {}): void {
36
+ _creatorUI.routerOpen(homePage, onChange, opts.showDefaultBackButton ?? false)
37
+ currentScreen = homePage
38
+ },
39
+ push(screen: UIScreen<any>): void {
40
+ _creatorUI.routerPush(screen)
41
+ },
42
+ replace(screen: UIScreen<any>, opts?: ReplaceOptions): void {
43
+ _creatorUI.routerReplace(screen, opts?.transition ?? "fade")
44
+ },
45
+ pop(to?: number): void {
46
+ _creatorUI.routerPop(to ?? -1)
47
+ },
48
+ hide() {
49
+ _creatorUI.routerHide()
50
+ },
51
+ restore() {
52
+ _creatorUI.routerRestore()
53
+ },
54
+
55
+ get current(): UIScreen<any> {
56
+ return currentScreen!
57
+ },
58
+
59
+ _changeListeners: [] as any[],
60
+ addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
61
+ if (channel === "change") {
62
+ this._changeListeners.push(callback)
63
+ }
64
+ },
65
+ removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
66
+ if (channel === "change") {
67
+ let index = this._changeListeners.indexOf(callback)
68
+ if (index >= 0) {
69
+ this._changeListeners.splice(index, 1)
70
+ }
71
+ }
72
+ }
73
+ })