flinker-dom 0.0.4 → 1.0.4
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 +556 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,561 @@
|
|
|
1
|
-
|
|
1
|
+
## Intro
|
|
2
|
+
__FlinkerDom__ (FD) is a TypeScript library for building user interfaces and single-page web-applications.
|
|
3
|
+
|
|
4
|
+
__FD__
|
|
5
|
+
+ uses all the power and flexibility of the [Flinker](https://github.com/Dittner/Flinker) FRP;
|
|
6
|
+
|
|
7
|
+
+ does not use a virtual DOM;
|
|
8
|
+
|
|
9
|
+
+ does not use TSX-files;
|
|
10
|
+
|
|
11
|
+
+ uses dynamic CSS rules and CSS selectors caching, does not generate Inline Styles;
|
|
12
|
+
|
|
13
|
+
+ is focused on updating each ui-component that is subscribed to changes of the RXObservable object.
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
1. Install vite and create vanilla-ts template project:
|
|
17
|
+
```cli
|
|
18
|
+
npm create vite@latest project-name -- --template vanilla-ts
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Update package.json:
|
|
22
|
+
```json
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"flinker": "^2.0.4",
|
|
25
|
+
"flinker-dom": "^1.0.4"
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. Install dependencies:
|
|
30
|
+
```cli
|
|
31
|
+
npm install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. Let's write our simple application:
|
|
35
|
+
```html
|
|
36
|
+
<!-- intex.html-->
|
|
37
|
+
<!DOCTYPE html>
|
|
38
|
+
<html lang="en">
|
|
39
|
+
<head>
|
|
40
|
+
...
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div id="root"></div>
|
|
44
|
+
<script type="module" src="/src/index.ts"></script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// index.ts
|
|
51
|
+
import './index.css'
|
|
52
|
+
import { App } from './App'
|
|
53
|
+
|
|
54
|
+
const app = App()
|
|
55
|
+
document.getElementById('root')!.appendChild(app.dom)
|
|
56
|
+
|
|
57
|
+
// App.ts
|
|
58
|
+
import { p } from 'flinker-dom'
|
|
59
|
+
|
|
60
|
+
export const App = () => {
|
|
61
|
+
return p().react(s => s.text = 'Hello, Flinker!')
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example 1: Counter
|
|
66
|
+
```ts
|
|
67
|
+
import { RXObservableValue } from 'flinker'
|
|
68
|
+
import { p, div, btn } from 'flinker-dom'
|
|
69
|
+
|
|
70
|
+
const Counter = () => {
|
|
71
|
+
// The function Counter will not be re-called by renderings.
|
|
72
|
+
// Therefore we can declare any functions and states
|
|
73
|
+
// in the Counter() body.
|
|
74
|
+
const rx = new RXObservableValue(0)
|
|
75
|
+
|
|
76
|
+
return div().children(() => {
|
|
77
|
+
p()
|
|
78
|
+
.observe(rx) // subscription to RXObservable object
|
|
79
|
+
.react(s => {
|
|
80
|
+
// react function will be called
|
|
81
|
+
// after the state (rx) changes
|
|
82
|
+
s.text = 'Count: ' + rx.value
|
|
83
|
+
s.textColor = '#222222'
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// btn will not be re-rendered,
|
|
87
|
+
// because it is not subscribed to external state
|
|
88
|
+
btn()
|
|
89
|
+
.react(s => {
|
|
90
|
+
s.text = 'Inc'
|
|
91
|
+
s.textColor = '#ffFFff'
|
|
92
|
+
s.bgColor = '#222222'
|
|
93
|
+
s.cornerRadius = '4px'
|
|
94
|
+
s.padding = '10px'
|
|
95
|
+
})
|
|
96
|
+
.whenHovered(s => {
|
|
97
|
+
s.bgColor = '#444444'
|
|
98
|
+
})
|
|
99
|
+
.onClick(() => rx.value++)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
As a rule, we do not use attributes of functional components to specify properties. This example is incorrect:
|
|
105
|
+
```ts
|
|
106
|
+
const Component = (props: { text: string, textColor: string }) => {
|
|
107
|
+
return p().react(s => {
|
|
108
|
+
s.text = props.text
|
|
109
|
+
s.textColor = props.textColor
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
But we can use observable objects (RXObservable):
|
|
115
|
+
```ts
|
|
116
|
+
const Counter = (rx: RXObservableValue<number>) => {
|
|
117
|
+
return p()
|
|
118
|
+
.observe(rx)
|
|
119
|
+
.react(s => ... )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const $state = new RXObservableValue(0)
|
|
123
|
+
Counter($state)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Example 2: Inheritance
|
|
127
|
+
In order not to duplicate the style of our buttons, we must describe the style once with the ability to specify only the text prop and add handlers later.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// Buttons.ts
|
|
131
|
+
export const ToggleBtn = ($isSelected: RXObservableValue<boolean>) => {
|
|
132
|
+
return btn()
|
|
133
|
+
.observe($isSelected)
|
|
134
|
+
.react(s => {
|
|
135
|
+
s.isSelected = $isSelected.value
|
|
136
|
+
s.textColor = '#ffFFff'
|
|
137
|
+
s.bgColor = '#222222'
|
|
138
|
+
s.cornerRadius = '5px'
|
|
139
|
+
s.padding = '10px'
|
|
140
|
+
})
|
|
141
|
+
.whenHovered(s => {
|
|
142
|
+
s.textColor = '#cc2222'
|
|
143
|
+
})
|
|
144
|
+
.whenSelected(s => {
|
|
145
|
+
s.bgColor = '#cc2222'
|
|
146
|
+
})
|
|
147
|
+
.onClick(() => {
|
|
148
|
+
$isSelected.value = !$isSelected.value
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Settings.ts
|
|
153
|
+
export class Settings {
|
|
154
|
+
readonly $rememberMe = new RXObservableValue(false)
|
|
155
|
+
|
|
156
|
+
constructor() {
|
|
157
|
+
this.$rememberMe.pipe()
|
|
158
|
+
.skipFirst() // ignore default false value
|
|
159
|
+
.onReceive(_ => {
|
|
160
|
+
this.storeSettings()
|
|
161
|
+
})
|
|
162
|
+
.subscribe()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private storeSettings() {
|
|
166
|
+
...
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// App.ts
|
|
171
|
+
const settings = new Settings()
|
|
172
|
+
|
|
173
|
+
const SettingsView = () => {
|
|
174
|
+
return vstack().children(() => {
|
|
175
|
+
ToggleBtn(settings.$rememberMe)
|
|
176
|
+
.react(s => s.text = 'Remember me')
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Example 3: Custom component
|
|
182
|
+
Let's create a button that has an icon and label.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// Buttons.ts
|
|
186
|
+
import { btn, ButtonProps } from 'flinker-dom'
|
|
187
|
+
|
|
188
|
+
export interface IconBtnProps extends ButtonProps {
|
|
189
|
+
icon?: MaterialIcon
|
|
190
|
+
iconSize?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const IconBtn = () => {
|
|
194
|
+
const $sharedState = new RXObservableValue<IconBtnProps>({})
|
|
195
|
+
return btn<IconBtnProps>()
|
|
196
|
+
// using propsDidChange handler,
|
|
197
|
+
// we can share btn-state to its children-components
|
|
198
|
+
.propsDidChange(props => $sharedState.value = props)
|
|
199
|
+
.react(s => {
|
|
200
|
+
s.display = 'flex'
|
|
201
|
+
s.flexDirection = 'row'
|
|
202
|
+
s.alignItems = 'center'
|
|
203
|
+
s.justifyContent = 'center'
|
|
204
|
+
s.gap = '5px'
|
|
205
|
+
s.wrap = false
|
|
206
|
+
s.boxSizing = 'border-box'
|
|
207
|
+
})
|
|
208
|
+
.children(() => {
|
|
209
|
+
|
|
210
|
+
//icon
|
|
211
|
+
$sharedState.value.icon && Icon()
|
|
212
|
+
.observe($sharedState) // subscription to the sharedState
|
|
213
|
+
.react(s => {
|
|
214
|
+
const ss = $sharedState.value
|
|
215
|
+
if (ss.icon) s.value = ss.icon
|
|
216
|
+
if (ss.iconSize) s.fontSize = ss.iconSize
|
|
217
|
+
s.textColor = 'inherit'
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
//label
|
|
221
|
+
$sharedState.value.text && span()
|
|
222
|
+
.observe($sharedState)
|
|
223
|
+
.react(s => {
|
|
224
|
+
const ss = $sharedState.value
|
|
225
|
+
s.text = ss.text
|
|
226
|
+
s.textColor = 'inherit'
|
|
227
|
+
s.fontSize = 'inherit'
|
|
228
|
+
s.fontFamily = 'inherit'
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
In the example above we have used Icon as MaterialIcon:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// Icons.ts
|
|
238
|
+
export interface IconProps extends TextProps {
|
|
239
|
+
value?: MaterialIcon
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const Icon = <P extends IconProps>() => {
|
|
243
|
+
return span<P>()
|
|
244
|
+
.react(s => {
|
|
245
|
+
s.value = MaterialIcon.question_mark // default icon
|
|
246
|
+
s.className = 'material_icon'
|
|
247
|
+
s.textSelectable = false
|
|
248
|
+
})
|
|
249
|
+
.map(s => s.text = s.value) // is called after react-functions
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export enum MaterialIcon {
|
|
253
|
+
av_timer = 'av_timer',
|
|
254
|
+
autorenew = 'autorenew',
|
|
255
|
+
autofps_select = 'autofps_select',
|
|
256
|
+
auto_stories = 'auto_stories',
|
|
257
|
+
...
|
|
258
|
+
zoom_out_map = 'zoom_out_map',
|
|
259
|
+
zoom_out = 'zoom_out',
|
|
260
|
+
zoom_in_map = 'zoom_in_map',
|
|
261
|
+
zoom_in = 'zoom_in',
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// index.css
|
|
265
|
+
@font-face {
|
|
266
|
+
font-family: 'MaterialIcons';
|
|
267
|
+
font-style: normal;
|
|
268
|
+
font-weight: 400;
|
|
269
|
+
src: url('resources/fonts/MaterialIcons.ttf') format('truetype');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.material_icon {
|
|
273
|
+
font-family: 'MaterialIcons';
|
|
274
|
+
font-weight: normal;
|
|
275
|
+
font-style: normal;
|
|
276
|
+
font-size: 24px; /* Preferred icon size */
|
|
277
|
+
display: inline-block;
|
|
278
|
+
line-height: 1;
|
|
279
|
+
text-transform: none;
|
|
280
|
+
letter-spacing: normal;
|
|
281
|
+
word-wrap: normal;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
direction: ltr;
|
|
284
|
+
|
|
285
|
+
/* Support for all WebKit browsers. */
|
|
286
|
+
-webkit-font-smoothing: antialiased;
|
|
287
|
+
/* Support for Safari and Chrome. */
|
|
288
|
+
text-rendering: optimizeLegibility;
|
|
289
|
+
|
|
290
|
+
/* Support for Firefox. */
|
|
291
|
+
-moz-osx-font-smoothing: grayscale;
|
|
292
|
+
|
|
293
|
+
/* Support for IE. */
|
|
294
|
+
-webkit-font-feature-settings: 'liga';
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
As a result:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// App.ts
|
|
302
|
+
IconBtn()
|
|
303
|
+
.react(s => {
|
|
304
|
+
s.icon = MaterialIcon.add
|
|
305
|
+
s.text = 'Btn with icon'
|
|
306
|
+
s.textColor = '#ffFFff'
|
|
307
|
+
s.bgColor = '#111111'
|
|
308
|
+
s.cornerRadius = '5px'
|
|
309
|
+
s.padding = '10px'
|
|
310
|
+
})
|
|
311
|
+
.whenHovered(s => {
|
|
312
|
+
s.textColor = '#cc2222'
|
|
313
|
+
s.bgColor = '#222222'
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Example 4: List
|
|
318
|
+
Lists manages re-rendering of its components. If we add to the end of the list a new component, the previous ones will not be re-created or re-rendered.
|
|
319
|
+
|
|
320
|
+
Let's create a simple ToDo App.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
// Model.ts
|
|
324
|
+
export interface Task {
|
|
325
|
+
id: number
|
|
326
|
+
text: string
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export class ToDoModel {
|
|
330
|
+
readonly $tasks = new RXSubject<Task[], never>([])
|
|
331
|
+
|
|
332
|
+
private lastTaskId = 0
|
|
333
|
+
createTask(text: string) {
|
|
334
|
+
this.$tasks.value.push({ id: this.lastTaskId++, text })
|
|
335
|
+
this.$tasks.resend()
|
|
336
|
+
// using resend-method, all subscribers to the $tasks
|
|
337
|
+
// will be notified even if the $tasks.value remains the same.
|
|
338
|
+
// Therefore we are using RXSubject instead of RXObservableValue
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Our view contains a list of tasks:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
// App.ts
|
|
347
|
+
const model = new ToDoModel()
|
|
348
|
+
|
|
349
|
+
const TodoList = () => {
|
|
350
|
+
return vstack().children(() => {
|
|
351
|
+
vlist<Task>()
|
|
352
|
+
.observe(model.$tasks)
|
|
353
|
+
.items(() => model.$tasks.value) // will be re-called if model.$tasks changes
|
|
354
|
+
.itemRenderer(TaskView)
|
|
355
|
+
|
|
356
|
+
btn()
|
|
357
|
+
.react(s => {
|
|
358
|
+
s.bgColor = '#222222'
|
|
359
|
+
s.padding = '10px'
|
|
360
|
+
s.cornerRadius = '4px'
|
|
361
|
+
s.text = '+ New Task'
|
|
362
|
+
})
|
|
363
|
+
.onClick(() => {
|
|
364
|
+
model.createTask('New Task')
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const TaskView = (t: Task) => {
|
|
370
|
+
return p()
|
|
371
|
+
.react(s => s.text = t.text)
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
When model.$tasks changes vlist call items function to get tasks. Then vlist compares two lists of the tasks before and after changes. If different items are found for the same index, the previous component will be removed from the dom-tree and the new one will be added. By default, strict equality (===) is used to compare two elements. We can override this behavior, using equals method:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
vlist<Task>()
|
|
379
|
+
.observe(model.$tasks)
|
|
380
|
+
.items(() => model.$tasks.value)
|
|
381
|
+
.equals((a, b) => a.id === b.id)
|
|
382
|
+
.itemRenderer(TaskView)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Vlist can be stylized as vstack:
|
|
386
|
+
```ts
|
|
387
|
+
vlist<Task>()
|
|
388
|
+
.observe(model.$tasks)
|
|
389
|
+
.items(() => model.$tasks.value)
|
|
390
|
+
.itemRenderer(TaskView)
|
|
391
|
+
.react(s => {
|
|
392
|
+
s.width = '100%'
|
|
393
|
+
s.halign = 'left'
|
|
394
|
+
s.valign = 'center'
|
|
395
|
+
s.gap = '10px'
|
|
396
|
+
s.padding = '20px'
|
|
397
|
+
})
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Example 5: Affects
|
|
401
|
+
By observing changes we can clearly specify what reactions (ObserveAffect) should follow. We have three types of affects:
|
|
402
|
+
|
|
403
|
+
+ __affectsProps__ (default) — only styles and props of the component will be updated, that has called an `observe`-method;
|
|
404
|
+
+ __affectsChildrenProps__ — styles and props of the component's children will be updated including their children;
|
|
405
|
+
+ __recreateChildren__ — old children will be removed from the dom-tree, and new ones will be added.
|
|
406
|
+
|
|
407
|
+
### affectsChildrenProps case
|
|
408
|
+
There are states of change that affect only the properties and styles of nested components, not the structure. In this case, to avoid having to subscribe to changes in each child component, you can subscribe only in the parent one, specifying the affectsChildrenProps-affect. The application theme can act as such a state.
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
export const App = () => {
|
|
412
|
+
const $theme = globalContext().app.$theme
|
|
413
|
+
|
|
414
|
+
return HomeView()
|
|
415
|
+
.observe($theme, 'affectsProps', 'affectsChildrenProps')
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Components support multiple observing, so we could write it like this:
|
|
420
|
+
```ts
|
|
421
|
+
HomeView()
|
|
422
|
+
.observe($theme, 'affectsProps')
|
|
423
|
+
.observe($theme, 'affectsChildrenProps')
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### recreateChildren case
|
|
427
|
+
Let's imagine that the user selects a document to view. Depending on the document, we may have different components structure. Therefore, we have to recreate the child components entirely.
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const DocView = () => {
|
|
431
|
+
const ctx = docContext()
|
|
432
|
+
|
|
433
|
+
return vstack()
|
|
434
|
+
.observe(ctx.$selectedDoc, 'recreateChildren')
|
|
435
|
+
.react(s => {
|
|
436
|
+
s.textColor = theme().text
|
|
437
|
+
s.gap = '0'
|
|
438
|
+
s.valign = 'top'
|
|
439
|
+
s.halign = 'left'
|
|
440
|
+
s.paddingVertical = '40px'
|
|
441
|
+
s.width = '100%'
|
|
442
|
+
}).children(() => {
|
|
443
|
+
// we always get an actual doc hier,
|
|
444
|
+
// since the children() method will be called
|
|
445
|
+
// every time $selectedDoc changes
|
|
446
|
+
const doc = ctx.$selectedDoc.value
|
|
447
|
+
|
|
448
|
+
DocInfo(doc)
|
|
449
|
+
DocHeader(doc)
|
|
450
|
+
DocBody(doc)
|
|
451
|
+
|
|
452
|
+
doc.isEditing && ToolBar(doc)
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
If selectedDoc can be undefined, then we usually use the `observer` component:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
const DocView = ($doc: RXObservableValue<Doc | undefined>) => {
|
|
461
|
+
return observer($doc).onReceive(doc => {
|
|
462
|
+
return doc && vstack()
|
|
463
|
+
.react(s => ...)
|
|
464
|
+
.children(() => {
|
|
465
|
+
|
|
466
|
+
DocInfo(doc)
|
|
467
|
+
DocHeader(doc)
|
|
468
|
+
DocBody(doc)
|
|
469
|
+
|
|
470
|
+
doc.isEditing && ToolBar(doc)
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Example 6: Input
|
|
477
|
+
input and textarea components use binding mechanism for bidirectional text updating:
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const TextInput = ($input: RXObservableValue<string>) => {
|
|
481
|
+
return input()
|
|
482
|
+
.bind($input)
|
|
483
|
+
.react(s => {
|
|
484
|
+
// react will not be re-called if $input changes
|
|
485
|
+
s.type = 'text'
|
|
486
|
+
s.width = '100%'
|
|
487
|
+
s.height = '40px'
|
|
488
|
+
s.fontSize = theme().defFontSize
|
|
489
|
+
s.textColor = theme().text
|
|
490
|
+
s.bgColor = theme().inputBg
|
|
491
|
+
s.padding = '10px'
|
|
492
|
+
s.autoCorrect = 'off'
|
|
493
|
+
s.autoComplete = 'off'
|
|
494
|
+
s.borderBottom = '1px solid ' + theme().violet
|
|
495
|
+
})
|
|
496
|
+
.whenFocused(s => {
|
|
497
|
+
s.borderBottom = '1px solid ' + theme().red
|
|
498
|
+
})
|
|
499
|
+
.whenPlaceholderShown(s => {
|
|
500
|
+
s.textColor = '#666666'
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
How is binding implemented?
|
|
506
|
+
```ts
|
|
507
|
+
// FlinkerDom/src/components.ts
|
|
508
|
+
export class Input<P extends InputProps> extends UIComponent<P> {
|
|
509
|
+
bind(rx: RXObservableValue<string>) {
|
|
510
|
+
this.unsubscribeColl.push(
|
|
511
|
+
rx.pipe()
|
|
512
|
+
.onReceive(v => (this.dom as HTMLTextAreaElement).value = v)
|
|
513
|
+
.subscribe()
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
this.onInput((e: any) => rx.value = e.target.value)
|
|
517
|
+
return this
|
|
518
|
+
}
|
|
519
|
+
...
|
|
520
|
+
|
|
521
|
+
onInput(callback: (event: Event) => void) {
|
|
522
|
+
this.dom.addEventListener('input', callback)
|
|
523
|
+
return this
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export const input = <P extends InputProps>(type: InputType = 'text') => {
|
|
528
|
+
return new Input<P>('input').react(s => s.type = type)
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## List of standard components (v.1.0):
|
|
533
|
+
+ div
|
|
534
|
+
+ p
|
|
535
|
+
+ span
|
|
536
|
+
+ h1
|
|
537
|
+
+ h2
|
|
538
|
+
+ h3
|
|
539
|
+
+ h4
|
|
540
|
+
+ h5
|
|
541
|
+
+ h6
|
|
542
|
+
+ btn
|
|
543
|
+
+ link (a)
|
|
544
|
+
+ switcher (div)
|
|
545
|
+
+ observer (p hidden)
|
|
546
|
+
+ vstack (div)
|
|
547
|
+
+ hstack (div)
|
|
548
|
+
+ vlist (div)
|
|
549
|
+
+ hlist (div)
|
|
550
|
+
+ spacer (div)
|
|
551
|
+
+ image
|
|
552
|
+
+ input
|
|
553
|
+
+ textarea
|
|
554
|
+
|
|
555
|
+
## Install
|
|
2
556
|
```cli
|
|
3
557
|
npm i flinker-dom
|
|
4
558
|
```
|
|
5
559
|
|
|
6
|
-
|
|
560
|
+
## License
|
|
7
561
|
MIT
|