elit 3.0.1 → 3.0.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/build.d.ts +4 -12
- package/dist/build.d.ts.map +1 -0
- package/dist/chokidar.d.ts +7 -9
- package/dist/chokidar.d.ts.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +250 -21
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/dom.d.ts +7 -14
- package/dist/dom.d.ts.map +1 -0
- package/dist/el.d.ts +19 -191
- package/dist/el.d.ts.map +1 -0
- package/dist/fs.d.ts +35 -35
- package/dist/fs.d.ts.map +1 -0
- package/dist/hmr.d.ts +3 -3
- package/dist/hmr.d.ts.map +1 -0
- package/dist/http.d.ts +20 -22
- package/dist/http.d.ts.map +1 -0
- package/dist/https.d.ts +12 -15
- package/dist/https.d.ts.map +1 -0
- package/dist/index.d.ts +10 -629
- package/dist/index.d.ts.map +1 -0
- package/dist/mime-types.d.ts +9 -9
- package/dist/mime-types.d.ts.map +1 -0
- package/dist/path.d.ts +22 -19
- package/dist/path.d.ts.map +1 -0
- package/dist/router.d.ts +10 -17
- package/dist/router.d.ts.map +1 -0
- package/dist/runtime.d.ts +5 -6
- package/dist/runtime.d.ts.map +1 -0
- package/dist/server.d.ts +109 -7
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +712 -137
- package/dist/server.mjs +711 -137
- package/dist/state.d.ts +21 -27
- package/dist/state.d.ts.map +1 -0
- package/dist/style.d.ts +14 -55
- package/dist/style.d.ts.map +1 -0
- package/dist/types.d.ts +26 -240
- package/dist/types.d.ts.map +1 -0
- package/dist/ws.d.ts +14 -17
- package/dist/ws.d.ts.map +1 -0
- package/dist/wss.d.ts +16 -16
- package/dist/wss.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/build.ts +337 -0
- package/src/chokidar.ts +401 -0
- package/src/cli.ts +638 -0
- package/src/config.ts +205 -0
- package/src/dom.ts +817 -0
- package/src/el.ts +164 -0
- package/src/fs.ts +727 -0
- package/src/hmr.ts +137 -0
- package/src/http.ts +775 -0
- package/src/https.ts +411 -0
- package/src/index.ts +14 -0
- package/src/mime-types.ts +222 -0
- package/src/path.ts +493 -0
- package/src/router.ts +237 -0
- package/src/runtime.ts +97 -0
- package/src/server.ts +1593 -0
- package/src/state.ts +468 -0
- package/src/style.ts +524 -0
- package/{dist/types-Du6kfwTm.d.ts → src/types.ts} +58 -141
- package/src/ws.ts +506 -0
- package/src/wss.ts +241 -0
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -207
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -163
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -629
- package/dist/mime-types.d.mts +0 -48
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -47
- package/dist/runtime.d.mts +0 -97
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -111
- package/dist/style.d.mts +0 -159
- package/dist/types-C0nGi6MX.d.mts +0 -346
- package/dist/types.d.mts +0 -452
- package/dist/ws.d.mts +0 -195
- package/dist/wss.d.mts +0 -108
package/src/state.ts
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Elit - State Management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { State, StateOptions, VNode, VirtualListController, Child, Props } from './types';
|
|
6
|
+
import { dom } from './dom';
|
|
7
|
+
|
|
8
|
+
// State management helpers
|
|
9
|
+
export const createState = <T>(initial: T, options?: StateOptions): State<T> =>
|
|
10
|
+
dom.createState(initial, options);
|
|
11
|
+
|
|
12
|
+
export const computed = <T extends any[], R>(
|
|
13
|
+
states: { [K in keyof T]: State<T[K]> },
|
|
14
|
+
fn: (...values: T) => R
|
|
15
|
+
): State<R> => dom.computed(states, fn);
|
|
16
|
+
|
|
17
|
+
export const effect = (fn: () => void): void => dom.effect(fn);
|
|
18
|
+
|
|
19
|
+
// Performance helpers
|
|
20
|
+
export const batchRender = (container: string | HTMLElement, vNodes: VNode[]): HTMLElement =>
|
|
21
|
+
dom.batchRender(container, vNodes);
|
|
22
|
+
|
|
23
|
+
export const renderChunked = (
|
|
24
|
+
container: string | HTMLElement,
|
|
25
|
+
vNodes: VNode[],
|
|
26
|
+
chunkSize?: number,
|
|
27
|
+
onProgress?: (current: number, total: number) => void
|
|
28
|
+
): HTMLElement => dom.renderChunked(container, vNodes, chunkSize, onProgress);
|
|
29
|
+
|
|
30
|
+
export const createVirtualList = <T>(
|
|
31
|
+
container: HTMLElement,
|
|
32
|
+
items: T[],
|
|
33
|
+
renderItem: (item: T, index: number) => VNode,
|
|
34
|
+
itemHeight?: number,
|
|
35
|
+
bufferSize?: number
|
|
36
|
+
): VirtualListController => dom.createVirtualList(container, items, renderItem, itemHeight, bufferSize);
|
|
37
|
+
|
|
38
|
+
export const lazy = <T extends any[], R>(loadFn: () => Promise<(...args: T) => R>) =>
|
|
39
|
+
dom.lazy(loadFn);
|
|
40
|
+
|
|
41
|
+
export const cleanupUnused = (root: HTMLElement): number =>
|
|
42
|
+
dom.cleanupUnusedElements(root);
|
|
43
|
+
|
|
44
|
+
// Throttle helper
|
|
45
|
+
export const throttle = <T extends any[]>(fn: (...args: T) => void, delay: number) => {
|
|
46
|
+
let timer: NodeJS.Timeout | null = null;
|
|
47
|
+
return (...args: T) => {
|
|
48
|
+
if (!timer) {
|
|
49
|
+
timer = setTimeout(() => {
|
|
50
|
+
timer = null;
|
|
51
|
+
fn(...args);
|
|
52
|
+
}, delay);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Debounce helper
|
|
58
|
+
export const debounce = <T extends any[]>(fn: (...args: T) => void, delay: number) => {
|
|
59
|
+
let timer: NodeJS.Timeout | null = null;
|
|
60
|
+
return (...args: T) => {
|
|
61
|
+
timer && clearTimeout(timer);
|
|
62
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ===== Shared State - syncs with elit-server =====
|
|
67
|
+
|
|
68
|
+
type StateChangeCallback<T = any> = (value: T, oldValue: T) => void;
|
|
69
|
+
|
|
70
|
+
interface StateMessage {
|
|
71
|
+
type: 'state:init' | 'state:update' | 'state:subscribe' | 'state:unsubscribe' | 'state:change';
|
|
72
|
+
key: string;
|
|
73
|
+
value?: any;
|
|
74
|
+
timestamp?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Shared State - syncs with elit-server
|
|
79
|
+
*/
|
|
80
|
+
export class SharedState<T = any> {
|
|
81
|
+
private localState: State<T>;
|
|
82
|
+
private ws: WebSocket | null = null;
|
|
83
|
+
private pendingUpdates: T[] = [];
|
|
84
|
+
private previousValue: T;
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
public readonly key: string,
|
|
88
|
+
defaultValue: T,
|
|
89
|
+
private wsUrl?: string
|
|
90
|
+
) {
|
|
91
|
+
this.localState = createState(defaultValue);
|
|
92
|
+
this.previousValue = defaultValue;
|
|
93
|
+
this.connect();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get current value
|
|
98
|
+
*/
|
|
99
|
+
get value(): T {
|
|
100
|
+
return this.localState.value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set new value and sync to server
|
|
105
|
+
*/
|
|
106
|
+
set value(newValue: T) {
|
|
107
|
+
this.previousValue = this.localState.value;
|
|
108
|
+
this.localState.value = newValue;
|
|
109
|
+
this.sendToServer(newValue);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the underlying Elit State (for reactive binding)
|
|
114
|
+
*/
|
|
115
|
+
get state(): State<T> {
|
|
116
|
+
return this.localState;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Subscribe to changes (returns Elit State for reactive)
|
|
121
|
+
*/
|
|
122
|
+
onChange(callback: StateChangeCallback<T>): () => void {
|
|
123
|
+
return this.localState.subscribe((newValue) => {
|
|
124
|
+
const oldValue = this.previousValue;
|
|
125
|
+
this.previousValue = newValue;
|
|
126
|
+
callback(newValue, oldValue);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update value using a function
|
|
132
|
+
*/
|
|
133
|
+
update(updater: (current: T) => T): void {
|
|
134
|
+
this.value = updater(this.value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Connect to WebSocket
|
|
139
|
+
*/
|
|
140
|
+
private connect(): void {
|
|
141
|
+
if (typeof window === 'undefined') return;
|
|
142
|
+
|
|
143
|
+
const url = this.wsUrl || `ws://${location.host}`;
|
|
144
|
+
this.ws = new WebSocket(url);
|
|
145
|
+
|
|
146
|
+
this.ws.addEventListener('open', () => {
|
|
147
|
+
this.subscribe();
|
|
148
|
+
|
|
149
|
+
// Send pending updates
|
|
150
|
+
while (this.pendingUpdates.length > 0) {
|
|
151
|
+
const value = this.pendingUpdates.shift();
|
|
152
|
+
this.sendToServer(value!);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.ws.addEventListener('message', (event) => {
|
|
157
|
+
this.handleMessage(event.data);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
this.ws.addEventListener('close', () => {
|
|
161
|
+
// Reconnect after delay
|
|
162
|
+
setTimeout(() => this.connect(), 1000);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this.ws.addEventListener('error', (error) => {
|
|
166
|
+
console.error('[SharedState] WebSocket error:', error);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Subscribe to server state
|
|
172
|
+
*/
|
|
173
|
+
private subscribe(): void {
|
|
174
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
175
|
+
|
|
176
|
+
this.ws.send(JSON.stringify({
|
|
177
|
+
type: 'state:subscribe',
|
|
178
|
+
key: this.key
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Handle message from server
|
|
184
|
+
*/
|
|
185
|
+
private handleMessage(data: string): void {
|
|
186
|
+
try {
|
|
187
|
+
const msg = JSON.parse(data) as StateMessage;
|
|
188
|
+
|
|
189
|
+
if (msg.key !== this.key) return;
|
|
190
|
+
|
|
191
|
+
if (msg.type === 'state:init' || msg.type === 'state:update') {
|
|
192
|
+
// Update local state without sending back to server
|
|
193
|
+
this.localState.value = msg.value;
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
// Ignore parse errors (could be HMR messages)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Send value to server
|
|
202
|
+
*/
|
|
203
|
+
private sendToServer(value: T): void {
|
|
204
|
+
if (!this.ws) return;
|
|
205
|
+
|
|
206
|
+
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
207
|
+
// Queue update for when connection is ready
|
|
208
|
+
this.pendingUpdates.push(value);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.ws.send(JSON.stringify({
|
|
213
|
+
type: 'state:change',
|
|
214
|
+
key: this.key,
|
|
215
|
+
value
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Disconnect
|
|
221
|
+
*/
|
|
222
|
+
disconnect(): void {
|
|
223
|
+
if (this.ws) {
|
|
224
|
+
this.ws.close();
|
|
225
|
+
this.ws = null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Destroy state and cleanup
|
|
231
|
+
*/
|
|
232
|
+
destroy(): void {
|
|
233
|
+
this.disconnect();
|
|
234
|
+
this.localState.destroy();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a shared state that syncs with elit-server
|
|
240
|
+
*/
|
|
241
|
+
export function createSharedState<T>(
|
|
242
|
+
key: string,
|
|
243
|
+
defaultValue: T,
|
|
244
|
+
wsUrl?: string
|
|
245
|
+
): SharedState<T> {
|
|
246
|
+
return new SharedState(key, defaultValue, wsUrl);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Shared State Manager for managing multiple shared states
|
|
251
|
+
*/
|
|
252
|
+
class SharedStateManager {
|
|
253
|
+
private states = new Map<string, SharedState<any>>();
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create or get a shared state
|
|
257
|
+
*/
|
|
258
|
+
create<T>(key: string, defaultValue: T, wsUrl?: string): SharedState<T> {
|
|
259
|
+
if (this.states.has(key)) {
|
|
260
|
+
return this.states.get(key) as SharedState<T>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const state = new SharedState<T>(key, defaultValue, wsUrl);
|
|
264
|
+
this.states.set(key, state);
|
|
265
|
+
return state;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get existing state
|
|
270
|
+
*/
|
|
271
|
+
get<T>(key: string): SharedState<T> | undefined {
|
|
272
|
+
return this.states.get(key) as SharedState<T>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Delete a state
|
|
277
|
+
*/
|
|
278
|
+
delete(key: string): boolean {
|
|
279
|
+
const state = this.states.get(key);
|
|
280
|
+
if (state) {
|
|
281
|
+
state.destroy();
|
|
282
|
+
return this.states.delete(key);
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Clear all states
|
|
289
|
+
*/
|
|
290
|
+
clear(): void {
|
|
291
|
+
this.states.forEach(state => state.destroy());
|
|
292
|
+
this.states.clear();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Export singleton instance
|
|
297
|
+
export const sharedStateManager = new SharedStateManager();
|
|
298
|
+
|
|
299
|
+
// ===== Reactive Rendering Helpers =====
|
|
300
|
+
|
|
301
|
+
// Helper function to schedule RAF updates (reused in reactive and reactiveAs)
|
|
302
|
+
const scheduleRAFUpdate = (rafId: number | null, updateFn: () => void): number => {
|
|
303
|
+
rafId && cancelAnimationFrame(rafId);
|
|
304
|
+
return requestAnimationFrame(() => {
|
|
305
|
+
updateFn();
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Helper function to render content to fragment (reused in reactive and reactiveAs)
|
|
310
|
+
const renderToFragment = (content: VNode | Child, isVNode?: boolean): DocumentFragment => {
|
|
311
|
+
const fragment = document.createDocumentFragment();
|
|
312
|
+
|
|
313
|
+
if (isVNode && content && typeof content === 'object' && 'tagName' in content) {
|
|
314
|
+
const { children } = content as VNode;
|
|
315
|
+
for (const child of children) {
|
|
316
|
+
dom.renderToDOM(child, fragment);
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
dom.renderToDOM(content, fragment);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return fragment;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Helper function to update element props (reused in reactive)
|
|
326
|
+
const updateElementProps = (element: HTMLElement | SVGElement, props: Props): void => {
|
|
327
|
+
for (const key in props) {
|
|
328
|
+
const value = props[key];
|
|
329
|
+
if (key === 'ref') continue;
|
|
330
|
+
|
|
331
|
+
if (key === 'class' || key === 'className') {
|
|
332
|
+
(element as HTMLElement).className = Array.isArray(value) ? value.join(' ') : (value || '');
|
|
333
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
334
|
+
const s = (element as HTMLElement).style;
|
|
335
|
+
for (const k in value) (s as any)[k] = value[k];
|
|
336
|
+
} else if (key.startsWith('on')) {
|
|
337
|
+
(element as any)[key.toLowerCase()] = value;
|
|
338
|
+
} else if (value != null && value !== false) {
|
|
339
|
+
element.setAttribute(key, String(value === true ? '' : value));
|
|
340
|
+
} else {
|
|
341
|
+
element.removeAttribute(key);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Reactive element helpers
|
|
347
|
+
export const reactive = <T>(state: State<T>, renderFn: (value: T) => VNode | Child): VNode => {
|
|
348
|
+
let rafId: number | null = null;
|
|
349
|
+
let elementRef: HTMLElement | SVGElement | null = null;
|
|
350
|
+
let placeholder: Comment | null = null;
|
|
351
|
+
let isInDOM = true;
|
|
352
|
+
|
|
353
|
+
const initialResult = renderFn(state.value);
|
|
354
|
+
const isVNodeResult = initialResult && typeof initialResult === 'object' && 'tagName' in initialResult;
|
|
355
|
+
const initialIsNull = initialResult == null || initialResult === false;
|
|
356
|
+
|
|
357
|
+
const updateElement = () => {
|
|
358
|
+
if (!elementRef && !placeholder) return;
|
|
359
|
+
|
|
360
|
+
const newResult = renderFn(state.value);
|
|
361
|
+
const resultIsNull = newResult == null || newResult === false;
|
|
362
|
+
|
|
363
|
+
if (resultIsNull) {
|
|
364
|
+
if (isInDOM && elementRef) {
|
|
365
|
+
placeholder = document.createComment('reactive');
|
|
366
|
+
elementRef.parentNode?.replaceChild(placeholder, elementRef);
|
|
367
|
+
isInDOM = false;
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
if (!isInDOM && placeholder && elementRef) {
|
|
371
|
+
placeholder.parentNode?.replaceChild(elementRef, placeholder);
|
|
372
|
+
placeholder = null;
|
|
373
|
+
isInDOM = true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (elementRef) {
|
|
377
|
+
const isCurrentVNode = !!(isVNodeResult && newResult && typeof newResult === 'object' && 'tagName' in newResult);
|
|
378
|
+
if (isCurrentVNode) {
|
|
379
|
+
const { props } = newResult as VNode;
|
|
380
|
+
updateElementProps(elementRef, props);
|
|
381
|
+
}
|
|
382
|
+
const fragment = renderToFragment(newResult, isCurrentVNode);
|
|
383
|
+
elementRef.textContent = '';
|
|
384
|
+
elementRef.appendChild(fragment);
|
|
385
|
+
dom.getElementCache().set(elementRef, true);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
state.subscribe(() => {
|
|
391
|
+
rafId = scheduleRAFUpdate(rafId, () => {
|
|
392
|
+
updateElement();
|
|
393
|
+
rafId = null;
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const refCallback = (el: HTMLElement | SVGElement) => {
|
|
398
|
+
elementRef = el;
|
|
399
|
+
if (initialIsNull && el.parentNode) {
|
|
400
|
+
placeholder = document.createComment('reactive');
|
|
401
|
+
el.parentNode.replaceChild(placeholder, el);
|
|
402
|
+
isInDOM = false;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
if (isVNodeResult) {
|
|
407
|
+
const vnode = initialResult as VNode;
|
|
408
|
+
return {
|
|
409
|
+
tagName: vnode.tagName,
|
|
410
|
+
props: { ...vnode.props, ref: refCallback },
|
|
411
|
+
children: vnode.children
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return { tagName: 'span', props: { ref: refCallback }, children: [initialResult] };
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Reactive element with custom wrapper tag
|
|
419
|
+
export const reactiveAs = <T>(
|
|
420
|
+
tagName: string,
|
|
421
|
+
state: State<T>,
|
|
422
|
+
renderFn: (value: T) => VNode | Child,
|
|
423
|
+
props: Props = {}
|
|
424
|
+
): VNode => {
|
|
425
|
+
let rafId: number | null = null;
|
|
426
|
+
let elementRef: HTMLElement | SVGElement | null = null;
|
|
427
|
+
|
|
428
|
+
state.subscribe(() => {
|
|
429
|
+
rafId = scheduleRAFUpdate(rafId, () => {
|
|
430
|
+
if (elementRef) {
|
|
431
|
+
const newResult = renderFn(state.value);
|
|
432
|
+
|
|
433
|
+
if (newResult == null || newResult === false) {
|
|
434
|
+
(elementRef as HTMLElement).style.display = 'none';
|
|
435
|
+
elementRef.textContent = '';
|
|
436
|
+
} else {
|
|
437
|
+
(elementRef as HTMLElement).style.display = '';
|
|
438
|
+
const fragment = renderToFragment(newResult, false);
|
|
439
|
+
elementRef.textContent = '';
|
|
440
|
+
elementRef.appendChild(fragment);
|
|
441
|
+
}
|
|
442
|
+
dom.getElementCache().set(elementRef, true);
|
|
443
|
+
}
|
|
444
|
+
rafId = null;
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const refCallback = (el: HTMLElement | SVGElement) => {
|
|
449
|
+
elementRef = el;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
return { tagName, props: { ...props, ref: refCallback }, children: [renderFn(state.value)] };
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
export const text = (state: State<any> | any): VNode | string =>
|
|
456
|
+
(state && state.value !== undefined)
|
|
457
|
+
? reactive(state, v => ({ tagName: 'span', props: {}, children: [String(v)] }))
|
|
458
|
+
: String(state);
|
|
459
|
+
|
|
460
|
+
export const bindValue = <T extends string | number>(state: State<T>): Props => ({
|
|
461
|
+
value: state.value,
|
|
462
|
+
oninput: (e: Event) => { state.value = (e.target as HTMLInputElement).value as T; }
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
export const bindChecked = (state: State<boolean>): Props => ({
|
|
466
|
+
checked: state.value,
|
|
467
|
+
onchange: (e: Event) => { state.value = (e.target as HTMLInputElement).checked; }
|
|
468
|
+
});
|