rip-lang 3.13.134 → 3.13.136

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/src/types.js CHANGED
@@ -13,6 +13,52 @@
13
13
  // emitEnum() — the one CodeEmitter method for runtime enum output.
14
14
  // Enums cross into the grammar because they emit runtime JavaScript.
15
15
 
16
+ // ============================================================================
17
+ // Shared type declaration constants — single source of truth
18
+ // ============================================================================
19
+ // Used by emitTypes() for .d.ts emission and by compileForCheck() in
20
+ // typecheck.js for virtual .ts injection. Keeping them here eliminates
21
+ // divergence between what gets written to disk and what TS analyzes.
22
+
23
+ export const INTRINSIC_TYPE_DECLS = [
24
+ 'type __RipElementMap = HTMLElementTagNameMap & Omit<SVGElementTagNameMap, keyof HTMLElementTagNameMap>;',
25
+ 'type __RipTag = keyof __RipElementMap;',
26
+ "type __RipBrowserElement = Omit<HTMLElement, 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & { hidden: boolean | 'until-found'; setAttribute(qualifiedName: string, value: any): void; querySelector(selectors: string): __RipBrowserElement | null; querySelectorAll(selectors: string): NodeListOf<__RipBrowserElement>; closest(selectors: string): __RipBrowserElement | null; };",
27
+ "type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;",
28
+ "type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' | 'classList' | 'className' | 'nodeValue' | 'textContent' | 'innerHTML' | 'innerText' | 'outerHTML' | 'outerText' | 'scrollLeft' | 'scrollTop' ? never : K extends `on${string}` | `aria${string}Element` | `aria${string}Elements` ? never : T[K] extends (...args: any[]) => any ? never : (<V>() => V extends Pick<T, K> ? 1 : 2) extends (<V>() => V extends { -readonly [P in K]: T[P] } ? 1 : 2) ? K : never }[keyof T] & string;",
29
+ 'type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };',
30
+ 'type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[];',
31
+ 'type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };',
32
+ ];
33
+
34
+ export const INTRINSIC_FN_DECL = 'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;';
35
+
36
+ export const ARIA_TYPE_DECLS = [
37
+ 'type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };',
38
+ "declare const ARIA: {",
39
+ " bindPopover(open: boolean, popover: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, source?: (() => Element | null | undefined) | null): void;",
40
+ " bindDialog(open: boolean, dialog: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, dismissable?: boolean): void;",
41
+ " popupDismiss(open: boolean, popup: () => Element | null | undefined, close: () => void, els?: Array<() => Element | null | undefined>, repos?: (() => void) | null): void;",
42
+ " popupGuard(delay?: number): any;",
43
+ " listNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers): void;",
44
+ " rovingNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers, orientation?: 'vertical' | 'horizontal' | 'both'): void;",
45
+ " positionBelow(trigger: Element | null | undefined, popup: Element | null | undefined, gap?: number, setVisible?: boolean): void;",
46
+ " position(trigger: Element | null | undefined, floating: Element | null | undefined, opts?: any): void;",
47
+ " trapFocus(panel: Element | null | undefined): void;",
48
+ " wireAria(panel: Element, id: string): void;",
49
+ " lockScroll(instance: any): void;",
50
+ " unlockScroll(instance: any): void;",
51
+ " hasAnchor: boolean;",
52
+ " [key: string]: any;",
53
+ "};",
54
+ ];
55
+
56
+ export const SIGNAL_INTERFACE = 'interface Signal<T> { value: T; read(): T; lock(): Signal<T>; free(): Signal<T>; kill(): T; }';
57
+ export const SIGNAL_FN = 'declare function __state<T>(value: T | Signal<T>): Signal<T>;';
58
+ export const COMPUTED_INTERFACE = 'interface Computed<T> { readonly value: T; read(): T; lock(): Computed<T>; free(): Computed<T>; kill(): T; }';
59
+ export const COMPUTED_FN = 'declare function __computed<T>(fn: () => T): Computed<T>;';
60
+ export const EFFECT_FN = 'declare function __effect(fn: () => void | (() => void)): () => void;';
61
+
16
62
  // ============================================================================
17
63
  // installTypeSupport — adds rewriteTypes() to Lexer.prototype
18
64
  // ============================================================================
@@ -35,6 +81,7 @@ export function installTypeSupport(Lexer) {
35
81
 
36
82
  proto.rewriteTypes = function() {
37
83
  let tokens = this.tokens;
84
+ let typeRefNames = this.typeRefNames = new Set();
38
85
  let gen = (tag, val, origin) => {
39
86
  let t = [tag, val];
40
87
  t.pre = 0;
@@ -50,27 +97,33 @@ export function installTypeSupport(Lexer) {
50
97
  this.scanTokens((token, i, tokens) => {
51
98
  let tag = token[0];
52
99
 
53
- // ── Generic type parameters: DEF name<T>(...) ──────────────────────
100
+ // ── Generic type parameters: DEF name<T>(...) or Name<T> = component ──
54
101
  // (Generic params on type aliases are handled by the `type` keyword handler below)
55
102
  if (tag === 'IDENTIFIER') {
56
103
  let next = tokens[i + 1];
57
104
  if (next && next[0] === 'COMPARE' && next[1] === '<' && !next.spaced) {
58
105
  let isDef = tokens[i - 1]?.[0] === 'DEF';
59
106
  let genTokens = collectBalancedAngles(tokens, i + 1);
60
- if (genTokens && isDef) {
61
- if (!token.data) token.data = {};
62
- token.data.typeParams = buildTypeString(genTokens);
63
- tokens.splice(i + 1, genTokens.length);
64
- // After removing <T>, retag ( as CALL_START if it follows DEF IDENTIFIER
65
- if (tokens[i + 1]?.[0] === '(') {
66
- tokens[i + 1][0] = 'CALL_START';
67
- // Find matching ) and retag as CALL_END
68
- let d = 1, m = i + 2;
69
- while (m < tokens.length && d > 0) {
70
- if (tokens[m][0] === '(' || tokens[m][0] === 'CALL_START') d++;
71
- if (tokens[m][0] === ')' || tokens[m][0] === 'CALL_END') d--;
72
- if (d === 0) tokens[m][0] = 'CALL_END';
73
- m++;
107
+ if (genTokens) {
108
+ // Check for component pattern: Name<T> = component
109
+ let afterAngles = i + 1 + genTokens.length;
110
+ let isComponent = !isDef && tokens[afterAngles]?.[0] === '=' &&
111
+ tokens[afterAngles + 1]?.[0] === 'COMPONENT';
112
+ if (isDef || isComponent) {
113
+ if (!token.data) token.data = {};
114
+ token.data.typeParams = buildTypeString(genTokens);
115
+ tokens.splice(i + 1, genTokens.length);
116
+ // After removing <T>, retag ( as CALL_START if it follows DEF IDENTIFIER
117
+ if (isDef && tokens[i + 1]?.[0] === '(') {
118
+ tokens[i + 1][0] = 'CALL_START';
119
+ // Find matching ) and retag as CALL_END
120
+ let d = 1, m = i + 2;
121
+ while (m < tokens.length && d > 0) {
122
+ if (tokens[m][0] === '(' || tokens[m][0] === 'CALL_START') d++;
123
+ if (tokens[m][0] === ')' || tokens[m][0] === 'CALL_END') d--;
124
+ if (d === 0) tokens[m][0] = 'CALL_END';
125
+ m++;
126
+ }
74
127
  }
75
128
  }
76
129
  }
@@ -102,7 +155,7 @@ export function installTypeSupport(Lexer) {
102
155
  propName = 'returnType';
103
156
  } else if (prevToken[0] === 'PARAM_END') {
104
157
  // Return type on arrow function — scan forward to -> token
105
- let arrowIdx = i + 1 + typeTokens.length;
158
+ let arrowIdx = i + 1 + typeTokens.consumed;
106
159
  let arrowToken = tokens[arrowIdx];
107
160
  if (arrowToken && (arrowToken[0] === '->' || arrowToken[0] === '=>')) {
108
161
  target = arrowToken;
@@ -117,8 +170,13 @@ export function installTypeSupport(Lexer) {
117
170
  if (!target.data) target.data = {};
118
171
  target.data[propName] = typeStr;
119
172
 
173
+ // Track identifiers used in type annotations for import elision
174
+ for (let tt of typeTokens) {
175
+ if (tt[0] === 'IDENTIFIER') typeRefNames.add(tt[1]);
176
+ }
177
+
120
178
  // Remove :: and type tokens from stream
121
- let removeCount = 1 + typeTokens.length;
179
+ let removeCount = 1 + typeTokens.consumed;
122
180
  tokens.splice(i, removeCount);
123
181
  return 0;
124
182
  }
@@ -180,7 +238,7 @@ export function installTypeSupport(Lexer) {
180
238
 
181
239
  // Simple alias: type Name = type-expression
182
240
  let typeTokens = collectTypeExpression(tokens, afterEq);
183
- tokens.splice(removeFrom, afterEq + typeTokens.length - removeFrom, makeDecl(buildTypeString(typeTokens)));
241
+ tokens.splice(removeFrom, afterEq + typeTokens.consumed - removeFrom, makeDecl(buildTypeString(typeTokens)));
184
242
  return 0;
185
243
  }
186
244
 
@@ -225,6 +283,61 @@ export function installTypeSupport(Lexer) {
225
283
 
226
284
  return 1;
227
285
  });
286
+
287
+ // ── Second pass: detect bodiless typed DEF (overload signatures) ──────
288
+ // Pattern: DEF IDENTIFIER CALL_START ... CALL_END TERMINATOR (no INDENT body)
289
+ // These are type-only overload declarations — remove from token stream
290
+ // and emit as TYPE_DECL markers so emitTypes() can generate DTS lines.
291
+ for (let i = tokens.length - 1; i >= 0; i--) {
292
+ if (tokens[i][0] !== 'DEF') continue;
293
+ let nameToken = tokens[i + 1];
294
+ if (!nameToken || nameToken[0] !== 'IDENTIFIER') continue;
295
+
296
+ // Find CALL_END
297
+ let j = i + 2;
298
+ if (tokens[j]?.[0] !== 'CALL_START') continue;
299
+ let depth = 1;
300
+ j++;
301
+ while (j < tokens.length && depth > 0) {
302
+ if (tokens[j][0] === 'CALL_START') depth++;
303
+ if (tokens[j][0] === 'CALL_END') depth--;
304
+ j++;
305
+ }
306
+ // j is now past CALL_END
307
+ let callEndIdx = j - 1;
308
+
309
+ // Bodiless = next is TERMINATOR or EOF (not INDENT)
310
+ let next = tokens[j];
311
+ if (next && next[0] !== 'TERMINATOR') continue;
312
+
313
+ // Must have type annotations to qualify as an overload signature
314
+ let hasTypes = nameToken.data?.returnType;
315
+ if (!hasTypes) {
316
+ for (let k = i + 2; k <= callEndIdx; k++) {
317
+ if (tokens[k].data?.type) { hasTypes = true; break; }
318
+ }
319
+ }
320
+ if (!hasTypes) continue;
321
+
322
+ // Save the overload tokens for emitTypes' collectParams
323
+ let overloadTokens = tokens.slice(i, j + 1); // DEF through TERMINATOR
324
+
325
+ // Check for export before DEF
326
+ let exported = i >= 1 && tokens[i - 1]?.[0] === 'EXPORT';
327
+ let spliceFrom = exported ? i - 1 : i;
328
+ let spliceCount = (j + 1) - spliceFrom; // include TERMINATOR
329
+
330
+ let marker = gen('TYPE_DECL', nameToken[1], nameToken);
331
+ marker.data = {
332
+ name: nameToken[1],
333
+ kind: 'overload',
334
+ overloadTokens,
335
+ exported,
336
+ };
337
+ if (nameToken.data?.typeParams) marker.data.typeParams = nameToken.data.typeParams;
338
+
339
+ tokens.splice(spliceFrom, spliceCount, marker);
340
+ }
228
341
  };
229
342
  }
230
343
 
@@ -236,6 +349,7 @@ export function installTypeSupport(Lexer) {
236
349
  function collectTypeExpression(tokens, j) {
237
350
  let typeTokens = [];
238
351
  let depth = 0;
352
+ let startJ = j;
239
353
 
240
354
  while (j < tokens.length) {
241
355
  let t = tokens[j];
@@ -275,6 +389,17 @@ function collectTypeExpression(tokens, j) {
275
389
 
276
390
  // Delimiters that end the type at depth 0
277
391
  if (depth === 0) {
392
+ // After =>, INDENT wraps the return type body — collect through OUTDENT
393
+ if (tTag === 'INDENT' && typeTokens.length > 0 && typeTokens[typeTokens.length - 1][0] === '=>') {
394
+ j++; // skip INDENT
395
+ let nest = 1;
396
+ while (j < tokens.length && nest > 0) {
397
+ if (tokens[j][0] === 'INDENT') { nest++; j++; }
398
+ else if (tokens[j][0] === 'OUTDENT') { nest--; j++; }
399
+ else { typeTokens.push(tokens[j]); j++; }
400
+ }
401
+ continue;
402
+ }
278
403
  if (tTag === '=' || tTag === 'REACTIVE_ASSIGN' ||
279
404
  tTag === 'COMPUTED_ASSIGN' || tTag === 'READONLY_ASSIGN' ||
280
405
  tTag === 'EFFECT' || tTag === 'TERMINATOR' ||
@@ -290,6 +415,8 @@ function collectTypeExpression(tokens, j) {
290
415
  j++;
291
416
  }
292
417
 
418
+ typeTokens.consumed = j - startJ;
419
+
293
420
  return typeTokens;
294
421
  }
295
422
 
@@ -306,7 +433,8 @@ function buildTypeString(typeTokens) {
306
433
  .replace(/\s*,\s*/g, ', ')
307
434
  .replace(/\s*=>\s*/g, ' => ')
308
435
  .replace(/ :: /g, ': ')
309
- .replace(/:: /g, ': ');
436
+ .replace(/:: /g, ': ')
437
+ .replace(/ : /g, ': ');
310
438
  return typeStr;
311
439
  }
312
440
 
@@ -348,6 +476,32 @@ function collectStructuralType(tokens, indentIdx) {
348
476
  }
349
477
  if (t[0] === 'TERMINATOR') { j++; continue; }
350
478
 
479
+ // Index signature: [key: Type]: ValueType
480
+ if (depth === 1 && t[0] === '[') {
481
+ let sigTokens = [];
482
+ j++; // skip [
483
+ // Collect tokens through matching ]
484
+ while (j < tokens.length && tokens[j][0] !== ']') {
485
+ sigTokens.push(tokens[j]);
486
+ j++;
487
+ }
488
+ j++; // skip ]
489
+ // Skip : separator after ]
490
+ if (tokens[j]?.[1] === ':' || tokens[j]?.[0] === 'TYPE_ANNOTATION') j++;
491
+ // Collect value type
492
+ let valTypeTokens = [];
493
+ while (j < tokens.length) {
494
+ let pt = tokens[j];
495
+ if (pt[0] === 'TERMINATOR' || pt[0] === 'OUTDENT') break;
496
+ valTypeTokens.push(pt);
497
+ j++;
498
+ }
499
+ let sigStr = buildTypeString(sigTokens);
500
+ let valStr = buildTypeString(valTypeTokens);
501
+ props.push(`[${sigStr}]: ${valStr}`);
502
+ continue;
503
+ }
504
+
351
505
  // Collect a property line: name (? optional) : type
352
506
  // Property tokens can be PROPERTY, IDENTIFIER, or keyword tags whose
353
507
  // value is a valid identifier (e.g. RENDER "render" in interfaces).
@@ -385,6 +539,20 @@ function collectStructuralType(tokens, indentIdx) {
385
539
  let typeDepth = 0;
386
540
  while (j < tokens.length) {
387
541
  let pt = tokens[j];
542
+ // Nested structural type: `prop: type` followed by INDENT block
543
+ if (pt[0] === 'IDENTIFIER' && pt[1] === 'type' && tokens[j + 1]?.[0] === 'INDENT') {
544
+ j++; // skip 'type'
545
+ let nestedType = collectStructuralType(tokens, j);
546
+ propTypeTokens.push(['', nestedType]);
547
+ // Skip past the INDENT...OUTDENT block
548
+ let nd = 1; j++;
549
+ while (j < tokens.length && nd > 0) {
550
+ if (tokens[j][0] === 'INDENT') nd++;
551
+ if (tokens[j][0] === 'OUTDENT') nd--;
552
+ j++;
553
+ }
554
+ continue;
555
+ }
388
556
  if (pt[0] === 'INDENT') { typeDepth++; j++; continue; }
389
557
  if (pt[0] === 'OUTDENT') {
390
558
  if (typeDepth > 0) { typeDepth--; j++; continue; }
@@ -514,7 +682,19 @@ export function emitTypes(tokens, sexpr = null, source = '') {
514
682
  else if (body[c] === '}') { depth--; if (depth === 0) { firstTopClose = c; break; } }
515
683
  }
516
684
  if (firstTopClose === body.length - 1) {
517
- let props = body.slice(2, -2).split('; ').filter(p => p.trim());
685
+ // Depth-aware split on '; ' at top level only
686
+ let inner = body.slice(2, -2);
687
+ let props = [], start = 0, d = 0;
688
+ for (let c = 0; c < inner.length; c++) {
689
+ if (inner[c] === '{') d++;
690
+ else if (inner[c] === '}') d--;
691
+ else if (d === 0 && inner[c] === ';' && inner[c + 1] === ' ') {
692
+ props.push(inner.slice(start, c));
693
+ start = c + 2;
694
+ }
695
+ }
696
+ if (start < inner.length) props.push(inner.slice(start));
697
+ props = props.filter(p => p.trim());
518
698
  if (props.length > 0) {
519
699
  lines.push(`${indent()}${prefix}{`);
520
700
  indentLevel++;
@@ -839,7 +1019,21 @@ export function emitTypes(tokens, sexpr = null, source = '') {
839
1019
  let exp = (exported || data.exported) ? 'export ' : '';
840
1020
  let params = data.typeParams || '';
841
1021
 
842
- if (data.kind === 'interface') {
1022
+ if (data.kind === 'overload') {
1023
+ // Emit function overload signature from saved tokens
1024
+ let ot = data.overloadTokens;
1025
+ let nameToken = ot[1]; // DEF is [0], name is [1]
1026
+ let { params: paramList } = collectParams(ot, 2);
1027
+ let returnType = nameToken.data?.returnType;
1028
+ let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
1029
+ let declare = inClass ? '' : (exp ? '' : 'declare ');
1030
+ let typeParams = data.typeParams || '';
1031
+ if (inClass) {
1032
+ lines.push(`${indent()}${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
1033
+ } else {
1034
+ lines.push(`${indent()}${exp}${declare}function ${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
1035
+ }
1036
+ } else if (data.kind === 'interface') {
843
1037
  let ext = data.extends ? ` extends ${data.extends}` : '';
844
1038
  emitBlock(`${exp}interface ${data.name}${params}${ext} `, data.typeText || '{}', '');
845
1039
  } else {
@@ -1143,43 +1337,21 @@ export function emitTypes(tokens, sexpr = null, source = '') {
1143
1337
  // Prepend reactive type definitions if used
1144
1338
  let preamble = [];
1145
1339
  if (usesRipIntrinsicProps) {
1146
- preamble.push('type __RipElementMap = HTMLElementTagNameMap & Omit<SVGElementTagNameMap, keyof HTMLElementTagNameMap>;');
1147
- preamble.push('type __RipTag = keyof __RipElementMap;');
1148
- preamble.push("type __RipBrowserElement = Omit<HTMLElement, 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & { hidden: boolean | 'until-found'; setAttribute(qualifiedName: string, value: any): void; querySelector(selectors: string): __RipBrowserElement | null; querySelectorAll(selectors: string): NodeListOf<__RipBrowserElement>; closest(selectors: string): __RipBrowserElement | null; };");
1149
- preamble.push("type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;");
1150
- preamble.push("type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' ? never : T[K] extends (...args: any[]) => any ? never : K }[keyof T] & string;");
1151
- preamble.push('type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };');
1152
- preamble.push('type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[]'); preamble.push('type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };');
1340
+ preamble.push(...INTRINSIC_TYPE_DECLS);
1153
1341
  }
1154
1342
  if (/\bARIA\./.test(source)) {
1155
- preamble.push("type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };");
1156
- preamble.push("declare const ARIA: {");
1157
- preamble.push(" bindPopover(open: boolean, popover: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, source?: (() => Element | null | undefined) | null): void;");
1158
- preamble.push(" bindDialog(open: boolean, dialog: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, dismissable?: boolean): void;");
1159
- preamble.push(" popupDismiss(open: boolean, popup: () => Element | null | undefined, close: () => void, els?: Array<() => Element | null | undefined>, repos?: (() => void) | null): void;");
1160
- preamble.push(" popupGuard(delay?: number): any;");
1161
- preamble.push(" listNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers): void;");
1162
- preamble.push(" rovingNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers, orientation?: 'vertical' | 'horizontal' | 'both'): void;");
1163
- preamble.push(" positionBelow(trigger: Element | null | undefined, popup: Element | null | undefined, gap?: number, setVisible?: boolean): void;");
1164
- preamble.push(" position(trigger: Element | null | undefined, floating: Element | null | undefined, opts?: any): void;");
1165
- preamble.push(" trapFocus(panel: Element | null | undefined): void;");
1166
- preamble.push(" wireAria(panel: Element, id: string): void;");
1167
- preamble.push(" lockScroll(instance: any): void;");
1168
- preamble.push(" unlockScroll(instance: any): void;");
1169
- preamble.push(" hasAnchor: boolean;");
1170
- preamble.push(" [key: string]: any;");
1171
- preamble.push("};");
1343
+ preamble.push(...ARIA_TYPE_DECLS);
1172
1344
  }
1173
1345
  if (usesSignal) {
1174
- preamble.push('interface Signal<T> { value: T; read(): T; lock(): Signal<T>; free(): Signal<T>; kill(): T; }');
1175
- preamble.push('declare function __state<T>(value: T | Signal<T>): Signal<T>;');
1346
+ preamble.push(SIGNAL_INTERFACE);
1347
+ preamble.push(SIGNAL_FN);
1176
1348
  }
1177
1349
  if (usesComputed) {
1178
- preamble.push('interface Computed<T> { readonly value: T; read(): T; lock(): Computed<T>; free(): Computed<T>; kill(): T; }');
1179
- preamble.push('declare function __computed<T>(fn: () => T): Computed<T>;');
1350
+ preamble.push(COMPUTED_INTERFACE);
1351
+ preamble.push(COMPUTED_FN);
1180
1352
  }
1181
1353
  if (usesSignal || usesComputed) {
1182
- preamble.push('declare function __effect(fn: () => void | (() => void)): () => void;');
1354
+ preamble.push(EFFECT_FN);
1183
1355
  }
1184
1356
  if (preamble.length > 0) {
1185
1357
  preamble.push('');
@@ -1253,6 +1425,7 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
1253
1425
  let exported = false;
1254
1426
  let name = null;
1255
1427
  let compNode = null;
1428
+ let typeParams = '';
1256
1429
 
1257
1430
  if (head === 'export' && Array.isArray(sexpr[1])) {
1258
1431
  exported = true;
@@ -1260,11 +1433,13 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
1260
1433
  let innerHead = inner[0]?.valueOf?.() ?? inner[0];
1261
1434
  if (innerHead === '=' && Array.isArray(inner[2]) &&
1262
1435
  (inner[2][0]?.valueOf?.() ?? inner[2][0]) === 'component') {
1436
+ typeParams = inner[1]?.typeParams || '';
1263
1437
  name = inner[1]?.valueOf?.() ?? inner[1];
1264
1438
  compNode = inner[2];
1265
1439
  }
1266
1440
  } else if (head === '=' && Array.isArray(sexpr[2]) &&
1267
1441
  (sexpr[2][0]?.valueOf?.() ?? sexpr[2][0]) === 'component') {
1442
+ typeParams = sexpr[1]?.typeParams || '';
1268
1443
  name = sexpr[1]?.valueOf?.() ?? sexpr[1];
1269
1444
  compNode = sexpr[2];
1270
1445
  }
@@ -1361,7 +1536,7 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
1361
1536
  }
1362
1537
  }
1363
1538
 
1364
- lines.push(`${exp}declare class ${name} {`);
1539
+ lines.push(`${exp}declare class ${name}${typeParams} {`);
1365
1540
  if (publicProps.length > 0 || inheritedPropsType) {
1366
1541
  let propsOpt = hasRequired ? '' : '?';
1367
1542
  if (publicProps.length > 0) {
package/src/ui.rip CHANGED
@@ -798,6 +798,10 @@ export createRenderer = (opts = {}) ->
798
798
  mp.appendChild cached._target
799
799
  currentComponent = cached
800
800
  currentRoute = route.file
801
+ cached.params = params if params
802
+ cached.query = query if query
803
+ cached.mounted() if cached.mounted
804
+ cached.load!(params, query) if cached.load
801
805
  else
802
806
  pageWrapper = document.createElement('div')
803
807
  pageWrapper.setAttribute 'data-component', route.file