@decky.fx/raven-tsx 0.1.0 → 0.1.1
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 +53 -3
- package/package.json +28 -9
- package/src/core/events.ts +74 -18
- package/src/core/subscriptions.ts +4 -2
- package/src/core/widget-instance.ts +333 -24
- package/src/core/window-instance.ts +423 -8
- package/src/react/host-config.ts +1 -1
- package/src/solid/babel-preset-solid.d.ts +7 -0
- package/src/solid/components.generated.ts +163 -0
- package/src/solid/index.ts +29 -0
- package/src/solid/node.ts +405 -0
- package/src/solid/plugin.ts +108 -0
- package/src/solid/portal.ts +27 -0
- package/src/solid/preload.ts +14 -0
- package/src/solid/primitives.ts +43 -0
- package/src/solid/renderer.ts +37 -0
- package/src/solid/root.ts +137 -0
- package/src/solid/tag.ts +37 -0
- package/src/solid/tags.ts +101 -0
- package/src/solid/teardown.ts +50 -0
- package/src/solid/web.ts +5 -0
|
@@ -4,6 +4,22 @@ import { applyHandlers, controlledSync, eventScope, forgetHandlers, setVirtualIt
|
|
|
4
4
|
import { applyPlotData, checkPlotData, restorePlotData } from "./plot";
|
|
5
5
|
import { CLEARED, resolveProps, type RavenProps, type ResolvedProps, type TreeItem } from "./props";
|
|
6
6
|
|
|
7
|
+
/** Runs a function, and keeps its failure with the others, to go on with what must be done. */
|
|
8
|
+
function attempt(failures: unknown[], run: () => void): void {
|
|
9
|
+
try {
|
|
10
|
+
run();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
failures.push(error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Throws the first of some failures, once all that could run has run. */
|
|
17
|
+
function throwFirst(failures: readonly unknown[]): void {
|
|
18
|
+
if (failures.length > 0) {
|
|
19
|
+
throw failures[0];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
/** The host type of a widget Raven made and an adapter only places: `adopt`. */
|
|
8
24
|
export const ADOPTED_TAG = "raven-tsx:adopted";
|
|
9
25
|
|
|
@@ -22,9 +38,16 @@ const placedInThisBatch: WidgetInstance[] = [];
|
|
|
22
38
|
* commit. The nodes made during the batch get their attributes that need a parent, as they have one now.
|
|
23
39
|
*/
|
|
24
40
|
export function finishPlacing(): void {
|
|
25
|
-
|
|
41
|
+
const placed = placedInThisBatch.splice(0);
|
|
42
|
+
for (const instance of placed) {
|
|
26
43
|
instance.applyParentAttributes();
|
|
27
44
|
}
|
|
45
|
+
// (then what waited for them to be placed, user code: a ref. All of it, then the first failure)
|
|
46
|
+
const failures: unknown[] = [];
|
|
47
|
+
for (const instance of placed) {
|
|
48
|
+
attempt(failures, () => instance.runPlaced());
|
|
49
|
+
}
|
|
50
|
+
throwFirst(failures);
|
|
28
51
|
}
|
|
29
52
|
|
|
30
53
|
/** What a framework's tree is made of: a widget, or a window, which is no widget of its parent. */
|
|
@@ -32,6 +55,8 @@ export interface HostNode {
|
|
|
32
55
|
readonly tag: string;
|
|
33
56
|
/** The widget to put in a parent, or null for a window, which stands by itself. */
|
|
34
57
|
readonly nativeWidget: r.Rid | null;
|
|
58
|
+
/** Whether Raven has it: made, and not deleted. A window open whose function threw still is. */
|
|
59
|
+
readonly placed: boolean;
|
|
35
60
|
/** Says who holds it. */
|
|
36
61
|
setParent(parent: WidgetInstance | null): void;
|
|
37
62
|
/**
|
|
@@ -46,11 +71,22 @@ export interface HostNode {
|
|
|
46
71
|
/** Adds a child while this new node is rendered, in its props only: Raven gets it at `materialize`. */
|
|
47
72
|
appendInitial(child: HostNode): void;
|
|
48
73
|
insertBefore(child: HostNode, before: HostNode): void;
|
|
74
|
+
/** Takes a child out without deleting it: a framework may place it again, here or elsewhere. */
|
|
75
|
+
release(child: HostNode): void;
|
|
49
76
|
remove(child: HostNode): void;
|
|
50
77
|
/** Forgets the handlers of this node and of everything under it. */
|
|
51
78
|
forgetTree(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Gives back the handlers `forgetTree` took, to what is still alive: a cleanup run while its tree
|
|
81
|
+
* was deleted placed it elsewhere. Nothing to do for a node never forgotten.
|
|
82
|
+
*/
|
|
83
|
+
rememberTree(): void;
|
|
52
84
|
/** Deletes it, after its parent let it go. */
|
|
53
85
|
dispose(): void;
|
|
86
|
+
/** Closes the windows in this tree, keeping them to open again: what holds them left the tree, alive. */
|
|
87
|
+
closeWindows(): void;
|
|
88
|
+
/** Opens the windows in this tree that are closed: what holds them is placed again. */
|
|
89
|
+
openWindows(): void;
|
|
54
90
|
}
|
|
55
91
|
|
|
56
92
|
/** How to read the value a widget holds by itself, for the attribute that sets it. */
|
|
@@ -262,6 +298,16 @@ export class WidgetInstance implements HostNode {
|
|
|
262
298
|
/** How many children the widget had when it was made: those of its markup come first. */
|
|
263
299
|
private madeChildren = 0;
|
|
264
300
|
private state: NodeState;
|
|
301
|
+
/** Whether the tree it is the top of is shown: a window that closes says no before its windows close. */
|
|
302
|
+
private shown = true;
|
|
303
|
+
/** What waits for it to be placed, at the end of the batch that placed it: a ref of a framework. */
|
|
304
|
+
private readonly placedWaiting: ((instance: WidgetInstance) => void)[] = [];
|
|
305
|
+
/** Made, and not placed yet: what waits for it to be placed waits for the end of the batch. */
|
|
306
|
+
private placing = false;
|
|
307
|
+
/** Its handlers were forgotten (`forgetTree`): placed again alive, it gets them back. */
|
|
308
|
+
private forgotten = false;
|
|
309
|
+
/** Counts the passes that close or open the windows in it: a newer one ends the one under way. */
|
|
310
|
+
private windowsPass = 0;
|
|
265
311
|
|
|
266
312
|
private constructor(
|
|
267
313
|
private id: r.Rid,
|
|
@@ -274,6 +320,11 @@ export class WidgetInstance implements HostNode {
|
|
|
274
320
|
|
|
275
321
|
/** A new widget of a tag, as a framework renders it: Raven makes it once it is placed, at `materialize`. */
|
|
276
322
|
static create<Child>(tag: string, props: RavenProps<Child>): WidgetInstance {
|
|
323
|
+
return new WidgetInstance(r.R_INVALID_ID, tag, WidgetInstance.resolveChecked(tag, props));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** The props of a tag, checked for what can be found wrong with them without Raven. */
|
|
327
|
+
private static resolveChecked<Child>(tag: string, props: RavenProps<Child>): ResolvedProps {
|
|
277
328
|
const resolved = resolveProps(props, DATA_PROPS[tag]?.names);
|
|
278
329
|
DATA_PROPS[tag]?.check(resolved.data);
|
|
279
330
|
for (const [name, check] of Object.entries(CHECKED_AT_RENDER[tag] ?? {})) {
|
|
@@ -282,7 +333,7 @@ export class WidgetInstance implements HostNode {
|
|
|
282
333
|
check(value);
|
|
283
334
|
}
|
|
284
335
|
}
|
|
285
|
-
return
|
|
336
|
+
return resolved;
|
|
286
337
|
}
|
|
287
338
|
|
|
288
339
|
/**
|
|
@@ -298,14 +349,35 @@ export class WidgetInstance implements HostNode {
|
|
|
298
349
|
this.madeChildren = r.Rwidget_get_child_count(this.id);
|
|
299
350
|
this.state = "live";
|
|
300
351
|
this.pending = null;
|
|
352
|
+
this.placing = true;
|
|
353
|
+
this.forgotten = false;
|
|
301
354
|
try {
|
|
302
355
|
this.applyAll(resolved);
|
|
303
|
-
for (const child of this.children) {
|
|
356
|
+
for (const child of [...this.children]) {
|
|
357
|
+
// (user code run for an earlier one may have deleted this one: the others are not made)
|
|
358
|
+
if (this.state !== "live") {
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
// (or taken it away)
|
|
362
|
+
if (!this.children.includes(child)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
304
365
|
child.materialize();
|
|
366
|
+
// (making it ran user code, which may have taken it away from here, or deleted this: its
|
|
367
|
+
// windows are not this one's to open then)
|
|
368
|
+
if (this.state !== "live" || !this.children.includes(child)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
child.rememberTree();
|
|
372
|
+
child.openWindows();
|
|
373
|
+
// (opening its windows ran user code too)
|
|
374
|
+
if (this.state !== "live" || !this.children.includes(child)) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
305
377
|
// (an adopted widget Raven moves here from where it is shown; one its element destroyed is left out)
|
|
306
378
|
const widget = child.nativeWidget;
|
|
307
379
|
if (this.holds(child) && widget !== null && r.Rwidget_is_valid(widget)) {
|
|
308
|
-
r.
|
|
380
|
+
r.Rwidget_insert_child(this.id, widget, this.placeAmongPlaced(child));
|
|
309
381
|
}
|
|
310
382
|
}
|
|
311
383
|
} catch (error) {
|
|
@@ -314,7 +386,30 @@ export class WidgetInstance implements HostNode {
|
|
|
314
386
|
this.dispose();
|
|
315
387
|
throw error;
|
|
316
388
|
}
|
|
317
|
-
|
|
389
|
+
if (this.state === "live") {
|
|
390
|
+
placedInThisBatch.push(this);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Where a child goes among the widgets Raven holds here: just before the first one that comes
|
|
396
|
+
* after it, or at the end. Only those placed count: user code run while a sibling is made may
|
|
397
|
+
* place a child while that sibling waits. (and those of its markup come first)
|
|
398
|
+
*/
|
|
399
|
+
private placeAmongPlaced(child: HostNode): number {
|
|
400
|
+
const count = r.Rwidget_get_child_count(this.id);
|
|
401
|
+
const after = new Set(
|
|
402
|
+
this.children
|
|
403
|
+
.slice(this.children.indexOf(child) + 1)
|
|
404
|
+
.filter((other) => this.holds(other))
|
|
405
|
+
.map((other) => other.nativeWidget),
|
|
406
|
+
);
|
|
407
|
+
for (let index = 0; index < count; index++) {
|
|
408
|
+
if (after.has(r.Rwidget_get_child(this.id, index))) {
|
|
409
|
+
return index;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return count;
|
|
318
413
|
}
|
|
319
414
|
|
|
320
415
|
/** An instance for a widget the framework does not own: the top of a root. */
|
|
@@ -360,6 +455,10 @@ export class WidgetInstance implements HostNode {
|
|
|
360
455
|
return this.id;
|
|
361
456
|
}
|
|
362
457
|
|
|
458
|
+
get placed(): boolean {
|
|
459
|
+
return this.state === "live";
|
|
460
|
+
}
|
|
461
|
+
|
|
363
462
|
/**
|
|
364
463
|
* Opens a `<Menu>` that stands by itself as a popup, at a position of its window. A right click
|
|
365
464
|
* gives that position: `OnContext` of a TreeView or a TileView, in the window.
|
|
@@ -380,6 +479,66 @@ export class WidgetInstance implements HostNode {
|
|
|
380
479
|
this.parent = parent;
|
|
381
480
|
}
|
|
382
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Whether it is in a tree that is shown: its parents lead to the top of a root (or to the
|
|
484
|
+
* content of a window), or to an island a window holds. One a framework took out, alive, is not:
|
|
485
|
+
* the windows in it stay closed until it is placed again.
|
|
486
|
+
*/
|
|
487
|
+
get attached(): boolean {
|
|
488
|
+
let top: WidgetInstance = this;
|
|
489
|
+
while (top.parent) {
|
|
490
|
+
top = top.parent;
|
|
491
|
+
}
|
|
492
|
+
return top.shown && (top.tag === "" || top.holder !== null);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Whether a node is one of its children. */
|
|
496
|
+
has(child: HostNode): boolean {
|
|
497
|
+
return this.children.includes(child);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Says whether the tree it is the top of is shown: the content of a window that closes is not. */
|
|
501
|
+
setShown(shown: boolean): void {
|
|
502
|
+
this.shown = shown;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Gives it to a function once Raven has it, placed: now if it is, or at the end of the batch
|
|
507
|
+
* that places it (`finishPlacing`), as React gives a ref after its commit.
|
|
508
|
+
*/
|
|
509
|
+
whenPlaced(run: (instance: WidgetInstance) => void): void {
|
|
510
|
+
if (this.state === "gone") {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// (one being made, or made in a batch not over, is not placed yet: it waits for `finishPlacing`)
|
|
514
|
+
if (this.state === "live" && !this.placing) {
|
|
515
|
+
// (a parent deleted with it leaves it live here, with no widget: it gets nothing)
|
|
516
|
+
if (r.Rwidget_is_valid(this.id)) {
|
|
517
|
+
run(this);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.placedWaiting.push(run);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Runs what waited for it to be placed: see `whenPlaced`. */
|
|
525
|
+
runPlaced(): void {
|
|
526
|
+
this.placing = false;
|
|
527
|
+
const waiting = this.placedWaiting.splice(0);
|
|
528
|
+
if (this.state !== "live") {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const failures: unknown[] = [];
|
|
532
|
+
for (const run of waiting) {
|
|
533
|
+
// (one of them may have deleted it, or a parent with it: the others get nothing)
|
|
534
|
+
if (this.state !== "live" || !r.Rwidget_is_valid(this.id)) {
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
attempt(failures, () => run(this));
|
|
538
|
+
}
|
|
539
|
+
throwFirst(failures);
|
|
540
|
+
}
|
|
541
|
+
|
|
383
542
|
/** Says who to tell of a new handle, when what holds this widget is not a parent. */
|
|
384
543
|
setHolder(holder: ((widget: r.Rid) => void) | null): void {
|
|
385
544
|
this.holder = holder;
|
|
@@ -509,6 +668,11 @@ export class WidgetInstance implements HostNode {
|
|
|
509
668
|
|
|
510
669
|
/** Goes from the last props to new ones, setting only what changed. */
|
|
511
670
|
update<Child>(props: RavenProps<Child>): void {
|
|
671
|
+
// (a framework that gives props one by one, Solid, gives some before the node is placed: Raven gets them at `materialize`)
|
|
672
|
+
if (this.state === "pending") {
|
|
673
|
+
this.pending = WidgetInstance.resolveChecked(this.tag, props);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
512
676
|
// (its window can be gone already: closed by the user, with no OnClose to ask first. An adopted widget has no props of the framework)
|
|
513
677
|
if (this.state !== "live" || !r.Rwidget_is_valid(this.id) || this.tag === ADOPTED_TAG) {
|
|
514
678
|
return;
|
|
@@ -633,20 +797,68 @@ export class WidgetInstance implements HostNode {
|
|
|
633
797
|
|
|
634
798
|
/** Adds a child at the end, making it in Raven if it was only pending. A child already here moves to the end. */
|
|
635
799
|
append(child: HostNode): void {
|
|
800
|
+
if (this.state === "pending") {
|
|
801
|
+
this.detach(child);
|
|
802
|
+
this.appendInitial(child);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
636
805
|
this.checkHolds(child);
|
|
637
|
-
child.materialize();
|
|
638
806
|
this.detach(child);
|
|
639
807
|
this.children.push(child);
|
|
640
808
|
child.setParent(this);
|
|
641
|
-
|
|
642
|
-
|
|
809
|
+
this.make(child, () => {
|
|
810
|
+
// (at its place, not at the end: making it ran user code, which may have added others after it)
|
|
811
|
+
if (this.holds(child)) {
|
|
812
|
+
r.Rwidget_insert_child(this.id, child.nativeWidget as r.Rid, this.placeAmongPlaced(child));
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Makes a child that is here in Raven, then places it there. The child is recorded first: if
|
|
819
|
+
* making it fails with something of it left in Raven (a window open whose function threw), what
|
|
820
|
+
* deletes this deletes it. One that Raven does not have after a failure is not kept.
|
|
821
|
+
*/
|
|
822
|
+
private make(child: HostNode, place: () => void): void {
|
|
823
|
+
const failures: unknown[] = [];
|
|
824
|
+
attempt(failures, () => child.materialize());
|
|
825
|
+
// (one that failed and that Raven does not have is not kept. Nor one whose making ran user code
|
|
826
|
+
// that deleted this: deleting this widget did not reach it, not placed yet)
|
|
827
|
+
// (making it ran user code, which may have placed it elsewhere: that parent has it, as it is)
|
|
828
|
+
if (!this.children.includes(child)) {
|
|
829
|
+
throwFirst(failures);
|
|
830
|
+
return;
|
|
643
831
|
}
|
|
832
|
+
if ((failures.length > 0 && !child.placed) || this.state !== "live") {
|
|
833
|
+
const index = this.children.indexOf(child);
|
|
834
|
+
if (index >= 0) {
|
|
835
|
+
this.children.splice(index, 1);
|
|
836
|
+
}
|
|
837
|
+
child.setParent(null);
|
|
838
|
+
// (what is left of it in JavaScript goes: its handlers, a window Raven destroyed)
|
|
839
|
+
attempt(failures, () => {
|
|
840
|
+
child.forgetTree();
|
|
841
|
+
child.dispose();
|
|
842
|
+
});
|
|
843
|
+
throwFirst(failures);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
// (making it ran user code, which may have taken it away from here)
|
|
847
|
+
if (this.children.includes(child)) {
|
|
848
|
+
child.rememberTree();
|
|
849
|
+
place();
|
|
850
|
+
// (in a tree that is not shown, its windows stay closed: they open when it is placed again)
|
|
851
|
+
attempt(failures, () => (this.attached ? child.openWindows() : child.closeWindows()));
|
|
852
|
+
}
|
|
853
|
+
throwFirst(failures);
|
|
644
854
|
}
|
|
645
855
|
|
|
646
856
|
/** Puts a child before another one. A child already here moves. */
|
|
647
857
|
insertBefore(child: HostNode, before: HostNode): void {
|
|
648
|
-
this.
|
|
649
|
-
|
|
858
|
+
const pending = this.state === "pending";
|
|
859
|
+
if (!pending) {
|
|
860
|
+
this.checkHolds(child);
|
|
861
|
+
}
|
|
650
862
|
this.detach(child);
|
|
651
863
|
const index = this.children.indexOf(before);
|
|
652
864
|
if (index < 0) {
|
|
@@ -654,8 +866,12 @@ export class WidgetInstance implements HostNode {
|
|
|
654
866
|
}
|
|
655
867
|
this.children.splice(index, 0, child);
|
|
656
868
|
child.setParent(this);
|
|
657
|
-
if (
|
|
658
|
-
|
|
869
|
+
if (!pending) {
|
|
870
|
+
this.make(child, () => {
|
|
871
|
+
if (this.holds(child)) {
|
|
872
|
+
r.Rwidget_insert_child(this.id, child.nativeWidget as r.Rid, this.placeAmongPlaced(child));
|
|
873
|
+
}
|
|
874
|
+
});
|
|
659
875
|
}
|
|
660
876
|
}
|
|
661
877
|
|
|
@@ -664,10 +880,61 @@ export class WidgetInstance implements HostNode {
|
|
|
664
880
|
const index = this.children.indexOf(child);
|
|
665
881
|
if (index >= 0) {
|
|
666
882
|
this.children.splice(index, 1);
|
|
667
|
-
|
|
668
|
-
|
|
883
|
+
const widget = child.nativeWidget;
|
|
884
|
+
// (a node not placed yet has no widget, and neither has a child that was not placed with it)
|
|
885
|
+
// (also while it is disposed: a cleanup run then may take one away, which must not go with it)
|
|
886
|
+
if (r.Rwidget_is_valid(this.id) && this.holds(child) && widget !== null && r.Rwidget_is_valid(widget) && r.Rwidget_get_parent(widget) === this.id) {
|
|
887
|
+
r.Rwidget_remove_child(this.id, widget);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
release(child: HostNode): void {
|
|
893
|
+
if (this.children.includes(child)) {
|
|
894
|
+
this.detach(child);
|
|
895
|
+
child.setParent(null);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
closeWindows(): void {
|
|
900
|
+
// (all of them, also past one that throws)
|
|
901
|
+
const failures: unknown[] = [];
|
|
902
|
+
const pass = ++this.windowsPass;
|
|
903
|
+
for (const child of [...this.children]) {
|
|
904
|
+
// (a cleanup of one may have shown this tree again, opening its windows: this pass is over)
|
|
905
|
+
if (this.windowsPass !== pass) {
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
// (or moved another away: it is no longer this one's to close)
|
|
909
|
+
if (this.children.includes(child)) {
|
|
910
|
+
attempt(failures, () => child.closeWindows());
|
|
669
911
|
}
|
|
670
912
|
}
|
|
913
|
+
throwFirst(failures);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
openWindows(): void {
|
|
917
|
+
const failures: unknown[] = [];
|
|
918
|
+
const pass = ++this.windowsPass;
|
|
919
|
+
for (const child of [...this.children]) {
|
|
920
|
+
// (and one that closes them again ends this one)
|
|
921
|
+
if (this.windowsPass !== pass) {
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
if (this.children.includes(child)) {
|
|
925
|
+
attempt(failures, () => child.openWindows());
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
throwFirst(failures);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** Takes every child out, alive, and gives them: a window that closes keeps what it holds. */
|
|
932
|
+
releaseChildren(): HostNode[] {
|
|
933
|
+
const children = [...this.children];
|
|
934
|
+
for (const child of children) {
|
|
935
|
+
this.release(child);
|
|
936
|
+
}
|
|
937
|
+
return children;
|
|
671
938
|
}
|
|
672
939
|
|
|
673
940
|
/** Removes a child and deletes it with everything under it. */
|
|
@@ -688,25 +955,54 @@ export class WidgetInstance implements HostNode {
|
|
|
688
955
|
}
|
|
689
956
|
|
|
690
957
|
forgetTree(): void {
|
|
958
|
+
this.forgotten = true;
|
|
959
|
+
// (what it holds may be made, added to it while it waited)
|
|
960
|
+
for (const child of this.children) {
|
|
961
|
+
child.forgetTree();
|
|
962
|
+
}
|
|
691
963
|
// (a node Raven has not made has no handlers: it gets them at `materialize`)
|
|
692
|
-
if (this.state
|
|
964
|
+
if (this.state === "live") {
|
|
965
|
+
forgetHandlers(this.tag, this.id, false);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
rememberTree(): void {
|
|
970
|
+
if (!this.forgotten) {
|
|
693
971
|
return;
|
|
694
972
|
}
|
|
695
|
-
|
|
696
|
-
|
|
973
|
+
this.forgotten = false;
|
|
974
|
+
for (const child of [...this.children]) {
|
|
975
|
+
child.rememberTree();
|
|
976
|
+
}
|
|
977
|
+
// (an adopted widget has no handlers of the framework: those of its element are its own)
|
|
978
|
+
if (this.state === "live" && this.tag !== ADOPTED_TAG && r.Rwidget_is_valid(this.id)) {
|
|
979
|
+
applyHandlers(this.tag, this.id, new Map(), this.resolved.handlers);
|
|
980
|
+
this.applyVirtualItems(null, this.resolved);
|
|
981
|
+
this.watchControlled();
|
|
697
982
|
}
|
|
698
|
-
forgetHandlers(this.tag, this.id, false);
|
|
699
983
|
}
|
|
700
984
|
|
|
701
985
|
/** Deletes what is in this tree without being a widget of it: deleting a widget does not reach those. */
|
|
702
986
|
disposeDetached(): void {
|
|
703
|
-
|
|
987
|
+
// (all of them, also past one whose cleanup throws)
|
|
988
|
+
const failures: unknown[] = [];
|
|
989
|
+
const parent = this.parent;
|
|
990
|
+
for (const child of [...this.children]) {
|
|
991
|
+
// (a cleanup of one may have placed this tree elsewhere: what is left is that place's now)
|
|
992
|
+
if (this.parent !== parent) {
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
// (or taken another away: it is no longer this one's)
|
|
996
|
+
if (!this.children.includes(child)) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
704
999
|
if (!this.holds(child)) {
|
|
705
|
-
child.dispose();
|
|
1000
|
+
attempt(failures, () => child.dispose());
|
|
706
1001
|
} else if (child instanceof WidgetInstance) {
|
|
707
|
-
child.disposeDetached();
|
|
1002
|
+
attempt(failures, () => child.disposeDetached());
|
|
708
1003
|
}
|
|
709
1004
|
}
|
|
1005
|
+
throwFirst(failures);
|
|
710
1006
|
}
|
|
711
1007
|
|
|
712
1008
|
/**
|
|
@@ -735,14 +1031,27 @@ export class WidgetInstance implements HostNode {
|
|
|
735
1031
|
const state = this.state;
|
|
736
1032
|
this.state = "gone";
|
|
737
1033
|
if (state !== "live") {
|
|
1034
|
+
// (what it holds may be made, added to it while it waited: deleting no widget deletes those)
|
|
1035
|
+
const failures: unknown[] = [];
|
|
1036
|
+
for (const child of [...this.children]) {
|
|
1037
|
+
// (a cleanup of one may have taken another away: it is no longer this one's)
|
|
1038
|
+
if (this.children.includes(child)) {
|
|
1039
|
+
attempt(failures, () => child.dispose());
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
throwFirst(failures);
|
|
738
1043
|
return;
|
|
739
1044
|
}
|
|
740
1045
|
if (r.Rwidget_is_valid(this.id)) {
|
|
741
1046
|
this.releaseAdopted();
|
|
742
1047
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1048
|
+
try {
|
|
1049
|
+
this.disposeDetached();
|
|
1050
|
+
} finally {
|
|
1051
|
+
// (also when a cleanup of what it held threw: it is gone, and nothing deletes it after)
|
|
1052
|
+
if (r.Rwidget_is_valid(this.id)) {
|
|
1053
|
+
r.Rwidget_destroy(this.id);
|
|
1054
|
+
}
|
|
746
1055
|
}
|
|
747
1056
|
}
|
|
748
1057
|
}
|