@jay-framework/runtime 0.6.10 → 0.8.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/dist/index.d.ts +35 -4
- package/dist/index.js +170 -6
- package/docs/runtime.md +253 -5
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ interface updateFunc<T> {
|
|
|
26
26
|
_origUpdates?: Array<updateFunc<T>>;
|
|
27
27
|
}
|
|
28
28
|
type MountFunc = () => void;
|
|
29
|
+
declare const noop: () => void;
|
|
29
30
|
declare const noopUpdate: updateFunc<any>;
|
|
30
31
|
declare const noopMount: MountFunc;
|
|
31
32
|
interface BaseJayElement<ViewState> {
|
|
@@ -72,6 +73,15 @@ type JayContract<ViewState extends object, Refs extends object> = {
|
|
|
72
73
|
};
|
|
73
74
|
type ExtractViewState<A> = A extends JayContract<infer ViewState, any> ? ViewState : never;
|
|
74
75
|
type ExtractRefs<A> = A extends JayContract<any, infer Refs> ? Refs : never;
|
|
76
|
+
declare enum LogType {
|
|
77
|
+
ASYNC_ERROR = 0,
|
|
78
|
+
CONTEXT_NOT_FOUND = 1
|
|
79
|
+
}
|
|
80
|
+
type JayLog = {
|
|
81
|
+
log: (type: LogType) => void;
|
|
82
|
+
error: (type: LogType, error: Error) => void;
|
|
83
|
+
};
|
|
84
|
+
declare const jayLog: JayLog;
|
|
75
85
|
|
|
76
86
|
declare module '@jay-framework/runtime' {
|
|
77
87
|
interface BaseJayElement<ViewState> {
|
|
@@ -433,21 +443,41 @@ interface Conditional<ViewState> {
|
|
|
433
443
|
condition: (newData: ViewState) => boolean;
|
|
434
444
|
elem: () => BaseJayElement<ViewState> | TextElement<ViewState>;
|
|
435
445
|
}
|
|
436
|
-
declare
|
|
437
|
-
|
|
446
|
+
declare enum WhenRole {
|
|
447
|
+
pending = 0,
|
|
448
|
+
resolved = 1,
|
|
449
|
+
rejected = 2
|
|
450
|
+
}
|
|
451
|
+
interface When<ViewState, ResolvedViewValue> {
|
|
452
|
+
role: WhenRole;
|
|
453
|
+
promise: (newData: ViewState) => Promise<ResolvedViewValue>;
|
|
454
|
+
elem: () => BaseJayElement<ResolvedViewValue> | TextElement<ResolvedViewValue> | string;
|
|
455
|
+
}
|
|
456
|
+
declare const resolved: <ViewState, ResolvedViewValue>(promise: (newData: ViewState) => Promise<ResolvedViewValue>, elem: () => string | BaseJayElement<ResolvedViewValue> | TextElement<ResolvedViewValue>) => When<ViewState, ResolvedViewValue>;
|
|
457
|
+
declare const rejected: <ViewState>(promise: (newData: ViewState) => Promise<any>, elem: () => BaseJayElement<Error> | TextElement<Error> | string) => When<ViewState, any>;
|
|
458
|
+
declare const pending: <ViewState>(promise: (newData: ViewState) => Promise<any>, elem: () => BaseJayElement<never> | TextElement<never> | string) => When<ViewState, any>;
|
|
459
|
+
declare function isCondition<ViewState>(c: Conditional<ViewState> | When<ViewState, any> | ForEach<ViewState, any> | WithData<ViewState, any> | TextElement<ViewState> | BaseJayElement<ViewState>): c is Conditional<ViewState>;
|
|
460
|
+
declare function isForEach<ViewState, Item>(c: Conditional<ViewState> | When<ViewState, any> | ForEach<ViewState, Item> | WithData<ViewState, any> | TextElement<ViewState> | BaseJayElement<ViewState>): c is ForEach<ViewState, Item>;
|
|
461
|
+
declare function isWhen<ViewState, Item>(c: Conditional<ViewState> | When<ViewState, any> | ForEach<ViewState, Item> | WithData<ViewState, any> | TextElement<ViewState> | BaseJayElement<ViewState>): c is When<ViewState, any>;
|
|
462
|
+
declare function isWithData<ViewState, ChildViewState>(c: Conditional<ViewState> | When<ViewState, any> | ForEach<ViewState, any> | WithData<ViewState, ChildViewState> | TextElement<ViewState> | BaseJayElement<ViewState>): c is WithData<ViewState, ChildViewState>;
|
|
438
463
|
declare function forEach<T, Item>(getItems: (T: any) => Array<Item>, elemCreator: (Item: any) => BaseJayElement<Item>, matchBy: string): ForEach<T, Item>;
|
|
439
464
|
interface ForEach<ViewState, Item> {
|
|
440
465
|
getItems: (T: any) => Array<Item>;
|
|
441
466
|
elemCreator: (Item: any, String: any) => BaseJayElement<Item>;
|
|
442
467
|
trackBy: string;
|
|
443
468
|
}
|
|
469
|
+
declare function withData<ParentViewState, ChildViewState>(accessor: (data: ParentViewState) => ChildViewState | null | undefined, elem: () => BaseJayElement<ChildViewState>): WithData<ParentViewState, ChildViewState>;
|
|
470
|
+
interface WithData<ParentViewState, ChildViewState> {
|
|
471
|
+
accessor: (data: ParentViewState) => ChildViewState | null | undefined;
|
|
472
|
+
elem: () => BaseJayElement<ChildViewState>;
|
|
473
|
+
}
|
|
444
474
|
declare function mkUpdateCollection<ViewState, Item>(child: ForEach<ViewState, Item>, group: KindergartenGroup): [updateFunc<ViewState>, MountFunc, MountFunc];
|
|
445
475
|
declare function dynamicText<ViewState>(textContent: (vs: ViewState) => string | number | boolean): TextElement<ViewState>;
|
|
446
476
|
type ElementChildren<ViewState> = Array<BaseJayElement<ViewState> | TextElement<ViewState> | string>;
|
|
447
477
|
declare const element: <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: ElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
448
478
|
declare const svgElement: <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: ElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
449
479
|
declare const mathMLElement: <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: ElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
450
|
-
type DynamicElementChildren<ViewState> = Array<Conditional<ViewState> | ForEach<ViewState, any> | TextElement<ViewState> | BaseJayElement<ViewState> | string>;
|
|
480
|
+
type DynamicElementChildren<ViewState> = Array<Conditional<ViewState> | ForEach<ViewState, any> | WithData<ViewState, any> | TextElement<ViewState> | BaseJayElement<ViewState> | When<ViewState, any> | string>;
|
|
451
481
|
declare const dynamicElementNS: (ns: string) => <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: DynamicElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
452
482
|
declare const dynamicElement: <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: DynamicElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
453
483
|
declare const svgDynamicElement: <ViewState>(tagName: string, attributes: Attributes<ViewState>, children?: DynamicElementChildren<ViewState>, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>) => BaseJayElement<ViewState>;
|
|
@@ -481,7 +511,8 @@ declare class ConstructContext<ViewState> {
|
|
|
481
511
|
get currData(): ViewState;
|
|
482
512
|
coordinate: (refName: string) => Coordinate;
|
|
483
513
|
forItem<ChildViewState>(childViewState: ChildViewState, id: string): ConstructContext<ChildViewState>;
|
|
514
|
+
forAsync<ChildViewState>(childViewState: ChildViewState): ConstructContext<ChildViewState>;
|
|
484
515
|
static withRootContext<ViewState, Refs>(viewState: ViewState, refManager: ReferencesManager, elementConstructor: () => BaseJayElement<ViewState>): JayElement<ViewState, Refs>;
|
|
485
516
|
}
|
|
486
517
|
|
|
487
|
-
export { type Attribute, type Attributes, type BaseJayElement, BaseReferencesManager, type ComponentCollectionProxy, ComponentCollectionRefImpl, type ComponentProxy, ComponentRefImpl, ComponentRefsImpl, type Conditional, ConstructContext, type ContextMarker, type Coordinate, type DynamicAttributeOrProperty, EVENT_TRAP, type ElementFrom, type EventEmitter, type EventTypeFrom, type ExtractRefs, type ExtractViewState, type ForEach, GetTrapProxy, type GlobalJayEvents, type HTMLElementCollectionProxy, type HTMLElementCollectionProxyTarget, type HTMLElementProxy, type HTMLElementProxyTarget, type HTMLNativeExec, type HeadLink, type JayComponent, type JayComponentConstructor, type JayContract, type JayElement, type JayEvent, type JayEventHandler, type JayEventHandlerWrapper, type JayNativeEventBuilder, type JayNativeFunction, type ManagedRefConstructor, ManagedRefType, type ManagedRefs, type MapEventEmitterViewState, type MountFunc, type OnlyEventEmitters, type PreRenderElement, type PrivateRef, type PrivateRefConstructor, PrivateRefs, type PropsFrom, ReferencesManager, type RenderElement, type RenderElementOptions, type TextElement, type ViewStateFrom, booleanAttribute, childComp, conditional, createJayContext, currentConstructionContext, defaultEventWrapper, dynamicAttribute, dynamicElement, dynamicElementNS, dynamicProperty, dynamicText, element, findContext, forEach, injectHeadLinks, isCondition, isForEach, mathMLDynamicElement, mathMLElement, mkUpdateCollection, noopMount, noopUpdate, normalizeMount, normalizeUpdates, restoreContext, saveContext, svgDynamicElement, svgElement, type updateFunc, useContext, withContext };
|
|
518
|
+
export { type Attribute, type Attributes, type BaseJayElement, BaseReferencesManager, type ComponentCollectionProxy, ComponentCollectionRefImpl, type ComponentProxy, ComponentRefImpl, ComponentRefsImpl, type Conditional, ConstructContext, type ContextMarker, type Coordinate, type DynamicAttributeOrProperty, EVENT_TRAP, type ElementFrom, type EventEmitter, type EventTypeFrom, type ExtractRefs, type ExtractViewState, type ForEach, GetTrapProxy, type GlobalJayEvents, type HTMLElementCollectionProxy, type HTMLElementCollectionProxyTarget, type HTMLElementProxy, type HTMLElementProxyTarget, type HTMLNativeExec, type HeadLink, type JayComponent, type JayComponentConstructor, type JayContract, type JayElement, type JayEvent, type JayEventHandler, type JayEventHandlerWrapper, type JayLog, type JayNativeEventBuilder, type JayNativeFunction, LogType, type ManagedRefConstructor, ManagedRefType, type ManagedRefs, type MapEventEmitterViewState, type MountFunc, type OnlyEventEmitters, type PreRenderElement, type PrivateRef, type PrivateRefConstructor, PrivateRefs, type PropsFrom, ReferencesManager, type RenderElement, type RenderElementOptions, type TextElement, type ViewStateFrom, type When, WhenRole, type WithData, booleanAttribute, childComp, conditional, createJayContext, currentConstructionContext, defaultEventWrapper, dynamicAttribute, dynamicElement, dynamicElementNS, dynamicProperty, dynamicText, element, findContext, forEach, injectHeadLinks, isCondition, isForEach, isWhen, isWithData, jayLog, mathMLDynamicElement, mathMLElement, mkUpdateCollection, noop, noopMount, noopUpdate, normalizeMount, normalizeUpdates, pending, rejected, resolved, restoreContext, saveContext, svgDynamicElement, svgElement, type updateFunc, useContext, withContext, withData };
|
package/dist/index.js
CHANGED
|
@@ -80,9 +80,18 @@ class Kindergarten {
|
|
|
80
80
|
return offset;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
const
|
|
83
|
+
const noop = () => {
|
|
84
84
|
};
|
|
85
|
-
const
|
|
85
|
+
const noopUpdate = noop;
|
|
86
|
+
const noopMount = noop;
|
|
87
|
+
var LogType = /* @__PURE__ */ ((LogType2) => {
|
|
88
|
+
LogType2[LogType2["ASYNC_ERROR"] = 0] = "ASYNC_ERROR";
|
|
89
|
+
LogType2[LogType2["CONTEXT_NOT_FOUND"] = 1] = "CONTEXT_NOT_FOUND";
|
|
90
|
+
return LogType2;
|
|
91
|
+
})(LogType || {});
|
|
92
|
+
const jayLog = {
|
|
93
|
+
log: noop,
|
|
94
|
+
error: noop
|
|
86
95
|
};
|
|
87
96
|
let currentContext = void 0;
|
|
88
97
|
function NewContextStack(context, marker, parent) {
|
|
@@ -157,6 +166,9 @@ class ConstructContext {
|
|
|
157
166
|
forItem(childViewState, id) {
|
|
158
167
|
return new ConstructContext(childViewState, false, [...this.coordinateBase, id]);
|
|
159
168
|
}
|
|
169
|
+
forAsync(childViewState) {
|
|
170
|
+
return new ConstructContext(childViewState, false, [...this.coordinateBase]);
|
|
171
|
+
}
|
|
160
172
|
static withRootContext(viewState, refManager, elementConstructor) {
|
|
161
173
|
let context = new ConstructContext(viewState);
|
|
162
174
|
let element2 = withContext(
|
|
@@ -239,15 +251,110 @@ function conditional(condition, elem) {
|
|
|
239
251
|
}
|
|
240
252
|
};
|
|
241
253
|
}
|
|
254
|
+
var WhenRole = /* @__PURE__ */ ((WhenRole2) => {
|
|
255
|
+
WhenRole2[WhenRole2["pending"] = 0] = "pending";
|
|
256
|
+
WhenRole2[WhenRole2["resolved"] = 1] = "resolved";
|
|
257
|
+
WhenRole2[WhenRole2["rejected"] = 2] = "rejected";
|
|
258
|
+
return WhenRole2;
|
|
259
|
+
})(WhenRole || {});
|
|
260
|
+
function when(status, promise, elem) {
|
|
261
|
+
return {
|
|
262
|
+
role: status,
|
|
263
|
+
promise,
|
|
264
|
+
elem
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const resolved = (promise, elem) => when(1, promise, elem);
|
|
268
|
+
const rejected = (promise, elem) => when(2, promise, elem);
|
|
269
|
+
const pending = (promise, elem) => when(0, promise, elem);
|
|
242
270
|
function isCondition(c) {
|
|
243
271
|
return c.condition !== void 0;
|
|
244
272
|
}
|
|
245
273
|
function isForEach(c) {
|
|
246
274
|
return c.elemCreator !== void 0;
|
|
247
275
|
}
|
|
276
|
+
function isWhen(c) {
|
|
277
|
+
return c.promise !== void 0;
|
|
278
|
+
}
|
|
279
|
+
function isWithData(c) {
|
|
280
|
+
return c.accessor !== void 0;
|
|
281
|
+
}
|
|
282
|
+
function mkWhenCondition(when2, group) {
|
|
283
|
+
switch (when2.role) {
|
|
284
|
+
case 1:
|
|
285
|
+
return mkWhenResolvedCondition(when2, group);
|
|
286
|
+
case 2:
|
|
287
|
+
return mkWhenRejectedCondition(when2, group);
|
|
288
|
+
default:
|
|
289
|
+
return mkWhenPendingCondition(when2, group);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const Hide = Symbol();
|
|
293
|
+
function mkWhenConditionBase(when2, group, setupPromise) {
|
|
294
|
+
let show = false;
|
|
295
|
+
let savedValue = void 0;
|
|
296
|
+
const parentContext = currentConstructionContext();
|
|
297
|
+
const savedContext = saveContext();
|
|
298
|
+
const [cUpdate, cMouth, cUnmount] = mkUpdateCondition(
|
|
299
|
+
conditional(
|
|
300
|
+
() => show,
|
|
301
|
+
() => {
|
|
302
|
+
let childContext = parentContext.forAsync(savedValue);
|
|
303
|
+
return restoreContext(savedContext, () => {
|
|
304
|
+
return withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => {
|
|
305
|
+
return when2.elem();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
),
|
|
310
|
+
group
|
|
311
|
+
);
|
|
312
|
+
let currentPromise = when2.promise(parentContext.currData);
|
|
313
|
+
const handleValue = (changedPromise) => (value) => {
|
|
314
|
+
if (changedPromise === currentPromise) {
|
|
315
|
+
show = value !== Hide;
|
|
316
|
+
savedValue = value;
|
|
317
|
+
cUpdate(value);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
setupPromise(currentPromise, handleValue(currentPromise));
|
|
321
|
+
const update = (viewState) => {
|
|
322
|
+
const newValue = when2.promise(viewState);
|
|
323
|
+
if (currentPromise !== newValue) {
|
|
324
|
+
currentPromise = newValue;
|
|
325
|
+
setupPromise(currentPromise, handleValue(currentPromise));
|
|
326
|
+
show = false;
|
|
327
|
+
restoreContext(savedContext, () => cUpdate(void 0));
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return [update, cMouth, cUnmount];
|
|
331
|
+
}
|
|
332
|
+
function mkWhenResolvedCondition(when2, group) {
|
|
333
|
+
return mkWhenConditionBase(
|
|
334
|
+
when2,
|
|
335
|
+
group,
|
|
336
|
+
(promise, handleValue) => promise.then(handleValue).catch((err) => jayLog.error(LogType.ASYNC_ERROR, err))
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
function mkWhenRejectedCondition(when2, group) {
|
|
340
|
+
return mkWhenConditionBase(when2, group, (promise, handleValue) => promise.catch(handleValue));
|
|
341
|
+
}
|
|
342
|
+
function mkWhenPendingCondition(when2, group) {
|
|
343
|
+
let timeout;
|
|
344
|
+
return mkWhenConditionBase(when2, group, (promise, handleValue) => {
|
|
345
|
+
timeout = setTimeout(() => handleValue(void 0), 1);
|
|
346
|
+
promise.finally(() => {
|
|
347
|
+
clearTimeout(timeout);
|
|
348
|
+
handleValue(Hide);
|
|
349
|
+
}).catch((err) => jayLog.error(LogType.ASYNC_ERROR, err));
|
|
350
|
+
});
|
|
351
|
+
}
|
|
248
352
|
function forEach(getItems, elemCreator, matchBy) {
|
|
249
353
|
return { getItems, elemCreator, trackBy: matchBy };
|
|
250
354
|
}
|
|
355
|
+
function withData(accessor, elem) {
|
|
356
|
+
return { accessor, elem };
|
|
357
|
+
}
|
|
251
358
|
function applyListChanges(group, instructions) {
|
|
252
359
|
instructions.forEach((instruction) => {
|
|
253
360
|
if (instruction.action === ITEM_ADDED) {
|
|
@@ -303,13 +410,16 @@ function mkUpdateCondition(child, group) {
|
|
|
303
410
|
let mount = noopMount, unmount = noopMount;
|
|
304
411
|
let lastResult = false;
|
|
305
412
|
let childElement = void 0;
|
|
413
|
+
const savedContext = saveContext();
|
|
306
414
|
const update = (newData) => {
|
|
307
|
-
|
|
308
|
-
|
|
415
|
+
const result = child.condition(newData);
|
|
416
|
+
if (!childElement && result) {
|
|
417
|
+
restoreContext(savedContext, () => {
|
|
418
|
+
childElement = child.elem();
|
|
419
|
+
});
|
|
309
420
|
mount = () => lastResult && childElement.mount();
|
|
310
421
|
unmount = () => childElement.unmount();
|
|
311
422
|
}
|
|
312
|
-
let result = child.condition(newData);
|
|
313
423
|
if (result) {
|
|
314
424
|
if (!lastResult) {
|
|
315
425
|
group.ensureNode(childElement.dom);
|
|
@@ -324,6 +434,46 @@ function mkUpdateCondition(child, group) {
|
|
|
324
434
|
};
|
|
325
435
|
return [update, mount, unmount];
|
|
326
436
|
}
|
|
437
|
+
function mkUpdateWithData(child, group) {
|
|
438
|
+
let mount = noopMount, unmount = noopMount;
|
|
439
|
+
let lastResult = false;
|
|
440
|
+
let childElement = void 0;
|
|
441
|
+
const parentContext = currentConstructionContext();
|
|
442
|
+
const savedContext = saveContext();
|
|
443
|
+
const update = (newData) => {
|
|
444
|
+
const childData = child.accessor(newData);
|
|
445
|
+
const result = childData != null;
|
|
446
|
+
if (!childElement && result) {
|
|
447
|
+
const childContext = parentContext.forAsync(childData);
|
|
448
|
+
childElement = restoreContext(
|
|
449
|
+
savedContext,
|
|
450
|
+
() => withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => child.elem())
|
|
451
|
+
);
|
|
452
|
+
mount = () => lastResult && childElement.mount();
|
|
453
|
+
unmount = () => childElement.unmount();
|
|
454
|
+
}
|
|
455
|
+
if (result) {
|
|
456
|
+
if (!lastResult) {
|
|
457
|
+
group.ensureNode(childElement.dom);
|
|
458
|
+
childElement.mount();
|
|
459
|
+
}
|
|
460
|
+
const childContext = parentContext.forAsync(childData);
|
|
461
|
+
restoreContext(
|
|
462
|
+
savedContext,
|
|
463
|
+
() => withContext(
|
|
464
|
+
CONSTRUCTION_CONTEXT_MARKER,
|
|
465
|
+
childContext,
|
|
466
|
+
() => childElement.update(childData)
|
|
467
|
+
)
|
|
468
|
+
);
|
|
469
|
+
} else if (lastResult) {
|
|
470
|
+
childElement.unmount();
|
|
471
|
+
group.removeNode(childElement.dom);
|
|
472
|
+
}
|
|
473
|
+
lastResult = result;
|
|
474
|
+
};
|
|
475
|
+
return [update, mount, unmount];
|
|
476
|
+
}
|
|
327
477
|
function text(content) {
|
|
328
478
|
return {
|
|
329
479
|
dom: document.createTextNode(content),
|
|
@@ -386,6 +536,10 @@ const dynamicElementNS = (ns) => (tagName, attributes, children = [], ref) => {
|
|
|
386
536
|
[update, mount, unmount] = mkUpdateCondition(child, group);
|
|
387
537
|
} else if (isForEach(child)) {
|
|
388
538
|
[update, mount, unmount] = mkUpdateCollection(child, group);
|
|
539
|
+
} else if (isWithData(child)) {
|
|
540
|
+
[update, mount, unmount] = mkUpdateWithData(child, group);
|
|
541
|
+
} else if (isWhen(child)) {
|
|
542
|
+
[update, mount, unmount] = mkWhenCondition(child, group);
|
|
389
543
|
} else {
|
|
390
544
|
group.ensureNode(child.dom);
|
|
391
545
|
if (child.update !== noopUpdate)
|
|
@@ -822,9 +976,11 @@ export {
|
|
|
822
976
|
ConstructContext,
|
|
823
977
|
EVENT_TRAP,
|
|
824
978
|
GetTrapProxy,
|
|
979
|
+
LogType,
|
|
825
980
|
ManagedRefType,
|
|
826
981
|
PrivateRefs,
|
|
827
982
|
ReferencesManager,
|
|
983
|
+
WhenRole,
|
|
828
984
|
booleanAttribute,
|
|
829
985
|
childComp,
|
|
830
986
|
conditional,
|
|
@@ -842,17 +998,25 @@ export {
|
|
|
842
998
|
injectHeadLinks,
|
|
843
999
|
isCondition,
|
|
844
1000
|
isForEach,
|
|
1001
|
+
isWhen,
|
|
1002
|
+
isWithData,
|
|
1003
|
+
jayLog,
|
|
845
1004
|
mathMLDynamicElement,
|
|
846
1005
|
mathMLElement,
|
|
847
1006
|
mkUpdateCollection,
|
|
1007
|
+
noop,
|
|
848
1008
|
noopMount,
|
|
849
1009
|
noopUpdate,
|
|
850
1010
|
normalizeMount,
|
|
851
1011
|
normalizeUpdates,
|
|
1012
|
+
pending,
|
|
1013
|
+
rejected,
|
|
1014
|
+
resolved,
|
|
852
1015
|
restoreContext,
|
|
853
1016
|
saveContext,
|
|
854
1017
|
svgDynamicElement,
|
|
855
1018
|
svgElement,
|
|
856
1019
|
useContext,
|
|
857
|
-
withContext
|
|
1020
|
+
withContext,
|
|
1021
|
+
withData
|
|
858
1022
|
};
|
package/docs/runtime.md
CHANGED
|
@@ -319,23 +319,271 @@ at which
|
|
|
319
319
|
|
|
320
320
|
## forEach
|
|
321
321
|
|
|
322
|
+
The `forEach` function creates a dynamic collection of child elements that automatically updates when the underlying array data changes. It efficiently manages adding, removing, and reordering child elements based on a tracking key.
|
|
323
|
+
|
|
324
|
+
`forEach` is used within a `dynamicElement` to render lists of items, where each item in the array becomes a separate child element. The runtime intelligently tracks elements by a specified key property to minimize DOM manipulations during updates.
|
|
325
|
+
|
|
322
326
|
```typescript
|
|
323
327
|
declare function forEach<ViewState, Item>(
|
|
324
|
-
getItems: (
|
|
325
|
-
elemCreator: (Item:
|
|
326
|
-
|
|
328
|
+
getItems: (vs: ViewState) => Array<Item>,
|
|
329
|
+
elemCreator: (item: Item, trackByValue: string) => BaseJayElement<Item>,
|
|
330
|
+
trackBy: string,
|
|
327
331
|
): ForEach<ViewState, Item>;
|
|
328
332
|
```
|
|
329
333
|
|
|
334
|
+
at which
|
|
335
|
+
|
|
336
|
+
- `ViewState` - the type of the parent element's view state containing the array
|
|
337
|
+
- `Item` - the type of individual items in the array
|
|
338
|
+
- `getItems` - a function that extracts the array of items from the parent's view state
|
|
339
|
+
- `elemCreator` - a factory function that creates a child element for each item, receives:
|
|
340
|
+
- `item` - the current item data
|
|
341
|
+
- `trackByValue` - the value of the tracking key for this item (useful for setting element IDs)
|
|
342
|
+
- `trackBy` - the property name used to track elements across updates (typically an `id` or `key` field)
|
|
343
|
+
|
|
344
|
+
The `trackBy` parameter is crucial for performance - it tells Jay how to identify which elements are the same across updates, enabling efficient reordering and minimizing unnecessary DOM operations.
|
|
345
|
+
|
|
346
|
+
Example usage for a simple list:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import {
|
|
350
|
+
forEach,
|
|
351
|
+
dynamicElement as de,
|
|
352
|
+
element as e,
|
|
353
|
+
dynamicText as dt,
|
|
354
|
+
} from '@jay-framework/runtime';
|
|
355
|
+
|
|
356
|
+
interface TodoItem {
|
|
357
|
+
id: string;
|
|
358
|
+
text: string;
|
|
359
|
+
completed: boolean;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
interface ViewState {
|
|
363
|
+
todos: TodoItem[];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
de('ul', { class: 'todo-list' }, [
|
|
367
|
+
forEach(
|
|
368
|
+
(vs) => vs.todos,
|
|
369
|
+
(item: TodoItem) =>
|
|
370
|
+
e('li', { id: item.id, class: item.completed ? 'completed' : '' }, [dt((item) => item.text)]),
|
|
371
|
+
'id',
|
|
372
|
+
),
|
|
373
|
+
]);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Example with nested structure:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
interface Message {
|
|
380
|
+
id: string;
|
|
381
|
+
author: string;
|
|
382
|
+
text: string;
|
|
383
|
+
timestamp: number;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
interface ChatViewState {
|
|
387
|
+
messages: Message[];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
de('div', { class: 'chat' }, [
|
|
391
|
+
forEach(
|
|
392
|
+
(vs) => vs.messages,
|
|
393
|
+
(msg: Message) =>
|
|
394
|
+
e('div', { class: 'message', id: msg.id }, [
|
|
395
|
+
e('div', { class: 'author' }, [dt((m) => m.author)]),
|
|
396
|
+
e('div', { class: 'text' }, [dt((m) => m.text)]),
|
|
397
|
+
e('div', { class: 'time' }, [dt((m) => new Date(m.timestamp).toLocaleTimeString())]),
|
|
398
|
+
]),
|
|
399
|
+
'id',
|
|
400
|
+
),
|
|
401
|
+
]);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Key Features**:
|
|
405
|
+
|
|
406
|
+
- **Efficient Updates**: Only modifies DOM elements that actually changed
|
|
407
|
+
- **Smart Reordering**: Moves existing elements rather than recreating them when order changes
|
|
408
|
+
- **Memory Management**: Properly unmounts removed elements
|
|
409
|
+
- **Empty Arrays**: Gracefully handles empty arrays and null/undefined values
|
|
410
|
+
|
|
411
|
+
The Jay-HTML compiler automatically generates `forEach` calls when encountering `forEach` attributes in Jay-HTML files.
|
|
412
|
+
|
|
330
413
|
## conditional
|
|
331
414
|
|
|
415
|
+
The `conditional` function conditionally renders a child element based on a boolean expression. When the condition is true, the element is rendered and mounted; when false, it is unmounted and removed from the DOM.
|
|
416
|
+
|
|
417
|
+
`conditional` is similar to an `if` statement in programming - it determines whether a piece of UI should be visible based on the current view state. The child element is lazily created only when the condition first becomes true.
|
|
418
|
+
|
|
332
419
|
```typescript
|
|
333
420
|
declare function conditional<ViewState>(
|
|
334
|
-
condition: (
|
|
335
|
-
elem:
|
|
421
|
+
condition: (vs: ViewState) => boolean,
|
|
422
|
+
elem: () => BaseJayElement<ViewState> | TextElement<ViewState>,
|
|
336
423
|
): Conditional<ViewState>;
|
|
337
424
|
```
|
|
338
425
|
|
|
426
|
+
at which
|
|
427
|
+
|
|
428
|
+
- `ViewState` - the type of the element's view state
|
|
429
|
+
- `condition` - a function that evaluates to true (render element) or false (hide element)
|
|
430
|
+
- `elem` - a factory function that creates the conditional element (called only when condition is first true)
|
|
431
|
+
|
|
432
|
+
Example usage for showing/hiding UI elements:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import {
|
|
436
|
+
conditional,
|
|
437
|
+
dynamicElement as de,
|
|
438
|
+
element as e,
|
|
439
|
+
dynamicText as dt,
|
|
440
|
+
} from '@jay-framework/runtime';
|
|
441
|
+
|
|
442
|
+
interface ViewState {
|
|
443
|
+
isLoggedIn: boolean;
|
|
444
|
+
username: string;
|
|
445
|
+
hasErrors: boolean;
|
|
446
|
+
errorMessage: string;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
de('div', { class: 'header' }, [
|
|
450
|
+
conditional(
|
|
451
|
+
(vs) => vs.isLoggedIn,
|
|
452
|
+
() => e('div', { class: 'user-info' }, [dt((vs) => `Welcome, ${vs.username}`)]),
|
|
453
|
+
),
|
|
454
|
+
conditional(
|
|
455
|
+
(vs) => !vs.isLoggedIn,
|
|
456
|
+
() => e('button', {}, ['Login']),
|
|
457
|
+
),
|
|
458
|
+
conditional(
|
|
459
|
+
(vs) => vs.hasErrors,
|
|
460
|
+
() => e('div', { class: 'error' }, [dt((vs) => vs.errorMessage)]),
|
|
461
|
+
),
|
|
462
|
+
]);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Example with complex conditions:
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
interface FormState {
|
|
469
|
+
step: number;
|
|
470
|
+
isValid: boolean;
|
|
471
|
+
isSubmitting: boolean;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
de('div', { class: 'form' }, [
|
|
475
|
+
conditional(
|
|
476
|
+
(vs) => vs.step === 1,
|
|
477
|
+
() => e('div', { class: 'step-1' }, ['Step 1: Basic Info']),
|
|
478
|
+
),
|
|
479
|
+
conditional(
|
|
480
|
+
(vs) => vs.step === 2,
|
|
481
|
+
() => e('div', { class: 'step-2' }, ['Step 2: Details']),
|
|
482
|
+
),
|
|
483
|
+
conditional(
|
|
484
|
+
(vs) => vs.step === 3 && vs.isValid,
|
|
485
|
+
() => e('div', { class: 'step-3' }, ['Step 3: Review']),
|
|
486
|
+
),
|
|
487
|
+
conditional(
|
|
488
|
+
(vs) => vs.isSubmitting,
|
|
489
|
+
() => e('div', { class: 'spinner' }, ['Loading...']),
|
|
490
|
+
),
|
|
491
|
+
]);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Key Features**:
|
|
495
|
+
|
|
496
|
+
- **Lazy Creation**: The element is only created when the condition first becomes true
|
|
497
|
+
- **Efficient Toggling**: Mounting/unmounting is efficient when toggling visibility
|
|
498
|
+
- **Clean DOM**: When false, the element is completely removed from the DOM (not just hidden)
|
|
499
|
+
- **Lifecycle Management**: Properly calls mount/unmount hooks when condition changes
|
|
500
|
+
|
|
501
|
+
**Note**: Complex boolean expressions with `&&` or `||` may need to be split into nested conditionals depending on the Jay-HTML parser capabilities.
|
|
502
|
+
|
|
503
|
+
The Jay-HTML compiler automatically generates `conditional` calls when encountering `if` attributes in Jay-HTML files.
|
|
504
|
+
|
|
505
|
+
## withData
|
|
506
|
+
|
|
507
|
+
The `withData` function enables context switching for child elements with a different data type. This is primarily used for recursive structures where a child element needs to operate on a subset or related piece of the parent's data.
|
|
508
|
+
|
|
509
|
+
`withData` is similar to `conditional`, but instead of checking a boolean condition, it:
|
|
510
|
+
|
|
511
|
+
1. Checks if the accessor function returns a non-null/undefined value
|
|
512
|
+
2. If present, renders the child element with the accessed data as its ViewState
|
|
513
|
+
3. If null/undefined, hides the child element
|
|
514
|
+
|
|
515
|
+
This is essential for recursive Jay-HTML structures where a node may have optional child nodes (like a tree or linked list).
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
declare function withData<ParentViewState, ChildViewState>(
|
|
519
|
+
accessor: (data: ParentViewState) => ChildViewState | null | undefined,
|
|
520
|
+
elem: () => BaseJayElement<ChildViewState>,
|
|
521
|
+
): WithData<ParentViewState, ChildViewState>;
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
at which
|
|
525
|
+
|
|
526
|
+
- `ParentViewState` - the type of the parent element's view state
|
|
527
|
+
- `ChildViewState` - the type of the child element's view state (can be the same as parent for recursive structures)
|
|
528
|
+
- `accessor` - a function that extracts child data from parent data, returns null/undefined if child should not render
|
|
529
|
+
- `elem` - a factory function that creates the child element (called only when accessor returns non-null data)
|
|
530
|
+
|
|
531
|
+
Example usage for a binary tree structure:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import {
|
|
535
|
+
element as e,
|
|
536
|
+
dynamicElement as de,
|
|
537
|
+
withData,
|
|
538
|
+
dynamicText as dt,
|
|
539
|
+
} from '@jay-framework/runtime';
|
|
540
|
+
|
|
541
|
+
interface TreeNode {
|
|
542
|
+
value: number;
|
|
543
|
+
left: TreeNode | null;
|
|
544
|
+
right: TreeNode | null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function renderTree(): BaseJayElement<TreeNode> {
|
|
548
|
+
return e('div', { class: 'tree-node' }, [
|
|
549
|
+
e('div', { class: 'value' }, [dt((vs) => vs.value)]),
|
|
550
|
+
de('div', { class: 'children' }, [
|
|
551
|
+
withData(
|
|
552
|
+
(vs) => vs.left,
|
|
553
|
+
() => renderTree(),
|
|
554
|
+
),
|
|
555
|
+
withData(
|
|
556
|
+
(vs) => vs.right,
|
|
557
|
+
() => renderTree(),
|
|
558
|
+
),
|
|
559
|
+
]),
|
|
560
|
+
]);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Example usage for a linked list:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
interface ListNode {
|
|
568
|
+
value: string;
|
|
569
|
+
next: ListNode | null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function renderList(): BaseJayElement<ListNode> {
|
|
573
|
+
return e('div', { class: 'list-item' }, [
|
|
574
|
+
e('span', {}, [dt((vs) => vs.value)]),
|
|
575
|
+
de('div', { class: 'next' }, [
|
|
576
|
+
withData(
|
|
577
|
+
(vs) => vs.next,
|
|
578
|
+
() => renderList(),
|
|
579
|
+
),
|
|
580
|
+
]),
|
|
581
|
+
]);
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The generated Jay-HTML compiler automatically uses `withData` when encountering recursive regions with the `<recurse>` element and an `accessor` attribute.
|
|
586
|
+
|
|
339
587
|
## ConstructionContext
|
|
340
588
|
|
|
341
589
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"test:watch": "vitest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@jay-framework/list-compare": "^0.
|
|
27
|
-
"@jay-framework/reactive": "^0.
|
|
26
|
+
"@jay-framework/list-compare": "^0.8.0",
|
|
27
|
+
"@jay-framework/reactive": "^0.8.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@jay-framework/dev-environment": "^0.
|
|
30
|
+
"@jay-framework/dev-environment": "^0.8.0",
|
|
31
31
|
"@testing-library/jest-dom": "^6.2.0",
|
|
32
32
|
"@types/jsdom": "^21.1.6",
|
|
33
33
|
"@types/node": "^20.11.5",
|