@abide/abide 0.104.0 → 0.105.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/CHANGELOG.md +16 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +17 -17
- package/docs/RULEBOOK.md +50 -12
- package/docs/abide.md +9 -9
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +45 -86
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -20
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +11 -9
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +180 -512
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +92 -123
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +49 -71
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +4 -2
- package/src/shared/reactive/Reactive.ts +285 -197
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -123
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/reconcile.ts +4 -110
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -33
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
package/src/ui/Level.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentShare,
|
|
3
|
+
leaveShare,
|
|
4
|
+
resumeShare,
|
|
5
|
+
type ShareScope,
|
|
6
|
+
} from '#shared/reactive/share.ts'
|
|
7
|
+
import { untracked } from '#shared/reactive/tracking.ts'
|
|
8
|
+
import { type Disposer, watchTracked } from '#shared/reactive/watch.ts'
|
|
9
|
+
import { applyHead, withdrawHead } from './applyHead.ts'
|
|
10
|
+
import { isEmptyRegion, type Region } from './regions.ts'
|
|
11
|
+
import type { Scope } from './Scope.ts'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ONE LEVEL OF A ROUTED CHAIN — a layout or the page — and the head it contributes (39.1, 39.14).
|
|
15
|
+
*
|
|
16
|
+
* 41.11 — the router holds these rather than letting each level own the one below it, which is
|
|
17
|
+
* what makes a shared layout something it can KEEP: a scope stopped from above takes its children
|
|
18
|
+
* with it, and a layout that survives a navigation must not take the page it was holding a moment
|
|
19
|
+
* ago. The module standing here is the router's `chain` at the same index.
|
|
20
|
+
*
|
|
21
|
+
* ON EVERY PAGE'S STATIC CLOSURE, because `contributeHead` is on the runtime barrel — so it knows
|
|
22
|
+
* nothing of the router, and the router knows it only through `levels`.
|
|
23
|
+
*/
|
|
24
|
+
export class Level {
|
|
25
|
+
/** The chain this level stands in, and where: a live head yields to every level below it. */
|
|
26
|
+
levels: Level[]
|
|
27
|
+
at: number
|
|
28
|
+
scope: Scope | undefined = undefined
|
|
29
|
+
/**
|
|
30
|
+
* Where this level's `<slot/>` is, learned when the level below it mounted there, and
|
|
31
|
+
* `undefined` again once that region leaves — a `<slot/>` inside a closed `{#if}` is nowhere.
|
|
32
|
+
*/
|
|
33
|
+
slot: Region | undefined = undefined
|
|
34
|
+
/**
|
|
35
|
+
* 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
|
|
36
|
+
* once every level below has been set up too; `undefined` once it has run, or where the module
|
|
37
|
+
* has none.
|
|
38
|
+
*/
|
|
39
|
+
markup: ((region: Region) => Scope | undefined) | undefined = undefined
|
|
40
|
+
/**
|
|
41
|
+
* 10.5 — the share scope this level's setup ran in, for the level below to be set up inside.
|
|
42
|
+
* `null` where the module left none.
|
|
43
|
+
*/
|
|
44
|
+
share: ShareScope | null = null
|
|
45
|
+
/**
|
|
46
|
+
* The head the components built on the client contributed while this level mounts, applied
|
|
47
|
+
* once its `mount` returns; `undefined` outside one, where a contribution applies at once.
|
|
48
|
+
*/
|
|
49
|
+
unapplied: string | undefined = undefined
|
|
50
|
+
/**
|
|
51
|
+
* This level's own head markup as it reads now; `undefined` for a level with no `<head>`.
|
|
52
|
+
*
|
|
53
|
+
* 39.14 — READ WHEN THE LEVEL LEAVES OR STAYS ABOVE ONE THAT DOES, which is why it is the
|
|
54
|
+
* markup's thunk and not the markup: a layout's `<title>{name}</title>` puts back the name it
|
|
55
|
+
* reads at that moment.
|
|
56
|
+
*/
|
|
57
|
+
head: (() => string) | undefined = undefined
|
|
58
|
+
/**
|
|
59
|
+
* The heads of the components mounted in this level, after its own and in the order they
|
|
60
|
+
* mounted. A COLLECTION, NOT A CLOSURE PER CONTRIBUTION: a `{#for}` of components with a
|
|
61
|
+
* `<head>` contributes once a row, and a chain of closures is read through as deep a stack. A
|
|
62
|
+
* `Set`, so a row leaving with its region (39.14, 39.16) is withdrawn without a walk of the rest.
|
|
63
|
+
*/
|
|
64
|
+
contributed = new Set<HeadContribution>()
|
|
65
|
+
|
|
66
|
+
constructor(levels: Level[], at: number) {
|
|
67
|
+
this.levels = levels
|
|
68
|
+
this.at = at
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Left by the level's mount (`emitClient`), painted or not. */
|
|
72
|
+
setHead(head: () => string): void {
|
|
73
|
+
this.head = head
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Left by the level's mount for the router to run once the chain is set up (41.19). */
|
|
77
|
+
leaveMarkup(
|
|
78
|
+
markup: (region: Region) => Scope | undefined,
|
|
79
|
+
share: ShareScope | null,
|
|
80
|
+
): void {
|
|
81
|
+
this.markup = markup
|
|
82
|
+
this.share = share
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The level's head as it reads now: its own, then every component's, deepest last (39.4). */
|
|
86
|
+
headMarkup(): string {
|
|
87
|
+
let markup = this.head?.() ?? ''
|
|
88
|
+
for (const one of this.contributed) markup += one.head()
|
|
89
|
+
return markup
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Joins a component's `<head>` to this level's. Applied once the level is mounted where the
|
|
94
|
+
* component builds its own markup; a region the server painted already carries the head the
|
|
95
|
+
* server wrote. It follows what it reads from here (39.13): A WATCH WHETHER OR NOT IT READS
|
|
96
|
+
* ANYTHING THAT MOVES, one per component carrying a `<head>`, where a flag would be a second
|
|
97
|
+
* path through here and an argument in every component that has one.
|
|
98
|
+
*/
|
|
99
|
+
contribute(region: Region, head: () => string): HeadContribution {
|
|
100
|
+
const contribution = new HeadContribution(this, head)
|
|
101
|
+
this.contributed.add(contribution)
|
|
102
|
+
contribution.dispose = watchTracked(() => contribution.follow())
|
|
103
|
+
// Batched while this level mounts; applied at once by an arm or a row mounted after it,
|
|
104
|
+
// under whatever is deeper (39.4) — only the levels below, since it was added last here.
|
|
105
|
+
const text = contribution.last ?? ''
|
|
106
|
+
if (isEmptyRegion(region))
|
|
107
|
+
if (this.unapplied !== undefined) this.unapplied += text
|
|
108
|
+
else
|
|
109
|
+
applyHead(
|
|
110
|
+
text,
|
|
111
|
+
untracked(Level.headsOf, this.levels, this.at + 1),
|
|
112
|
+
)
|
|
113
|
+
return contribution
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Writes `text` over the `last` that `contributor` in this level wrote.
|
|
118
|
+
*
|
|
119
|
+
* 39.4 — IT YIELDS TO EVERY CONTRIBUTOR DEEPER, read as they stand at the write and untracked:
|
|
120
|
+
* the components that contributed after it, then every level below, so a layout's
|
|
121
|
+
* `<title>{site}</title>` moving does not write over the page's `<title>`, and the page's head
|
|
122
|
+
* moving wakes only the page's own watch. 39.15 — its keyless nodes replace the ones it wrote.
|
|
123
|
+
*/
|
|
124
|
+
reapply(text: string, last: string, contributor: HeadContribution): void {
|
|
125
|
+
applyHead(
|
|
126
|
+
text,
|
|
127
|
+
untracked(Level.headsAfter, this, contributor),
|
|
128
|
+
last,
|
|
129
|
+
// Read only where a key `last` held is withdrawn.
|
|
130
|
+
() => untracked(Level.headsBefore, this, contributor),
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The heads either side of `contribution` in `level` (`headsBefore`, `headsAfter`). One `level`
|
|
136
|
+
* does not hold is the level's own head (`followHead`), which stands before every component's.
|
|
137
|
+
*/
|
|
138
|
+
static headsAround(
|
|
139
|
+
level: Level,
|
|
140
|
+
contribution: HeadContribution,
|
|
141
|
+
): [string, string] {
|
|
142
|
+
return [
|
|
143
|
+
Level.headsBefore(level, contribution),
|
|
144
|
+
Level.headsAfter(level, contribution),
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Every level above `level`, then what stands before `contribution` in it. */
|
|
149
|
+
static headsBefore(level: Level, contribution: HeadContribution): string {
|
|
150
|
+
let outer = Level.headsOf(level.levels, 0, level.at)
|
|
151
|
+
if (!level.contributed.has(contribution)) return outer
|
|
152
|
+
outer += level.head?.() ?? ''
|
|
153
|
+
for (const one of level.contributed) {
|
|
154
|
+
if (one === contribution) break
|
|
155
|
+
outer += one.head()
|
|
156
|
+
}
|
|
157
|
+
return outer
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** What stands after `contribution` in `level`, then every level below it. */
|
|
161
|
+
static headsAfter(level: Level, contribution: HeadContribution): string {
|
|
162
|
+
let inner = ''
|
|
163
|
+
let past = !level.contributed.has(contribution)
|
|
164
|
+
for (const one of level.contributed)
|
|
165
|
+
if (past) inner += one.head()
|
|
166
|
+
else if (one === contribution) past = true
|
|
167
|
+
return inner + Level.headsOf(level.levels, level.at + 1)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 39.13 — A HEAD THAT READS A VALUE FOLLOWS IT for as long as the level is mounted, which is
|
|
172
|
+
* `scope`'s life. The first run only subscribes: the head it would write is already there,
|
|
173
|
+
* written by the server or applied by the level's mount ahead of its fill (39.6). Followed as a
|
|
174
|
+
* component's is, by a contribution the level does not hold (`headsAround`).
|
|
175
|
+
*/
|
|
176
|
+
followHead(scope: Scope): void {
|
|
177
|
+
const own = new HeadContribution(this, this.head as () => string)
|
|
178
|
+
scope.watch(() => own.follow())
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Runs `run` with this the level components add their `<head>` to (39.1) — and applies what
|
|
183
|
+
* those built on the client contributed once it returns, rather than once a component.
|
|
184
|
+
*/
|
|
185
|
+
mount<Result>(run: () => Result): Result {
|
|
186
|
+
// 39.1 — THE LEVEL RIDES THE SHARE SCOPE, so a component finds it as it finds a shared key,
|
|
187
|
+
// whether it mounts now or in an arm or a row after (`contributeHead`).
|
|
188
|
+
const back = resumeShare({
|
|
189
|
+
parent: currentShare(),
|
|
190
|
+
values: new Map(),
|
|
191
|
+
level: this,
|
|
192
|
+
})
|
|
193
|
+
this.unapplied = ''
|
|
194
|
+
let unapplied: string
|
|
195
|
+
let result: Result
|
|
196
|
+
try {
|
|
197
|
+
result = run()
|
|
198
|
+
} finally {
|
|
199
|
+
unapplied = this.unapplied
|
|
200
|
+
this.unapplied = undefined
|
|
201
|
+
leaveShare(back)
|
|
202
|
+
}
|
|
203
|
+
// 39.4 — under whatever a level below wrote while this one was mounting: the page in a
|
|
204
|
+
// layout's `<slot/>` applied its own head ahead of the components that layout holds.
|
|
205
|
+
if (unapplied !== '')
|
|
206
|
+
applyHead(unapplied, Level.headsOf(this.levels, this.at + 1))
|
|
207
|
+
return result
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The heads of `levels` from `from` to `to`, outermost first. */
|
|
211
|
+
static headsOf(levels: Level[], from: number, to = levels.length): string {
|
|
212
|
+
let markup = ''
|
|
213
|
+
for (let at = from; at < to; at += 1)
|
|
214
|
+
markup += (levels[at] as Level).headMarkup()
|
|
215
|
+
return markup
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* One component's `<head>` in its level — what the component's scope owns, so the head leaves with
|
|
221
|
+
* the component's region (39.14, 39.16).
|
|
222
|
+
*/
|
|
223
|
+
export class HeadContribution {
|
|
224
|
+
level: Level
|
|
225
|
+
head: () => string
|
|
226
|
+
/** What this contribution last wrote, or `undefined` before its first run. */
|
|
227
|
+
last: string | undefined = undefined
|
|
228
|
+
dispose: Disposer | undefined = undefined
|
|
229
|
+
|
|
230
|
+
constructor(level: Level, head: () => string) {
|
|
231
|
+
this.level = level
|
|
232
|
+
this.head = head
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** 39.13 — the first run only subscribes, as a level's own does. */
|
|
236
|
+
follow(): void {
|
|
237
|
+
const text = this.head()
|
|
238
|
+
const last = this.last
|
|
239
|
+
if (last !== undefined && text !== last)
|
|
240
|
+
this.level.reapply(text, last, this)
|
|
241
|
+
this.last = text
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 39.14 — WITHDRAWN AS A LEAVING LEVEL'S IS, its keys going back to what stands outside it and
|
|
246
|
+
* staying where something deeper holds them. Nothing where the level is leaving too: the router
|
|
247
|
+
* withdraws the whole level once, and cleared its contributions before stopping it.
|
|
248
|
+
*/
|
|
249
|
+
stop(): void {
|
|
250
|
+
this.dispose?.()
|
|
251
|
+
const level = this.level
|
|
252
|
+
if (!level.contributed.has(this)) return
|
|
253
|
+
withdrawHead(this.last ?? '', () =>
|
|
254
|
+
untracked(Level.headsAround, level, this),
|
|
255
|
+
)
|
|
256
|
+
level.contributed.delete(this)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* A component's `<head>`, added to the page or layout it stands in (39.1) — found through the share
|
|
262
|
+
* scope the component runs in, so an arm or a row mounted after its level finds it as its first
|
|
263
|
+
* mount did (39.13).
|
|
264
|
+
*
|
|
265
|
+
* Joined to that level's head, so it is withdrawn as the level's is when it leaves (39.14), and
|
|
266
|
+
* deeper than the level's own, so it wins by key (39.4). Nothing outside a routed level.
|
|
267
|
+
*/
|
|
268
|
+
export function contributeHead(
|
|
269
|
+
region: Region,
|
|
270
|
+
head: () => string,
|
|
271
|
+
): HeadContribution | undefined {
|
|
272
|
+
return (currentShare()?.level as Level | undefined)?.contribute(
|
|
273
|
+
region,
|
|
274
|
+
head,
|
|
275
|
+
)
|
|
276
|
+
}
|
package/src/ui/List.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { listOf } from '#shared/listOf.ts'
|
|
2
2
|
import { untracked } from '#shared/reactive/tracking.ts'
|
|
3
3
|
import { HYDRATE_LOG, Mismatch, warnMismatch } from './Mismatch.ts'
|
|
4
|
-
import {
|
|
4
|
+
import { reconcile } from './reconcile.ts'
|
|
5
5
|
import { isEmptyRegion, type Region, removeUntil } from './regions.ts'
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
dropRow,
|
|
8
|
+
makeRow,
|
|
9
|
+
type Row,
|
|
10
|
+
type RowBuilders,
|
|
11
|
+
rowOf,
|
|
12
|
+
rowsIn,
|
|
13
|
+
} from './rows.ts'
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* The state of one `{#for}` block, shared by its reconcile and its async append.
|
|
@@ -253,21 +259,15 @@ async function pump(list: List, source: AsyncIterable<unknown>): Promise<void> {
|
|
|
253
259
|
}
|
|
254
260
|
// Appended, so its position IS the index it arrived at.
|
|
255
261
|
list.rows.push(
|
|
256
|
-
makeRow(
|
|
262
|
+
makeRow(
|
|
263
|
+
list.setUp,
|
|
264
|
+
parent,
|
|
265
|
+
region.close,
|
|
266
|
+
list.key(item, at),
|
|
267
|
+
item,
|
|
268
|
+
at,
|
|
269
|
+
),
|
|
257
270
|
)
|
|
258
271
|
at += 1
|
|
259
272
|
}
|
|
260
273
|
}
|
|
261
|
-
|
|
262
|
-
export type RowBuilders = {
|
|
263
|
-
/**
|
|
264
|
-
* A row's nodes, BUILT in the document the list stands in — handed over by the reconcile
|
|
265
|
-
* rather than found by the row, which is the one place a row's document is decided.
|
|
266
|
-
*/
|
|
267
|
-
make: (document: Document, item: unknown, at: number) => Node[]
|
|
268
|
-
/**
|
|
269
|
-
* Handed the row's RECORD rather than its nodes: a row it rebuilds (41.10) writes its new
|
|
270
|
-
* first node back there.
|
|
271
|
-
*/
|
|
272
|
-
mount: (row: Row, item: unknown, at: number) => Scope
|
|
273
|
-
}
|
package/src/ui/Router.ts
CHANGED
|
@@ -8,9 +8,9 @@ import { arrive, route } from '#shared/route.ts'
|
|
|
8
8
|
import { rankRoutes, type Segment } from '#shared/routePattern.ts'
|
|
9
9
|
import { setUpLevels } from '#shared/setUpLevels.ts'
|
|
10
10
|
import { paintedCallsLanded } from '#shared/takeEarlyCall.ts'
|
|
11
|
-
import {
|
|
11
|
+
import { withdrawHead } from './applyHead.ts'
|
|
12
12
|
import { LAYER_MOUNT, LAYER_NAVIGATE } from './LAYER_NAMES.ts'
|
|
13
|
-
import {
|
|
13
|
+
import { Level } from './Level.ts'
|
|
14
14
|
import { beginLayer, endLayer } from './measureLayer.ts'
|
|
15
15
|
import { fillRegion, pageRegion, type Region } from './regions.ts'
|
|
16
16
|
import type { Scope, Stoppable } from './Scope.ts'
|
|
@@ -51,43 +51,6 @@ type Compiled = Route & { segments: Segment[] }
|
|
|
51
51
|
|
|
52
52
|
type Found = { route: Compiled; params: Params }
|
|
53
53
|
|
|
54
|
-
// 41.11 — ONE LEVEL OF WHAT IS MOUNTED: the scope that stops it and the region its child
|
|
55
|
-
// occupies. The module standing there is `chain`'s at the same index. The router holds these
|
|
56
|
-
// rather than letting each level own the one below it, which is what makes a shared layout
|
|
57
|
-
// something it can KEEP: a scope stopped from above takes its children with it, and a layout that
|
|
58
|
-
// survives a navigation must not take the page it was holding a moment ago.
|
|
59
|
-
type Level = {
|
|
60
|
-
scope: Scope | undefined
|
|
61
|
-
/**
|
|
62
|
-
* Where this level's `<slot/>` is, learned when the level below it mounted there, and
|
|
63
|
-
* `undefined` again once that region leaves — a `<slot/>` inside a closed `{#if}` is nowhere.
|
|
64
|
-
*/
|
|
65
|
-
slot: Region | undefined
|
|
66
|
-
/**
|
|
67
|
-
* This level's head markup as it reads now, left by its mount (`emitClient`); `undefined` for
|
|
68
|
-
* a level with no `<head>`.
|
|
69
|
-
*
|
|
70
|
-
* 39.14 — READ WHEN THE LEVEL LEAVES OR STAYS ABOVE ONE THAT DOES, which is why it is the
|
|
71
|
-
* markup's thunk and not the markup: a layout's `<title>{name}</title>` puts back the name it
|
|
72
|
-
* reads at that moment. Left painted or not, since a painted level's head is the one the server
|
|
73
|
-
* wrote and the first navigation away from it withdraws that head like any other.
|
|
74
|
-
*/
|
|
75
|
-
head: (() => string) | undefined
|
|
76
|
-
/** The heads of the components mounted in this level, joined to `head` (`contributeHead`). */
|
|
77
|
-
contributed: (() => string)[] | undefined
|
|
78
|
-
/**
|
|
79
|
-
* 41.19 — this level's markup, left by its mount once its setup has run, for the router to run
|
|
80
|
-
* once every level below has been set up too; `undefined` once it has run, or where the module
|
|
81
|
-
* has none.
|
|
82
|
-
*/
|
|
83
|
-
markup: ((region: Region) => Scope | undefined) | undefined
|
|
84
|
-
/**
|
|
85
|
-
* 10.5 — the share scope this level's setup ran in, left by its mount, for `setUpLevels` to
|
|
86
|
-
* set the level below up inside. `null` where the module left none.
|
|
87
|
-
*/
|
|
88
|
-
share: ShareScope | null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
54
|
/**
|
|
92
55
|
* The client router: matches addresses, and mounts and replaces the page chain.
|
|
93
56
|
*
|
|
@@ -354,7 +317,11 @@ export class Router {
|
|
|
354
317
|
// exports is the same function either way.
|
|
355
318
|
const shared = this.sharedLevels(arriving)
|
|
356
319
|
// READ BEFORE THE STOP, while the leaving levels' heads read what they read on screen.
|
|
357
|
-
const leaving =
|
|
320
|
+
const leaving = Level.headsOf(
|
|
321
|
+
this.levels,
|
|
322
|
+
shared,
|
|
323
|
+
this.levels.length,
|
|
324
|
+
)
|
|
358
325
|
this.stopFrom(shared)
|
|
359
326
|
// The region the arriving part goes into: the page region where nothing is kept, and
|
|
360
327
|
// the deepest kept layout's own slot where something is.
|
|
@@ -375,9 +342,10 @@ export class Router {
|
|
|
375
342
|
this.mountChain(target, found, arriving, shared, into)
|
|
376
343
|
// 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
|
|
377
344
|
// nearest kept layout's, else leaves.
|
|
378
|
-
withdrawHead(leaving,
|
|
379
|
-
this.
|
|
380
|
-
|
|
345
|
+
withdrawHead(leaving, () => [
|
|
346
|
+
Level.headsOf(this.levels, 0, shared),
|
|
347
|
+
Level.headsOf(this.levels, shared, this.levels.length),
|
|
348
|
+
])
|
|
381
349
|
// The top of the page, or — as the browser does on a load — the fragment's element.
|
|
382
350
|
if (options.keepScroll !== true) scrollToFragment(target.hash)
|
|
383
351
|
} finally {
|
|
@@ -407,14 +375,6 @@ export class Router {
|
|
|
407
375
|
return shared
|
|
408
376
|
}
|
|
409
377
|
|
|
410
|
-
/** The heads of the standing levels from `from` to `to`, outermost first. */
|
|
411
|
-
private heads(from: number, to: number): string {
|
|
412
|
-
let markup = ''
|
|
413
|
-
for (let level = from; level < to; level += 1)
|
|
414
|
-
markup += (this.levels[level] as Level).head?.() ?? ''
|
|
415
|
-
return markup
|
|
416
|
-
}
|
|
417
|
-
|
|
418
378
|
/**
|
|
419
379
|
* Stops every level from `at` down, inside out, and drops them.
|
|
420
380
|
*
|
|
@@ -423,8 +383,12 @@ export class Router {
|
|
|
423
383
|
* two files would expect.
|
|
424
384
|
*/
|
|
425
385
|
private stopFrom(at: number): void {
|
|
426
|
-
for (let
|
|
427
|
-
|
|
386
|
+
for (let index = this.levels.length - 1; index >= at; index -= 1) {
|
|
387
|
+
const level = this.levels[index] as Level
|
|
388
|
+
// The level's head leaves whole, so none of its components withdraws its own.
|
|
389
|
+
level.contributed.clear()
|
|
390
|
+
level.scope?.stop()
|
|
391
|
+
}
|
|
428
392
|
this.levels.length = at
|
|
429
393
|
}
|
|
430
394
|
|
|
@@ -463,14 +427,7 @@ export class Router {
|
|
|
463
427
|
props: Record<string, (() => unknown) | undefined>,
|
|
464
428
|
): ShareScope | null {
|
|
465
429
|
const module = this.chain[at] as PageModule
|
|
466
|
-
const level
|
|
467
|
-
scope: undefined,
|
|
468
|
-
slot: undefined,
|
|
469
|
-
head: undefined,
|
|
470
|
-
contributed: undefined,
|
|
471
|
-
markup: undefined,
|
|
472
|
-
share: null,
|
|
473
|
-
}
|
|
430
|
+
const level = new Level(this.levels, at)
|
|
474
431
|
this.levels[at] = level
|
|
475
432
|
if (at < this.chain.length - 1)
|
|
476
433
|
props.children = () => ({
|
|
@@ -493,9 +450,7 @@ export class Router {
|
|
|
493
450
|
// runs; a module mounted by the router leaves its markup rather than using this one.
|
|
494
451
|
// A deferred level answers the scope its setup made, so the watches that setup opened are
|
|
495
452
|
// stopped with the level even where its markup never runs — a slot that never opens.
|
|
496
|
-
level.scope =
|
|
497
|
-
module.default(region, props, level),
|
|
498
|
-
)
|
|
453
|
+
level.scope = level.mount(() => module.default(region, props, level))
|
|
499
454
|
return level.share
|
|
500
455
|
}
|
|
501
456
|
|
|
@@ -505,7 +460,7 @@ export class Router {
|
|
|
505
460
|
const markup = level.markup
|
|
506
461
|
if (markup === undefined) return
|
|
507
462
|
level.markup = undefined
|
|
508
|
-
level.scope =
|
|
463
|
+
level.scope = level.mount(() => markup(region))
|
|
509
464
|
}
|
|
510
465
|
|
|
511
466
|
/**
|
|
@@ -675,25 +630,3 @@ export function startRouter(
|
|
|
675
630
|
CURRENT_ROUTER.current = router
|
|
676
631
|
return router
|
|
677
632
|
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* `run`, with `level` the one its components add their `<head>` to (39.1) — and what those built on
|
|
681
|
-
* the client contributed applied once it returns, rather than once a component.
|
|
682
|
-
*/
|
|
683
|
-
function mounting<Result>(level: Level, run: () => Result): Result {
|
|
684
|
-
const back = MOUNTING_LEVEL.current
|
|
685
|
-
const backUnapplied = MOUNTING_LEVEL.unapplied
|
|
686
|
-
MOUNTING_LEVEL.current = level
|
|
687
|
-
MOUNTING_LEVEL.unapplied = ''
|
|
688
|
-
let unapplied: string
|
|
689
|
-
let result: Result
|
|
690
|
-
try {
|
|
691
|
-
result = run()
|
|
692
|
-
} finally {
|
|
693
|
-
unapplied = MOUNTING_LEVEL.unapplied
|
|
694
|
-
MOUNTING_LEVEL.current = back
|
|
695
|
-
MOUNTING_LEVEL.unapplied = backUnapplied
|
|
696
|
-
}
|
|
697
|
-
applyHead(unapplied)
|
|
698
|
-
return result
|
|
699
|
-
}
|