@lightspeed-js/core 0.1.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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/compile.js +1494 -0
  4. package/dist/index.js +3060 -0
  5. package/dist/ssg.js +3620 -0
  6. package/dist/types/build.d.ts +1 -0
  7. package/dist/types/index.d.ts +8 -0
  8. package/dist/types/src/agent/index.d.ts +175 -0
  9. package/dist/types/src/cli/commands/agent.d.ts +9 -0
  10. package/dist/types/src/cli/commands/build.d.ts +2 -0
  11. package/dist/types/src/cli/commands/create.d.ts +2 -0
  12. package/dist/types/src/cli/commands/dev.d.ts +2 -0
  13. package/dist/types/src/cli/commands/index.d.ts +3 -0
  14. package/dist/types/src/cli/index.d.ts +2 -0
  15. package/dist/types/src/cli/types/commands.d.ts +11 -0
  16. package/dist/types/src/cli/types/index.d.ts +1 -0
  17. package/dist/types/src/cli/utils/commands.d.ts +5 -0
  18. package/dist/types/src/cli/utils/index.d.ts +1 -0
  19. package/dist/types/src/compile/expressions.d.ts +76 -0
  20. package/dist/types/src/compile/index.d.ts +44 -0
  21. package/dist/types/src/compile/logic.d.ts +101 -0
  22. package/dist/types/src/manifest.d.ts +148 -0
  23. package/dist/types/src/marker.d.ts +85 -0
  24. package/dist/types/src/parser/index.d.ts +2 -0
  25. package/dist/types/src/parser/layout.d.ts +32 -0
  26. package/dist/types/src/parser/lsp.d.ts +56 -0
  27. package/dist/types/src/parser/route.d.ts +87 -0
  28. package/dist/types/src/parser/style-source-node.d.ts +19 -0
  29. package/dist/types/src/parser/style.d.ts +20 -0
  30. package/dist/types/src/parser/template.d.ts +32 -0
  31. package/dist/types/src/parser/test.d.ts +1 -0
  32. package/dist/types/src/runtime/bindings/BindingEvaluator.d.ts +174 -0
  33. package/dist/types/src/runtime/bindings/index.d.ts +2 -0
  34. package/dist/types/src/runtime/compiler/ImportTransformer.d.ts +11 -0
  35. package/dist/types/src/runtime/compiler/index.d.ts +1 -0
  36. package/dist/types/src/runtime/components/ComponentInstance.d.ts +110 -0
  37. package/dist/types/src/runtime/components/ComponentRegistry.d.ts +11 -0
  38. package/dist/types/src/runtime/components/index.d.ts +3 -0
  39. package/dist/types/src/runtime/components/types/index.d.ts +34 -0
  40. package/dist/types/src/runtime/core.d.ts +251 -0
  41. package/dist/types/src/runtime/diagnostics.d.ts +33 -0
  42. package/dist/types/src/runtime/dom/ForBlock.d.ts +127 -0
  43. package/dist/types/src/runtime/dom/Mounter.d.ts +223 -0
  44. package/dist/types/src/runtime/dom/StyleManager.d.ts +57 -0
  45. package/dist/types/src/runtime/dom/hydration.d.ts +35 -0
  46. package/dist/types/src/runtime/dom/index.d.ts +7 -0
  47. package/dist/types/src/runtime/events/EventBinder.d.ts +18 -0
  48. package/dist/types/src/runtime/events/index.d.ts +1 -0
  49. package/dist/types/src/runtime/globals/GlobalFunctions.d.ts +24 -0
  50. package/dist/types/src/runtime/globals/index.d.ts +1 -0
  51. package/dist/types/src/runtime/helpers.d.ts +141 -0
  52. package/dist/types/src/runtime/index.d.ts +16 -0
  53. package/dist/types/src/runtime/reactive/ReactiveProxy.d.ts +63 -0
  54. package/dist/types/src/runtime/reactive/UpdateScheduler.d.ts +9 -0
  55. package/dist/types/src/runtime/reactive/index.d.ts +2 -0
  56. package/dist/types/src/runtime/signals/SignalManager.d.ts +10 -0
  57. package/dist/types/src/runtime/signals/index.d.ts +3 -0
  58. package/dist/types/src/runtime/signals/signals.d.ts +16 -0
  59. package/dist/types/src/runtime/signals/types/index.d.ts +1 -0
  60. package/dist/types/src/runtime/signals/types/signals.d.ts +7 -0
  61. package/dist/types/src/runtime/state/Cell.d.ts +68 -0
  62. package/dist/types/src/runtime/state/CellManager.d.ts +101 -0
  63. package/dist/types/src/runtime/state/index.d.ts +4 -0
  64. package/dist/types/src/specifiers.d.ts +16 -0
  65. package/dist/types/src/ssg/index.d.ts +21 -0
  66. package/dist/types/src/ssg/rebuild.d.ts +49 -0
  67. package/dist/types/src/ssg/render.d.ts +104 -0
  68. package/dist/types/src/types/lsp.d.ts +22 -0
  69. package/dist/types/src/types/optional.d.ts +14 -0
  70. package/dist/types/src/types/virtual.d.ts +4 -0
  71. package/dist/types/src/vite/csp.d.ts +12 -0
  72. package/dist/types/src/vite/index.d.ts +2 -0
  73. package/dist/types/src/vite/layout.d.ts +17 -0
  74. package/dist/types/src/vite/plugin.d.ts +69 -0
  75. package/dist/types/src/vite/routes.d.ts +139 -0
  76. package/dist/types/test/adopted-styles.test.d.ts +1 -0
  77. package/dist/types/test/adoption.test.d.ts +1 -0
  78. package/dist/types/test/agent.test.d.ts +1 -0
  79. package/dist/types/test/audit.test.d.ts +1 -0
  80. package/dist/types/test/bindings.test.d.ts +1 -0
  81. package/dist/types/test/branches-in-loops.test.d.ts +1 -0
  82. package/dist/types/test/build.test.d.ts +1 -0
  83. package/dist/types/test/buildtime.test.d.ts +1 -0
  84. package/dist/types/test/built.test.d.ts +1 -0
  85. package/dist/types/test/cell-props.test.d.ts +1 -0
  86. package/dist/types/test/comments.test.d.ts +1 -0
  87. package/dist/types/test/compile-expressions.test.d.ts +1 -0
  88. package/dist/types/test/compile-logic.test.d.ts +1 -0
  89. package/dist/types/test/components.test.d.ts +1 -0
  90. package/dist/types/test/composition.test.d.ts +1 -0
  91. package/dist/types/test/declared-needs.test.d.ts +1 -0
  92. package/dist/types/test/diagnostics.test.d.ts +1 -0
  93. package/dist/types/test/dynamic.test.d.ts +1 -0
  94. package/dist/types/test/example.test.d.ts +1 -0
  95. package/dist/types/test/faq.test.d.ts +1 -0
  96. package/dist/types/test/flatten-nested.test.d.ts +1 -0
  97. package/dist/types/test/html-binding.test.d.ts +1 -0
  98. package/dist/types/test/hydration-report.test.d.ts +1 -0
  99. package/dist/types/test/hydration.test.d.ts +1 -0
  100. package/dist/types/test/layout-resolution.test.d.ts +1 -0
  101. package/dist/types/test/layout.test.d.ts +1 -0
  102. package/dist/types/test/lazy.test.d.ts +1 -0
  103. package/dist/types/test/manifest.test.d.ts +1 -0
  104. package/dist/types/test/model-props.test.d.ts +1 -0
  105. package/dist/types/test/model.test.d.ts +1 -0
  106. package/dist/types/test/mounting.test.d.ts +1 -0
  107. package/dist/types/test/needs.test.d.ts +1 -0
  108. package/dist/types/test/nested-rows.test.d.ts +1 -0
  109. package/dist/types/test/on-mounted.test.d.ts +1 -0
  110. package/dist/types/test/on-update.test.d.ts +1 -0
  111. package/dist/types/test/page-island.test.d.ts +1 -0
  112. package/dist/types/test/pages.test.d.ts +1 -0
  113. package/dist/types/test/reactivity.test.d.ts +1 -0
  114. package/dist/types/test/rebuild-keeps-logic.test.d.ts +1 -0
  115. package/dist/types/test/rebuild.test.d.ts +1 -0
  116. package/dist/types/test/reenumerate.test.d.ts +1 -0
  117. package/dist/types/test/refresh.test.d.ts +1 -0
  118. package/dist/types/test/refs.test.d.ts +1 -0
  119. package/dist/types/test/reorder.test.d.ts +1 -0
  120. package/dist/types/test/route.test.d.ts +1 -0
  121. package/dist/types/test/routes-build.test.d.ts +1 -0
  122. package/dist/types/test/routes-dev.test.d.ts +1 -0
  123. package/dist/types/test/routes-emit.test.d.ts +1 -0
  124. package/dist/types/test/routes-runtime.test.d.ts +1 -0
  125. package/dist/types/test/setup.d.ts +59 -0
  126. package/dist/types/test/show.test.d.ts +1 -0
  127. package/dist/types/test/skeleton-adoption.test.d.ts +1 -0
  128. package/dist/types/test/slots.test.d.ts +1 -0
  129. package/dist/types/test/specifiers.test.d.ts +1 -0
  130. package/dist/types/test/ssg.test.d.ts +1 -0
  131. package/dist/types/test/state.test.d.ts +1 -0
  132. package/dist/types/test/static-islands.test.d.ts +1 -0
  133. package/dist/types/test/stats.test.d.ts +1 -0
  134. package/dist/types/test/structural.test.d.ts +1 -0
  135. package/dist/types/test/style-sources.test.d.ts +1 -0
  136. package/dist/types/test/styles.test.d.ts +1 -0
  137. package/dist/types/test/surgical-rebuild.test.d.ts +1 -0
  138. package/dist/types/test/tables.test.d.ts +1 -0
  139. package/dist/types/test/unmount.test.d.ts +1 -0
  140. package/dist/types/test/verbatim.test.d.ts +1 -0
  141. package/dist/vite.js +5655 -0
  142. package/package.json +64 -0
@@ -0,0 +1,34 @@
1
+ export interface ComponentDefinition {
2
+ id: string;
3
+ template: string;
4
+ /** Static placeholder shown until this component hydrates. */
5
+ skeleton?: string;
6
+ /**
7
+ * Cell keys the component declared it cannot render without.
8
+ *
9
+ * Travels on the definition so a *usage* of it inherits the wait: a caller
10
+ * should not have to know the child's data dependencies, which is the
11
+ * coupling a prop chain has. Merged with any `$needs` the placement adds.
12
+ */
13
+ needs?: string[];
14
+ style: string;
15
+ /**
16
+ * The component's compiled logic.
17
+ *
18
+ * A real function built by the compiler, not a string evaluated at mount:
19
+ * that is what lets logic `import` project modules and what keeps the
20
+ * browser free of `new Function`. See [[ADR-016]].
21
+ */
22
+ setup?: (context: Record<string, unknown>) => void;
23
+ /**
24
+ * Handler names an event expression is compiled with, in order.
25
+ *
26
+ * Fixed at build so the compiled signature cannot depend on what the
27
+ * instance happened to register. See [[ADR-016]].
28
+ */
29
+ handlers?: string[];
30
+ /** Per-component hash stamped on owned elements for style scoping. */
31
+ /** Deferred CSS, applied when the component hydrates. */
32
+ styleDeferred?: string;
33
+ scopeId?: string;
34
+ }
@@ -0,0 +1,251 @@
1
+ import { ComponentInstance, type ComponentDefinition } from "./components/index.js";
2
+ import type { MarkerRange } from "./dom/Mounter.js";
3
+ /**
4
+ * A mounted root. Hold onto it to unmount that island later.
5
+ *
6
+ * `instance` is replaced when the root is remounted (hot reload), so read it
7
+ * through the handle rather than caching it.
8
+ */
9
+ export interface MountHandle {
10
+ id: string;
11
+ componentId: string;
12
+ instance: ComponentInstance;
13
+ props: any;
14
+ /** Set when mounted imperatively into a container element. */
15
+ container?: Element;
16
+ /** Set when discovered as an island marker in markup. */
17
+ range?: MarkerRange;
18
+ /**
19
+ * Original slot markup. Kept as a string so a remount can rebuild the
20
+ * content it consumed.
21
+ */
22
+ slotHtml?: string;
23
+ }
24
+ export declare function registerPageExpressions(expressions?: Record<string, any>, writers?: Record<string, any>): void;
25
+ export declare class LightspeedRuntime {
26
+ private componentRegistry;
27
+ /** Keyed by instance id ("button#2"), not component id. */
28
+ private instances;
29
+ private globalSignals;
30
+ private cells;
31
+ private initialised;
32
+ private instanceCounter;
33
+ /**
34
+ * Independently mounted islands. Lightspeed does not own the page - a HUD, a
35
+ * form and an overlay are three roots that happen to share a runtime.
36
+ */
37
+ /**
38
+ * Component id to a dynamic import.
39
+ *
40
+ * Supplied by the build's virtual module. Each import is a chunk boundary,
41
+ * so a component's code arrives only when something mounts it - which is
42
+ * what makes `$hydrate` defer bytes rather than only execution.
43
+ */
44
+ private loaders;
45
+ private loading;
46
+ /** Skeleton markup and scope for components that have not loaded yet. */
47
+ private pageMetadata;
48
+ private roots;
49
+ private rootCounter;
50
+ private ssg;
51
+ /** Cancels for islands still waiting on a hydration trigger. */
52
+ private pendingIslands;
53
+ private reactiveProxy;
54
+ private updateScheduler;
55
+ private bindingEvaluator;
56
+ private eventBinder;
57
+ private globalFunctions;
58
+ /**
59
+ * Instances with `onMounted` callbacks that have not run yet.
60
+ *
61
+ * Order is mount order, which puts children before parents: a child mounts
62
+ * during its parent's collection, so it queues first. A parent that measures
63
+ * something a child rendered therefore sees it.
64
+ */
65
+ private pendingMounts;
66
+ /** Guards against a callback that mounts something re-entering the flush. */
67
+ private flushing;
68
+ private mounter;
69
+ private styleManager;
70
+ constructor(options?: {
71
+ ssg?: boolean;
72
+ });
73
+ /**
74
+ * Instances whose `onUpdate` is running, so their writes do not queue again.
75
+ *
76
+ * The write is not lost - the bindings evaluate immediately after, in this
77
+ * same pass. See the guard in the `ReactiveProxy` callback above.
78
+ */
79
+ private updating;
80
+ private runUpdateCallbacks;
81
+ /**
82
+ * Apply a batch of state changes.
83
+ *
84
+ * Each owner is the instance whose state changed, so only its bindings are
85
+ * re-evaluated. A null owner comes from the window-global `reactive()`,
86
+ * which has no instance - that falls back to updating the whole tree.
87
+ */
88
+ private handleUpdate;
89
+ registerComponent(component: ComponentDefinition): void;
90
+ /**
91
+ * Metadata the page inlined for components that may not have loaded.
92
+ *
93
+ * Read from the document rather than passed in, so an app never has to wire
94
+ * up something the build already knows.
95
+ */
96
+ private readPageMetadata;
97
+ /**
98
+ * Content the build resolved and baked into the page.
99
+ *
100
+ * Seeded in the constructor, so cells hold their values before anything
101
+ * mounts - `$needs` gates are already open, and what the client renders
102
+ * matches the HTML, so adoption does not bail out.
103
+ */
104
+ /**
105
+ * Resolve `route` from the URL, for a page whose paths were not knowable at
106
+ * build.
107
+ *
108
+ * A baked route ships its params in the state payload; a rewritten one ships
109
+ * only the pattern, because one file answers many URLs. Seeding the same
110
+ * cell either way is what lets a component read `shared.route.params.id`
111
+ * without knowing or caring which kind of page it is on.
112
+ *
113
+ * Runs before `setup()`, so the value is present for the first render rather
114
+ * than arriving as an update.
115
+ */
116
+ private readPageRoute;
117
+ private readPageState;
118
+ /** Hand the runtime a lazy import per component. */
119
+ useComponents(loaders: Record<string, () => Promise<any>>,
120
+ /**
121
+ * Expressions a page contributes, from its island markers.
122
+ *
123
+ * They belong to no component, so they arrive here - with the entry chunk
124
+ * that hydration itself comes from, which is the only place guaranteed to
125
+ * be present when the first island mounts.
126
+ */
127
+ page?: {
128
+ expressions?: Record<string, any>;
129
+ writers?: Record<string, any>;
130
+ }): void;
131
+ /**
132
+ * A component's definition, fetching it if it has not arrived yet.
133
+ *
134
+ * Concurrent requests share one import - two islands using the same
135
+ * component must not trigger two fetches.
136
+ */
137
+ private resolveComponent;
138
+ updateComponent(id: string, component: ComponentDefinition): void;
139
+ /**
140
+ * Create an instance and run its logic. Called by the mounter for every
141
+ * <lsp-mount> it encounters, so one definition yields many instances.
142
+ */
143
+ /**
144
+ * Let an instance hear about the objects it was handed.
145
+ *
146
+ * A form shares one mutable object - the parent owns it, children write into
147
+ * it with `$model`. Writing notifies whoever *wrapped* the object, which is
148
+ * the parent alone, so a sibling reading the same object kept rendering the
149
+ * previous value until something else happened to update it. Registering
150
+ * every holder is what makes a shared object behave like shared state.
151
+ */
152
+ private shareObjectProps;
153
+ private createInstance;
154
+ private createExecutionContext;
155
+ /** Whether this render declared any build-time content. */
156
+ hasBuildData(): boolean;
157
+ /** Wait for build-time content, and let a failure fail the build. */
158
+ settleBuildData(): Promise<void>;
159
+ /** Resolved build-time content, for the page to carry to the client. */
160
+ buildPayload(): Record<string, unknown>;
161
+ /** Seed content the build resolved, before anything mounts. */
162
+ /**
163
+ * What each mounted island would need the browser for, if anything.
164
+ *
165
+ * Per instance, and per instance only. What one island needs from another is
166
+ * visible in the markers rather than here - see `flattenStatic`.
167
+ */
168
+ hydrationReport(options?: {
169
+ routeIsLive?: boolean;
170
+ }): {
171
+ id: string;
172
+ componentId: string;
173
+ live: boolean;
174
+ reasons: string[];
175
+ }[];
176
+ seedState(values: Record<string, unknown>): void;
177
+ /**
178
+ * A shared cell, from outside any component.
179
+ *
180
+ * Needed by `$needs`, which is a promise that *someone* fills the cell - and
181
+ * that someone cannot be the gated component itself, whose logic has not run.
182
+ * A page-level script, an earlier island, or a cell initialiser fills it; this
183
+ * is how the first of those reaches it.
184
+ *
185
+ * Unscoped, so a subscription here lives until its returned unsubscribe is
186
+ * called. Component logic gets the instance-bound facade instead.
187
+ */
188
+ shared(key: string, init?: any, options?: any): {
189
+ key: string;
190
+ value: any;
191
+ readonly pending: boolean;
192
+ set: (next: any) => void;
193
+ subscribe: (listener: (value: any) => void) => () => void;
194
+ };
195
+ /**
196
+ * A cell facade bound to one instance, so manual subscriptions are dropped
197
+ * when that instance is destroyed - same contract as scoped signals.
198
+ */
199
+ private scopedCell;
200
+ private defaultProps;
201
+ /**
202
+ * Mount a component into a container as an independent root.
203
+ *
204
+ * Call it as many times as there are islands on the page. Roots share this
205
+ * runtime - and therefore signals and styles - but nothing else.
206
+ *
207
+ * Replaces the container's contents. Attaching to server-rendered markup
208
+ * instead is deferred until there is markup to attach to.
209
+ */
210
+ mount(componentId: string, target: Element | string, props?: any, slotHtml?: string): Promise<MountHandle | null>;
211
+ /** Tear down one root, leaving the others alone. */
212
+ unmount(handle: MountHandle): void;
213
+ private remountAll;
214
+ /**
215
+ * Find and mount every island in the markup.
216
+ *
217
+ * This is the primary path: a page - hand-written or server-rendered - marks
218
+ * its islands with <lsp-mount>, and nobody hand-maintains a list of mount()
219
+ * calls beside it. The same marker the compiler emits for a nested child in a
220
+ * template marks a top-level island in a page; one concept, two contexts.
221
+ *
222
+ * Only outermost markers are scanned. A marker nested inside another belongs
223
+ * to that island's tree and is mounted with it.
224
+ *
225
+ * The <lsp-mount> element is kept in the DOM as a wrapper rather than being
226
+ * replaced, giving a stable anchor for remounts and, later, for deferred
227
+ * hydration. It is styled `display: contents` so it does not affect layout.
228
+ */
229
+ hydrate(scope?: ParentNode): Promise<MountHandle[]>;
230
+ /**
231
+ * Run the `onMounted` callbacks whose nodes are now in the document.
232
+ *
233
+ * Drains rather than iterates: a callback is allowed to mount something,
234
+ * and whatever that queues belongs to the next pass rather than to a list
235
+ * being walked. Each callback is isolated - one that throws is the
236
+ * component's fault and must not stop the others, which is a real failure
237
+ * mode when a page has a dozen islands and one has a typo.
238
+ */
239
+ private flushMounted;
240
+ /** Bring a discovered island to life and register it as a root. */
241
+ private mountIsland;
242
+ private parseSlotHtml;
243
+ private resolveTarget;
244
+ private unregisterSubtree;
245
+ /** Convenience for the single-root case. */
246
+ init(rootComponentId?: string, target?: Element | string): Promise<void>;
247
+ destroy(): void;
248
+ }
249
+ export declare function createLightspeedApp(options?: {
250
+ ssg?: boolean;
251
+ }): LightspeedRuntime;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One place that decides what the runtime says, and when.
3
+ *
4
+ * The distinction that matters is **fault versus chatter**, not dev versus
5
+ * production:
6
+ *
7
+ * - A *fault* means the page will not behave as written - a binding that threw,
8
+ * a component that could not be found, an island that had to be rebuilt.
9
+ * These are always reported. Hiding them behind a DEV flag is how a page ends
10
+ * up quietly broken in the one environment users see: adoption bail-outs were
11
+ * DEV-gated, so a bug that left *nothing on a built page interactive* reported
12
+ * zero problems while six components failed. Twice this was chased down the
13
+ * wrong path because the evidence was suppressed.
14
+ *
15
+ * - *Chatter* is useful while building and noise otherwise - "mounted x",
16
+ * "styles updated". That stays behind DEV.
17
+ *
18
+ * Everything carries a prefix so it can be filtered, and so a warning from
19
+ * Lightspeed is distinguishable from one the application produced.
20
+ */
21
+ /** Something is wrong and the page will not behave as written. */
22
+ export declare function warn(message: string, ...detail: unknown[]): void;
23
+ /** Something failed outright. */
24
+ export declare function fail(message: string, ...detail: unknown[]): void;
25
+ /**
26
+ * Extra context for a fault - a compiled source dump, a full stack.
27
+ *
28
+ * Reported only in dev, because it is unreadable in a production console and
29
+ * can be large. The fault itself is always reported first.
30
+ */
31
+ export declare function detail(message: string, ...args: unknown[]): void;
32
+ /** Useful while developing, noise in production. */
33
+ export declare function debug(message: string, ...args: unknown[]): void;
@@ -0,0 +1,127 @@
1
+ import type { ComponentInstance } from "../components/ComponentInstance.js";
2
+ import type { Binding } from "../bindings/index.js";
3
+ /**
4
+ * Anything that can own bindings and be torn down: a component instance, or a
5
+ * single row of a `$for`.
6
+ */
7
+ export interface BindingHolder {
8
+ addBinding(binding: Binding): void;
9
+ addCleanup(cleanup: Function): void;
10
+ addBlock(block: ForBlock): void;
11
+ }
12
+ /**
13
+ * One iteration of a `$for`.
14
+ *
15
+ * Owns everything created for it, so removing an item tears down exactly that
16
+ * item's bindings, listeners, child components and nested blocks.
17
+ */
18
+ export declare class Row implements BindingHolder {
19
+ locals: any[];
20
+ key: string;
21
+ nodes: Node[];
22
+ bindings: Binding[];
23
+ blocks: ForBlock[];
24
+ cleanup: Function[];
25
+ instances: ComponentInstance[];
26
+ /** Loop variables in scope here: outer aliases first, then this row's item. */
27
+ constructor(locals: any[]);
28
+ addBinding(binding: Binding): void;
29
+ addCleanup(cleanup: Function): void;
30
+ addBlock(block: ForBlock): void;
31
+ get item(): any;
32
+ set item(value: any);
33
+ }
34
+ export interface RowRenderer {
35
+ createRow(block: ForBlock, item: any): Promise<Row>;
36
+ refreshRow(block: ForBlock, row: Row): void;
37
+ destroyRow(block: ForBlock, row: Row): void;
38
+ /**
39
+ * Rows have landed in the document.
40
+ *
41
+ * A block places its own nodes rather than going through `fillRange`, so
42
+ * without this nothing told the runtime that anything inside them was
43
+ * connected - and `onMounted` for a component in a row never ran.
44
+ */
45
+ connected?(): void;
46
+ }
47
+ /** `$for="n in 5"` and iterables, not just arrays. */
48
+ export declare function toIterable(value: any): any[];
49
+ /**
50
+ * A `$for` block: an anchor, a row template, and a keyed set of rows.
51
+ *
52
+ * Rows are always keyed. Unkeyed reuse looks fine for text but silently hands
53
+ * one row's carried state - a child component, an input's value, focus - to a
54
+ * different item. See ADR-007.
55
+ */
56
+ export declare class ForBlock {
57
+ owner: ComponentInstance;
58
+ anchor: Comment;
59
+ /**
60
+ * One template for `$for`; one per branch for an `$if` chain, indexed by
61
+ * the branch the condition selects.
62
+ */
63
+ templates: Element[];
64
+ /** null for `$if`, which has no loop variable. */
65
+ alias: string | null;
66
+ /** Alias names in scope from enclosing blocks, outermost first. */
67
+ params: string[];
68
+ /** Live reference to the enclosing row's locals, if nested. */
69
+ parentLocals: any[];
70
+ keyFor: ((locals: any[]) => any) | null;
71
+ private renderer;
72
+ /** `$if`: zero or one row, driven by a condition rather than a list. */
73
+ conditional: boolean;
74
+ /** The collection expression, kept so adoption can re-evaluate it. */
75
+ itemsExpr: string;
76
+ rows: Map<string, Row>;
77
+ order: string[];
78
+ private pendingItems;
79
+ private running;
80
+ private warnedDuplicates;
81
+ constructor(owner: ComponentInstance, anchor: Comment,
82
+ /**
83
+ * One template for `$for`; one per branch for an `$if` chain, indexed by
84
+ * the branch the condition selects.
85
+ */
86
+ templates: Element[],
87
+ /** null for `$if`, which has no loop variable. */
88
+ alias: string | null,
89
+ /** Alias names in scope from enclosing blocks, outermost first. */
90
+ params: string[],
91
+ /** Live reference to the enclosing row's locals, if nested. */
92
+ parentLocals: any[], keyFor: ((locals: any[]) => any) | null, renderer: RowRenderer,
93
+ /** `$if`: zero or one row, driven by a condition rather than a list. */
94
+ conditional?: boolean,
95
+ /** The collection expression, kept so adoption can re-evaluate it. */
96
+ itemsExpr?: string);
97
+ /**
98
+ * Turn a bound expression into the rows it implies.
99
+ *
100
+ * `$if` is the degenerate case - a block whose collection is empty or a
101
+ * single placeholder - so it needs no reconciliation logic of its own.
102
+ */
103
+ toItems(value: any): any[];
104
+ /** The markup a row should clone. */
105
+ templateFor(item: any): Element;
106
+ /**
107
+ * Queue a reconcile.
108
+ *
109
+ * Rows containing components mount asynchronously, so a reconcile can still
110
+ * be in flight when the collection changes again. Only the latest snapshot
111
+ * matters, so it replaces any queued one and the loop runs until settled.
112
+ */
113
+ schedule(items: any[]): void;
114
+ private drain;
115
+ /** Public so adoption can match rendered rows to the current data. */
116
+ keysFor(items: any[]): string[];
117
+ private reconcile;
118
+ /**
119
+ * Put rows in order before the anchor.
120
+ *
121
+ * Walks backwards so each row is inserted before the one after it, and skips
122
+ * nodes already in place. No longest-increasing-subsequence pass - simple
123
+ * moves are proportionate for the list sizes this targets.
124
+ */
125
+ private position;
126
+ destroy(): void;
127
+ }
@@ -0,0 +1,223 @@
1
+ import type { ComponentRegistry } from "../components/ComponentRegistry.js";
2
+ import type { ComponentInstance } from "../components/ComponentInstance.js";
3
+ import type { BindingEvaluator } from "../bindings/index.js";
4
+ import type { EventBinder } from "../events/index.js";
5
+ import { ForBlock, Row, type RowRenderer } from "./ForBlock.js";
6
+ import { type MarkerPayload } from "../../marker.js";
7
+ /** An island boundary found while walking, plus what sits between it. */
8
+ export interface MarkerRange {
9
+ start: Comment;
10
+ end: Comment;
11
+ payload: MarkerPayload;
12
+ /** Nodes between the comments - slot content, owned by the parent. */
13
+ slotNodes: Node[];
14
+ /**
15
+ * This range was found by adopting build-rendered DOM, so whatever sits
16
+ * between its comments is rendered output.
17
+ *
18
+ * Only *top-level* markers carry the `ssr` flag, so a nested island had no
19
+ * way to know its own content came from the build. A deferred one would
20
+ * rebuild itself when its trigger fired - throwing away correct DOM, losing
21
+ * focus and scroll, in the exact case ADR-011 exists to prevent.
22
+ */
23
+ adopted?: boolean;
24
+ }
25
+ export type InitInstance = (componentId: string, parent: ComponentInstance | null, props: any) => Promise<ComponentInstance | null>;
26
+ /** Drop an instance and everything below it from the runtime's registry. */
27
+ export type ReleaseInstance = (instance: ComponentInstance) => void;
28
+ /**
29
+ * Builds the instance tree and the DOM for it.
30
+ *
31
+ * A component's template is cloned per usage, so every <lsp-mount> becomes its
32
+ * own instance with its own state. The whole tree is built detached and
33
+ * inserted once, so nothing intermediate is ever painted.
34
+ */
35
+ export declare class Mounter implements RowRenderer {
36
+ private registry;
37
+ private bindings;
38
+ private events;
39
+ private initInstance;
40
+ private releaseInstance;
41
+ /** Fetches a component's definition if it has not arrived yet. */
42
+ private resolve;
43
+ /** Skeleton and scope for a component that has not been fetched. */
44
+ private metadata;
45
+ /** Build-time rendering: mount everything that is not waiting on data. */
46
+ private ssg;
47
+ /** Emits the `$shimmer` placeholder styles on first use. */
48
+ private styles;
49
+ constructor(registry: ComponentRegistry, bindings: BindingEvaluator, events: EventBinder, initInstance: InitInstance, releaseInstance?: ReleaseInstance,
50
+ /** Fetches a component's definition if it has not arrived yet. */
51
+ resolve?: (componentId: string) => Promise<any>,
52
+ /** Skeleton and scope for a component that has not been fetched. */
53
+ metadata?: (componentId: string) => {
54
+ scope?: string;
55
+ skeleton?: string;
56
+ } | null,
57
+ /** Build-time rendering: mount everything that is not waiting on data. */
58
+ ssg?: boolean,
59
+ /** Emits the `$shimmer` placeholder styles on first use. */
60
+ styles?: {
61
+ ensurePendingStyles(): void;
62
+ } | null);
63
+ mountRoot(componentId: string, container: Element, props?: any, slotNodes?: Node[]): Promise<ComponentInstance | null>;
64
+ mount(componentId: string, parent: ComponentInstance | null, props: any, slotNodes: Node[], preserved?: Map<string, Node[]>,
65
+ /** Server-rendered nodes to bind to instead of building fresh. */
66
+ adopt?: Node[],
67
+ /**
68
+ * Detaches nested build-rendered content, for the rebuild path only.
69
+ *
70
+ * Deliberately lazy. Doing it eagerly guts the very DOM adoption is about
71
+ * to walk: every nested marker comes back empty, every nested component
72
+ * bails out, and the page silently rebuilds everything. Only called if
73
+ * adoption is not attempted or does not succeed.
74
+ */
75
+ rescue?: () => Map<string, Node[]>): Promise<ComponentInstance | null>;
76
+ /** Remove an instance's DOM and tear down its subtree. */
77
+ unmount(instance: ComponentInstance): void;
78
+ private parseTemplate;
79
+ private fillSlots;
80
+ /**
81
+ * Walk a subtree, attaching bindings and listeners to the owning instance
82
+ * and queueing any nested mount points.
83
+ */
84
+ private collect;
85
+ /**
86
+ * Walk a node's children, treating an island boundary as a unit.
87
+ *
88
+ * Index-based rather than per-node, because a marker claims a *range* of
89
+ * siblings rather than a single element.
90
+ */
91
+ /**
92
+ * `$needs` on an ordinary element: hold its children until the cells resolve.
93
+ *
94
+ * The lightweight tier of ADR-010. A component with a `<skeleton>` is the
95
+ * answer when the placeholder deserves real markup; this is for the case
96
+ * where one value is outstanding and a component would be ceremony.
97
+ */
98
+ private collectNeeds;
99
+ private collectChildren;
100
+ /**
101
+ * Detach the content of every nested marker inside a range.
102
+ *
103
+ * Called before the range is rebuilt, while the rendered DOM is still there.
104
+ * Nodes are moved into a fragment rather than referenced, because filling the
105
+ * range removes anything still attached.
106
+ */
107
+ private rescueRendered;
108
+ private takeRescued;
109
+ /**
110
+ * Bind to server-rendered DOM instead of rebuilding it.
111
+ *
112
+ * Walks the template and the rendered nodes in lockstep. Every divergence -
113
+ * a different tag, a missing block anchor, the wrong number of rows - returns
114
+ * false, and the caller rebuilds that component the way it always has. So
115
+ * adoption can be a performance win but never a correctness risk: a mismatch
116
+ * costs a rebuild and a dev warning rather than leaving bindings pointed at
117
+ * DOM that does not match the data.
118
+ *
119
+ * That guard matters more since ADR-010, because most components now render
120
+ * real content at build. Anything time- or environment-dependent (a
121
+ * timestamp, a persisted cell) legitimately differs between build and client.
122
+ */
123
+ private adoptNodes;
124
+ /**
125
+ * Adopt a `$for` / `$if` block against its rendered anchor and rows.
126
+ *
127
+ * Returns how many rendered nodes were consumed, or -1 to bail out.
128
+ *
129
+ * Rather than rebuilding rows, this pre-populates the block so the first
130
+ * reconcile matches them by key and calls refreshRow - the existing keyed
131
+ * path does the rest.
132
+ */
133
+ private adoptBlock;
134
+ /**
135
+ * Called once nodes have been inserted somewhere connected to the document.
136
+ *
137
+ * The runtime uses it to run `onMounted`. Here rather than at the end of a
138
+ * mount, because a component built fresh lands in a *fragment* first - its
139
+ * nodes are not in the document until an ancestor is inserted, and a
140
+ * callback that measures anything before then measures nothing.
141
+ */
142
+ onConnected?: () => void;
143
+ /** Replace whatever sits between a marker pair. */
144
+ private fillRange;
145
+ private collectElement;
146
+ /**
147
+ * Turn `$for="item in expr"` into an anchored block.
148
+ *
149
+ * The element is replaced by a comment anchor and kept as the row template.
150
+ * A comment, not an element: a `$for` inside <tbody> or <select> cannot
151
+ * carry a wrapper, and unlike an empty text node a comment survives
152
+ * serialisation, which matters once markup is server-rendered.
153
+ */
154
+ private createBlock;
155
+ /**
156
+ * Hand a block the rows already in the document.
157
+ *
158
+ * Keys are recomputed from the current data rather than shipped, so the
159
+ * first reconcile matches these by key and refreshes them in place instead
160
+ * of building replacements.
161
+ */
162
+ private adoptRows;
163
+ /**
164
+ * Collect an `$if` / `$else-if` / `$else` chain from adjacent siblings.
165
+ *
166
+ * `nextElementSibling` skips the whitespace and comments between them. Each
167
+ * branch is detached as it is claimed, so a later walk cannot mistake a
168
+ * consumed `$else` for a stray one.
169
+ */
170
+ private gatherBranches;
171
+ createRow(block: ForBlock, item: any): Promise<Row>;
172
+ /** A block placed its rows in the document. See `RowRenderer.connected`. */
173
+ connected(): void;
174
+ refreshRow(block: ForBlock, row: Row): void;
175
+ destroyRow(block: ForBlock, row: Row): void;
176
+ private cellsProxy;
177
+ private applyDirective;
178
+ /**
179
+ * A bare name resolves to a handler; anything else is an expression.
180
+ *
181
+ * Expressions are what make per-row actions possible - `@click="remove(item)"`
182
+ * inside a `$for` needs the loop variable, which a handler name cannot carry.
183
+ * Handler names and loop aliases are passed as parameters so free identifiers
184
+ * in the expression resolve without a scope object or `with`.
185
+ */
186
+ private bindEvent;
187
+ private mountMarker;
188
+ /**
189
+ * Leave a skeleton between the markers and mount when the trigger fires.
190
+ *
191
+ * Slot content is detached first: rendering the skeleton clears the range,
192
+ * and those nodes already carry bindings the parent collected, so they must
193
+ * be preserved rather than rebuilt.
194
+ */
195
+ private deferMarker;
196
+ /** Evaluate a `$is` expression to a component id. */
197
+ private resolveDynamicId;
198
+ /** Show a component's static placeholder, scoped so its own styles apply. */
199
+ renderSkeleton(range: MarkerRange): void;
200
+ private completeMount;
201
+ /**
202
+ * Top-level island markers in a scope.
203
+ *
204
+ * Depth tracking rather than containment: comments have no children, so a
205
+ * nested marker is one that appears between another's boundaries.
206
+ */
207
+ findIslands(scope: ParentNode): MarkerRange[];
208
+ /** Mount a discovered island into its own boundary. */
209
+ mountRange(range: MarkerRange, props: any, slotNodes: Node[]): Promise<ComponentInstance | null>;
210
+ /** Attributes the parent wrote, applied to the child's root. */
211
+ private applyForwarded;
212
+ }
213
+ /**
214
+ * Collapse an if/else-if/else chain into one expression yielding the index of
215
+ * the winning branch, or -1 when none matches.
216
+ *
217
+ * Doing it in a single expression keeps "first truthy wins" in one place, and
218
+ * means the chain needs no evaluation machinery beyond an ordinary binding.
219
+ */
220
+ export declare function composeBranchExpression(branches: Array<{
221
+ condition: string | null;
222
+ el: Element;
223
+ }>): string;