cellery 0.0.4 → 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.
- package/README.md +68 -6
- package/index.js +9 -0
- package/lib/cellery.js +24 -0
- package/lib/cells.js +116 -0
- package/lib/{base.js → decorations.js} +47 -85
- package/package.json +5 -26
- package/example.js +0 -258
- package/lib/components/base.js +0 -165
- package/lib/components/index.js +0 -7
- package/lib/components/list.js +0 -107
- package/lib/index.js +0 -7
- package/lib/keys.js +0 -9
- package/lib/renderers/html-renderer.js +0 -133
- package/lib/renderers/index.js +0 -4
- package/lib/renderers/tui-renderer.js +0 -480
package/example.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
const { EdgeInsets, Color, Cellery, BoxDecoration, Border, Alignment, HotKey, keys } = require('.')
|
|
2
|
-
const { Container, Text, Center, Pressable, Scrollable } = require('cellery/components')
|
|
3
|
-
const { CelleryRendererTUI } = require('cellery/renderers')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const repos = [
|
|
6
|
-
'my-first-repo',
|
|
7
|
-
'cellery',
|
|
8
|
-
'git-remote-pear-transport',
|
|
9
|
-
'pear-desktop',
|
|
10
|
-
'pear-runtime',
|
|
11
|
-
'bare-kit',
|
|
12
|
-
'bare-dev',
|
|
13
|
-
'hypercore',
|
|
14
|
-
'hyperswarm',
|
|
15
|
-
'hyperdht',
|
|
16
|
-
'hypercore-crypto',
|
|
17
|
-
'compact-encoding',
|
|
18
|
-
'protomux',
|
|
19
|
-
'b4a',
|
|
20
|
-
'random-access-storage',
|
|
21
|
-
'random-access-file',
|
|
22
|
-
'brittle',
|
|
23
|
-
'quickbit',
|
|
24
|
-
'safety-catch'
|
|
25
|
-
]
|
|
26
|
-
// Load file content once
|
|
27
|
-
const fileContent = fs.readFileSync('./example.js', 'utf8')
|
|
28
|
-
const lines = fileContent.split('\n')
|
|
29
|
-
|
|
30
|
-
// Create persistent stateful components outside render function
|
|
31
|
-
let selected = 0
|
|
32
|
-
let currentView = 'list' // 'list' or 'file'
|
|
33
|
-
|
|
34
|
-
// Scrollable for the repo list - maintains its own scroll state
|
|
35
|
-
const listScrollable = new Scrollable({
|
|
36
|
-
width: '100%',
|
|
37
|
-
height: 'calc(100% - 1)',
|
|
38
|
-
scrollOffset: 0,
|
|
39
|
-
child: new Container({
|
|
40
|
-
width: '100%',
|
|
41
|
-
height: '100%',
|
|
42
|
-
alignment: Alignment.Center,
|
|
43
|
-
children: [] // Will be populated in render
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// Scrollable for file viewer - maintains its own scroll state
|
|
48
|
-
const fileScrollable = new Scrollable({
|
|
49
|
-
width: '100%',
|
|
50
|
-
height: '100%',
|
|
51
|
-
scrollOffset: 0,
|
|
52
|
-
child: new Container({
|
|
53
|
-
width: '100%',
|
|
54
|
-
height: '100%',
|
|
55
|
-
children: lines.map(
|
|
56
|
-
(line) =>
|
|
57
|
-
new Text({
|
|
58
|
-
value: line,
|
|
59
|
-
width: '100%'
|
|
60
|
-
})
|
|
61
|
-
)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Navigation controls for list view
|
|
66
|
-
const listUpControl = new Pressable({
|
|
67
|
-
hotkey: new HotKey({ key: keys.ARROW_UP }),
|
|
68
|
-
onPress: function () {
|
|
69
|
-
selected = selected === 0 ? repos.length - 1 : selected - 1
|
|
70
|
-
updateListView()
|
|
71
|
-
cellery.render()
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const listDownControl = new Pressable({
|
|
76
|
-
hotkey: new HotKey({ key: keys.ARROW_DOWN }),
|
|
77
|
-
onPress: function () {
|
|
78
|
-
selected = selected === repos.length - 1 ? 0 : selected + 1
|
|
79
|
-
updateListView()
|
|
80
|
-
cellery.render()
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const listEnterControl = new Pressable({
|
|
85
|
-
hotkey: new HotKey({ key: keys.ENTER }),
|
|
86
|
-
onPress: function () {
|
|
87
|
-
currentView = 'file'
|
|
88
|
-
fileScrollable.scrollOffset = 0
|
|
89
|
-
cellery.update(App())
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// Navigation controls for file view
|
|
94
|
-
const fileUpControl = new Pressable({
|
|
95
|
-
hotkey: new HotKey({ key: keys.ARROW_UP }),
|
|
96
|
-
onPress: function () {
|
|
97
|
-
if (fileScrollable.scrollOffset > 0) {
|
|
98
|
-
fileScrollable.scrollOffset--
|
|
99
|
-
cellery.render()
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const fileDownControl = new Pressable({
|
|
105
|
-
hotkey: new HotKey({ key: keys.ARROW_DOWN }),
|
|
106
|
-
onPress: function () {
|
|
107
|
-
const viewport = fileScrollable._renderedViewport
|
|
108
|
-
if (viewport && fileScrollable.scrollOffset + viewport.itemCount < lines.length) {
|
|
109
|
-
fileScrollable.scrollOffset++
|
|
110
|
-
cellery.render()
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const fileBackControl = new Pressable({
|
|
116
|
-
hotkey: new HotKey({ key: keys.ESC }),
|
|
117
|
-
onPress: function () {
|
|
118
|
-
currentView = 'list'
|
|
119
|
-
cellery.update(App())
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// Update list view children based on current selection
|
|
124
|
-
function updateListView() {
|
|
125
|
-
// Auto-scroll to keep selection visible
|
|
126
|
-
const viewport = listScrollable._renderedViewport
|
|
127
|
-
if (viewport && viewport.itemCount > 0) {
|
|
128
|
-
const positionInViewport = selected - listScrollable.scrollOffset
|
|
129
|
-
const triggerDistance = 1
|
|
130
|
-
// Scroll down if selection is too close to bottom
|
|
131
|
-
if (positionInViewport >= viewport.itemCount - triggerDistance) {
|
|
132
|
-
listScrollable.scrollOffset = Math.min(
|
|
133
|
-
repos.length - viewport.itemCount,
|
|
134
|
-
selected - viewport.itemCount + triggerDistance + 1
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
// Scroll up if selection is too close to top
|
|
138
|
-
if (positionInViewport < triggerDistance) {
|
|
139
|
-
listScrollable.scrollOffset = Math.max(0, selected - triggerDistance)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// Update list children with current selection highlighting
|
|
143
|
-
listScrollable.child.children = repos.map(
|
|
144
|
-
(name, i) =>
|
|
145
|
-
new Container({
|
|
146
|
-
width: '50%',
|
|
147
|
-
height: 3,
|
|
148
|
-
decoration: new BoxDecoration({
|
|
149
|
-
border: Border.all({
|
|
150
|
-
color: selected === i ? Color.from('#fa0') : Color.from('#bade5b')
|
|
151
|
-
})
|
|
152
|
-
}),
|
|
153
|
-
children: [
|
|
154
|
-
new Text({
|
|
155
|
-
value: name,
|
|
156
|
-
color: Color.from('#fff')
|
|
157
|
-
})
|
|
158
|
-
]
|
|
159
|
-
})
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function App() {
|
|
164
|
-
// Initialize list on first render
|
|
165
|
-
if (listScrollable.child.children.length === 0) {
|
|
166
|
-
updateListView()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const header = new Container({
|
|
170
|
-
width: '100%',
|
|
171
|
-
height: 3,
|
|
172
|
-
decoration: new BoxDecoration({
|
|
173
|
-
border: Border.all({
|
|
174
|
-
color: Color.from('#bade5b')
|
|
175
|
-
})
|
|
176
|
-
}),
|
|
177
|
-
children: [
|
|
178
|
-
new Center({
|
|
179
|
-
width: '100%',
|
|
180
|
-
height: 3,
|
|
181
|
-
child: new Text({
|
|
182
|
-
value: currentView === 'list' ? 'Pear Git' : `Pear Git - ${repos[selected]}`,
|
|
183
|
-
color: Color.from('#fff')
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
]
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
if (currentView === 'file') {
|
|
190
|
-
return new Container({
|
|
191
|
-
width: '100%',
|
|
192
|
-
height: '100%',
|
|
193
|
-
margin: EdgeInsets.all(2),
|
|
194
|
-
alignment: Alignment.Center,
|
|
195
|
-
decoration: new BoxDecoration({
|
|
196
|
-
border: Border.all()
|
|
197
|
-
}),
|
|
198
|
-
children: [
|
|
199
|
-
fileUpControl,
|
|
200
|
-
fileDownControl,
|
|
201
|
-
fileBackControl,
|
|
202
|
-
header,
|
|
203
|
-
new Text({
|
|
204
|
-
value: 'Use ↑/↓ to scroll, ESC to go back',
|
|
205
|
-
color: Color.from({ red: 200, green: 200, blue: 200 })
|
|
206
|
-
}),
|
|
207
|
-
new Container({
|
|
208
|
-
width: '100%',
|
|
209
|
-
height: 'calc(100% - 5)',
|
|
210
|
-
decoration: new BoxDecoration({
|
|
211
|
-
border: Border.all({
|
|
212
|
-
color: Color.from('#bade5b')
|
|
213
|
-
})
|
|
214
|
-
}),
|
|
215
|
-
children: [fileScrollable]
|
|
216
|
-
})
|
|
217
|
-
]
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// List view
|
|
222
|
-
const footer = new Text({
|
|
223
|
-
value: `${selected + 1}/${repos.length} | scroll: ${listScrollable.scrollOffset}`,
|
|
224
|
-
color: Color.from({ red: 100, green: 100, blue: 100 })
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
return new Container({
|
|
228
|
-
width: '100%',
|
|
229
|
-
height: '100%',
|
|
230
|
-
margin: EdgeInsets.all(2),
|
|
231
|
-
alignment: Alignment.Center,
|
|
232
|
-
decoration: new BoxDecoration({
|
|
233
|
-
border: Border.all()
|
|
234
|
-
}),
|
|
235
|
-
children: [
|
|
236
|
-
listUpControl,
|
|
237
|
-
listDownControl,
|
|
238
|
-
listEnterControl,
|
|
239
|
-
header,
|
|
240
|
-
new Text({
|
|
241
|
-
value: 'Use ↑/↓ to navigate, ENTER to view file',
|
|
242
|
-
color: Color.from({ red: 200, green: 200, blue: 200 })
|
|
243
|
-
}),
|
|
244
|
-
new Container({
|
|
245
|
-
width: '100%',
|
|
246
|
-
height: '70%',
|
|
247
|
-
alignment: Alignment.Center,
|
|
248
|
-
children: [listScrollable, footer]
|
|
249
|
-
})
|
|
250
|
-
]
|
|
251
|
-
})
|
|
252
|
-
}
|
|
253
|
-
const cellery = new Cellery({
|
|
254
|
-
renderer: new CelleryRendererTUI(),
|
|
255
|
-
child: App()
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
cellery.render()
|
package/lib/components/base.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
const { Cell } = require('../base')
|
|
2
|
-
|
|
3
|
-
class Center extends Cell {
|
|
4
|
-
constructor(opts = {}) {
|
|
5
|
-
super(opts)
|
|
6
|
-
|
|
7
|
-
this.child = opts.child
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Base container class for holding and aligning multiple children
|
|
13
|
-
*/
|
|
14
|
-
class Container extends Cell {
|
|
15
|
-
static observedAttributes = ['width', 'height']
|
|
16
|
-
|
|
17
|
-
constructor(opts = {}) {
|
|
18
|
-
super(opts)
|
|
19
|
-
this.width = opts.width
|
|
20
|
-
this.height = opts.height
|
|
21
|
-
this.alignment = opts.alignment
|
|
22
|
-
this.margin = opts.margin
|
|
23
|
-
this.padding = opts.padding
|
|
24
|
-
this.decoration = opts.decoration
|
|
25
|
-
this.color = opts.color
|
|
26
|
-
this.children = opts.children || []
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
30
|
-
// console.log('attributeChangedCallback', name, oldValue, newValue)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const TextAlign = {
|
|
35
|
-
Left: 'left',
|
|
36
|
-
Right: 'right',
|
|
37
|
-
Center: 'center'
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class Text extends Cell {
|
|
41
|
-
constructor(opts = {}) {
|
|
42
|
-
super(opts)
|
|
43
|
-
|
|
44
|
-
this.value = opts.value || ''
|
|
45
|
-
this.color = opts.color
|
|
46
|
-
this.textAlign = opts.textAlign
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
class Pressable extends Cell {
|
|
51
|
-
hotkey = null
|
|
52
|
-
#onPress = null
|
|
53
|
-
|
|
54
|
-
constructor(opts = {}) {
|
|
55
|
-
super(opts)
|
|
56
|
-
|
|
57
|
-
this.child = opts.child
|
|
58
|
-
this.#onPress = opts.onPress
|
|
59
|
-
this.hotkey = opts.hotkey
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async onPress() {
|
|
63
|
-
if (!this.#onPress) return
|
|
64
|
-
|
|
65
|
-
await this.#onPress()
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Scrollable component - manages viewport and scroll offset
|
|
71
|
-
*
|
|
72
|
-
* The framework keeps it simple: this component just tracks scroll state.
|
|
73
|
-
* Renderers implement their own scroll triggering logic while maintaining
|
|
74
|
-
* consistent UX.
|
|
75
|
-
*
|
|
76
|
-
* Takes a single child (typically a Container with children array)
|
|
77
|
-
*/
|
|
78
|
-
class Scrollable extends Cell {
|
|
79
|
-
constructor(opts = {}) {
|
|
80
|
-
super(opts)
|
|
81
|
-
|
|
82
|
-
this.width = opts.width
|
|
83
|
-
this.height = opts.height
|
|
84
|
-
this.child = opts.child // Single child, not children array
|
|
85
|
-
|
|
86
|
-
// Scroll state - managed by the component consumer (e.g., your app logic)
|
|
87
|
-
this.scrollOffset = opts.scrollOffset || 0
|
|
88
|
-
|
|
89
|
-
// Optional: callback when scroll would be useful (renderer can trigger this)
|
|
90
|
-
this.onScrollRequest = opts.onScrollRequest || null
|
|
91
|
-
|
|
92
|
-
// Renderer will populate this after rendering
|
|
93
|
-
this._renderedViewport = null
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get viewport info from last render
|
|
98
|
-
* Renderer populates this during render
|
|
99
|
-
*/
|
|
100
|
-
getViewportInfo() {
|
|
101
|
-
return this._renderedViewport || { itemCount: 0, height: 0 }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Request scroll by delta
|
|
106
|
-
* This is called by renderers or application logic to update scroll position
|
|
107
|
-
*/
|
|
108
|
-
requestScroll(delta) {
|
|
109
|
-
const newOffset = this.scrollOffset + delta
|
|
110
|
-
|
|
111
|
-
if (this.onScrollRequest) {
|
|
112
|
-
this.onScrollRequest(newOffset, delta)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return newOffset
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Scroll to make a specific child index visible
|
|
120
|
-
* Returns the new scroll offset
|
|
121
|
-
*/
|
|
122
|
-
scrollToIndex(index, viewportHeight) {
|
|
123
|
-
if (index < this.scrollOffset) {
|
|
124
|
-
// Scroll up to show this item
|
|
125
|
-
return index
|
|
126
|
-
} else if (index >= this.scrollOffset + viewportHeight) {
|
|
127
|
-
// Scroll down to show this item
|
|
128
|
-
return index - viewportHeight + 1
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return this.scrollOffset
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Check if we can scroll in a direction
|
|
136
|
-
* childCount is the number of items in the scrollable content
|
|
137
|
-
*/
|
|
138
|
-
canScroll(direction, viewportHeight, childCount) {
|
|
139
|
-
if (direction < 0) {
|
|
140
|
-
return this.scrollOffset > 0
|
|
141
|
-
} else {
|
|
142
|
-
return this.scrollOffset + viewportHeight < childCount
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get the visible range of children for current scroll position
|
|
148
|
-
* childCount is the total number of items
|
|
149
|
-
*/
|
|
150
|
-
getVisibleRange(viewportHeight, childCount) {
|
|
151
|
-
const start = Math.max(0, this.scrollOffset)
|
|
152
|
-
const end = Math.min(childCount, start + viewportHeight)
|
|
153
|
-
|
|
154
|
-
return { start, end }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = {
|
|
159
|
-
Center,
|
|
160
|
-
Container,
|
|
161
|
-
Pressable,
|
|
162
|
-
Scrollable,
|
|
163
|
-
Text,
|
|
164
|
-
TextAlign
|
|
165
|
-
}
|
package/lib/components/index.js
DELETED
package/lib/components/list.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
const { Cell, HotKey, Alignment } = require('../base')
|
|
2
|
-
const keys = require('../keys')
|
|
3
|
-
const { Scrollable, Pressable, Container } = require('./base')
|
|
4
|
-
|
|
5
|
-
class List extends Cell {
|
|
6
|
-
constructor(opts = {}) {
|
|
7
|
-
super(opts)
|
|
8
|
-
|
|
9
|
-
this.children = opts.children || []
|
|
10
|
-
this.selected = opts.selected
|
|
11
|
-
this.scrollOffset = opts.scrollOffset || 0
|
|
12
|
-
this.triggerDistance = opts.triggerDistance || 0
|
|
13
|
-
this.viewportItemCount = opts.viewportItemCount || 0
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
render() {
|
|
17
|
-
// Calculate new scroll offset based on viewport info from last render
|
|
18
|
-
let newScrollOffset = this.scrollOffset
|
|
19
|
-
|
|
20
|
-
if (this.selected >= 0 && this.viewportItemCount) {
|
|
21
|
-
if (this.viewportItemCount > 0) {
|
|
22
|
-
const positionInViewport = this.selected - this.scrollOffset
|
|
23
|
-
|
|
24
|
-
// Scroll down if we're too close to bottom
|
|
25
|
-
if (positionInViewport >= this.viewportItemCount - this.triggerDistance) {
|
|
26
|
-
newScrollOffset = Math.min(
|
|
27
|
-
this.children.length - this.viewportItemCount,
|
|
28
|
-
this.selected - this.viewportItemCount + this.triggerDistance + 1
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Scroll up if we're too close to top
|
|
33
|
-
if (positionInViewport < this.triggerDistance) {
|
|
34
|
-
newScrollOffset = Math.max(0, this.selected - this.triggerDistance)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Clamp to valid range
|
|
38
|
-
newScrollOffset = Math.max(
|
|
39
|
-
0,
|
|
40
|
-
Math.min(Math.max(0, this.children.length - this.viewportItemCount), newScrollOffset)
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Navigation controls (invisible, just for hotkeys)
|
|
46
|
-
const upControl = new Pressable({
|
|
47
|
-
hotkey: new HotKey({ key: keys.ARROW_UP }),
|
|
48
|
-
onPress: () => {
|
|
49
|
-
const newSelected = this.selected === 0 ? this.children.length - 1 : this.selected - 1
|
|
50
|
-
this.emit('navigate', {
|
|
51
|
-
selected: newSelected,
|
|
52
|
-
scrollOffset: newScrollOffset,
|
|
53
|
-
viewportItemCount: this._scrollableRef._renderedViewport.itemCount
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const downControl = new Pressable({
|
|
59
|
-
hotkey: new HotKey({ key: keys.ARROW_DOWN }),
|
|
60
|
-
onPress: () => {
|
|
61
|
-
const newSelected = this.selected === this.children.length - 1 ? 0 : this.selected + 1
|
|
62
|
-
this.emit('navigate', {
|
|
63
|
-
selected: newSelected,
|
|
64
|
-
scrollOffset: newScrollOffset,
|
|
65
|
-
viewportItemCount: this._scrollableRef._renderedViewport.itemCount
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
// Create container with all children and their pressables
|
|
71
|
-
const listContainer = new Container({
|
|
72
|
-
width: '100%',
|
|
73
|
-
height: '100%', // Use full height available from Scrollable
|
|
74
|
-
alignment: Alignment.Center,
|
|
75
|
-
children: this.children.map(
|
|
76
|
-
(child, i) =>
|
|
77
|
-
new Pressable({
|
|
78
|
-
hotkey: i === this.selected ? new HotKey({ key: keys.ENTER }) : null,
|
|
79
|
-
onPress: () => {
|
|
80
|
-
this.emit('select', { selected: this.selected })
|
|
81
|
-
},
|
|
82
|
-
child
|
|
83
|
-
})
|
|
84
|
-
)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// Wrap container in scrollable
|
|
88
|
-
const scrollable = new Scrollable({
|
|
89
|
-
width: '100%',
|
|
90
|
-
height: 'calc(100% - 1)', // Reserve 1 row for footer
|
|
91
|
-
scrollOffset: newScrollOffset,
|
|
92
|
-
child: listContainer
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// Store ref for next render
|
|
96
|
-
this._scrollableRef = scrollable
|
|
97
|
-
|
|
98
|
-
return new Container({
|
|
99
|
-
width: '100%',
|
|
100
|
-
height: '70%',
|
|
101
|
-
alignment: Alignment.Center,
|
|
102
|
-
children: [upControl, downControl, scrollable]
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = List
|
package/lib/index.js
DELETED
package/lib/keys.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
function escapeHTML(str) {
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
class CelleryRendererHTML {
|
|
11
|
-
components = {
|
|
12
|
-
Container: function () {
|
|
13
|
-
const width = this.width
|
|
14
|
-
const height = this.height
|
|
15
|
-
|
|
16
|
-
// Calculate margins (outside everything)
|
|
17
|
-
const margin = this.margin || { left: 0, top: 0, right: 0, bottom: 0 }
|
|
18
|
-
const marginLeft = margin.left
|
|
19
|
-
const marginTop = margin.top
|
|
20
|
-
const marginRight = margin.right
|
|
21
|
-
const marginBottom = margin.bottom
|
|
22
|
-
|
|
23
|
-
// Check if border exists
|
|
24
|
-
const hasBorder = this.decoration && this.decoration.border
|
|
25
|
-
const borderWidth = hasBorder ? 1 : 0
|
|
26
|
-
|
|
27
|
-
// Calculate padding (inside decoration/border)
|
|
28
|
-
const padding = this.padding || { left: 0, top: 0, right: 0, bottom: 0 }
|
|
29
|
-
const paddingLeft = padding.left
|
|
30
|
-
const paddingTop = padding.top
|
|
31
|
-
const paddingRight = padding.right
|
|
32
|
-
const paddingBottom = padding.bottom
|
|
33
|
-
|
|
34
|
-
// Build styles
|
|
35
|
-
const styles = {
|
|
36
|
-
width: `${width}px`,
|
|
37
|
-
height: `${height}px`,
|
|
38
|
-
margin: `${marginTop}px ${marginRight}px ${marginBottom}px ${marginLeft}px`,
|
|
39
|
-
padding: `${paddingTop}px ${paddingRight}px ${paddingBottom}px ${paddingLeft}px`,
|
|
40
|
-
boxSizing: 'border-box',
|
|
41
|
-
display: 'flex',
|
|
42
|
-
flexDirection: 'column'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (hasBorder) {
|
|
46
|
-
const borderColor = this.decoration.border.color?.toRGBA() || '#000'
|
|
47
|
-
styles.border = `${borderWidth}px solid ${borderColor}`
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (this.color) {
|
|
51
|
-
styles.backgroundColor = this.color.toRGBA()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Render children
|
|
55
|
-
let childrenHTML = ''
|
|
56
|
-
if (this.children && this.children.length > 0) {
|
|
57
|
-
for (const child of this.children) {
|
|
58
|
-
childrenHTML += this.renderer._renderComponent(child)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const styleStr = Object.entries(styles)
|
|
63
|
-
.map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`)
|
|
64
|
-
.join('; ')
|
|
65
|
-
|
|
66
|
-
return `<div style="${styleStr}">${childrenHTML}</div>`
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
Center: function () {
|
|
70
|
-
const width = this.width
|
|
71
|
-
const height = this.height
|
|
72
|
-
|
|
73
|
-
const styles = {
|
|
74
|
-
width: `${width}px`,
|
|
75
|
-
height: `${height}px`,
|
|
76
|
-
display: 'flex',
|
|
77
|
-
justifyContent: 'center',
|
|
78
|
-
alignItems: 'center'
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Render child if exists
|
|
82
|
-
let childHTML = ''
|
|
83
|
-
if (this.child) {
|
|
84
|
-
childHTML = this.renderer._renderComponent(this.child)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const styleStr = Object.entries(styles)
|
|
88
|
-
.map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`)
|
|
89
|
-
.join('; ')
|
|
90
|
-
|
|
91
|
-
return `<div style="${styleStr}">${childHTML}</div>`
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
Text: function () {
|
|
95
|
-
const text = String(this.value)
|
|
96
|
-
const color = this.color
|
|
97
|
-
const colorStr = color
|
|
98
|
-
? `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha || 1})`
|
|
99
|
-
: 'inherit'
|
|
100
|
-
|
|
101
|
-
return `<span style="color: ${colorStr}; textAlign: ${this.textAlign || 'left'}">${escapeHTML(text)}</span>`
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
_renderComponent(component) {
|
|
106
|
-
component.renderer = this
|
|
107
|
-
const rendererFn = this.components[component.constructor.name]
|
|
108
|
-
return rendererFn.call(component)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
render(component) {
|
|
112
|
-
const html = this._renderComponent(component)
|
|
113
|
-
return `<!DOCTYPE html>
|
|
114
|
-
<html>
|
|
115
|
-
<head>
|
|
116
|
-
<meta charset="UTF-8">
|
|
117
|
-
<style>
|
|
118
|
-
body {
|
|
119
|
-
margin: 0;
|
|
120
|
-
padding: 20px;
|
|
121
|
-
font-family: monospace;
|
|
122
|
-
background: #f0f0f0;
|
|
123
|
-
}
|
|
124
|
-
</style>
|
|
125
|
-
</head>
|
|
126
|
-
<body>
|
|
127
|
-
${html}
|
|
128
|
-
</body>
|
|
129
|
-
</html>`
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = { CelleryRendererHTML }
|
package/lib/renderers/index.js
DELETED