rasti 4.1.3-alpha.0 → 4.1.3-alpha.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.3-alpha.0/docs/logo-dark.svg">
4
- <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.3-alpha.0/docs/logo.svg" height="120">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.3-alpha.1/docs/logo-dark.svg">
4
+ <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.3-alpha.1/docs/logo.svg" height="120">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -248,14 +248,19 @@ new Plain({ anything: 'goes' }); // ✅
248
248
  When a component is used with inner content (`<${Card}>...</${Card}>`), rasti injects a `renderChildren` function into its props at runtime. Declare it in `P` to use it:
249
249
 
250
250
  ```ts
251
- const Card = Component.create<{ title: string; renderChildren?: () => any }>`
251
+ interface CardProps { title: string; renderChildren?: () => any; }
252
+ type CardComponent = Component<CardProps>;
253
+
254
+ const Card = Component.create<CardProps>`
252
255
  <div class="card">
253
- <h2>${({ props }) => props.title}</h2>
254
- ${({ props }) => props.renderChildren?.()}
256
+ <h2>${(({ props }) => props.title) satisfies RenderExpression<CardComponent>}</h2>
257
+ ${(({ props }) => props.renderChildren?.()) satisfies RenderExpression<CardComponent>}
255
258
  </div>
256
259
  `;
257
260
  ```
258
261
 
262
+ The interpolations are typed with `satisfies` (see [Typing template interpolations](#typing-template-interpolations)).
263
+
259
264
  `Component.extend` adds the object members to the instance type. Inside its methods, `this` is the extended component, and lifecycle overrides get their parameters typed automatically:
260
265
 
261
266
  ```ts
@@ -338,13 +343,13 @@ type P = ComponentProps<Counter>; // pass the instance; `ComponentProps<typeof C
338
343
  type S = ComponentState<Counter>;
339
344
  ```
340
345
 
341
- > Components made with `Component.create` are **values**, not types. To use one as a type — as with `Counter` above — add `type X = InstanceType<typeof X>` next to the definition, or write `InstanceType<typeof X>` inline. A `Model` subclass needs no alias, since `class` already declares both a value and a type.
346
+ > Components made with `Component.create` are **values**, not types. To use one as a type outside its own template — as with `Counter` above — add `type X = InstanceType<typeof X>` next to the definition, or write `InstanceType<typeof X>` inline. Inside its own template, use `Component<P, S, M>` instead (see [Typing template interpolations](#typing-template-interpolations)). A `Model` subclass needs no alias, since `class` already declares both a value and a type.
342
347
 
343
348
  ### Typing template interpolations
344
349
 
345
- Functions inside a template are `any` — rasti can't infer them from the surrounding string. Which type to use depends on how rasti treats the function (quoted attribute or content → run on render; unquoted attribute → passed as-is):
350
+ Functions inside a template are `any` — rasti can't infer them from the surrounding string. Type each one inline with `satisfies` and the helper that matches how rasti treats it. `satisfies` types the function's parameters and `this` from the helper, and checks the function against it.
346
351
 
347
- > Under `strict` / `noImplicitAny`, every interpolation callback **must** be annotated — an untyped parameter is an error (TS7031/TS7006), not a silent `any`. In non-strict mode typing is opt-in: annotate where you want safety and leave trivial ones as `any`.
352
+ > Under `strict` / `noImplicitAny`, an untyped interpolation callback is an error (TS7031/TS7006), not a silent `any`. In non-strict mode typing is opt-in.
348
353
 
349
354
  | Interpolation | What it is | Type to use |
350
355
  |---|---|---|
@@ -352,28 +357,59 @@ Functions inside a template are `any` — rasti can't infer them from the surrou
352
357
  | Unquoted `onX=${fn}` | DOM handler, called `(event, component, matched)` | `EventHandler<C, E>` |
353
358
  | Function passed to a child (`handler=${fn}`) | Becomes the child's prop; typed by the child, not this component | the child's prop signature |
354
359
 
355
- Three ways to apply them:
360
+ The component type `C` is `Component<P, S, M>` with the same generics passed to `create`, aliased next to the component. It is not a copy: it is the exact type of the component's instances.
356
361
 
357
362
  ```ts
358
- // `Home` is a value (made with Component.create), so alias it to use the name as a type:
359
- const Home = Component.create<{}, { location: string }>`<div></div>`.extend({
360
- close() { /* ... */ },
363
+ interface ToggleProps { label: string; }
364
+ class ToggleState extends Model<{ active: boolean }> {}
365
+ interface ToggleState { active: boolean; }
366
+ type ToggleComponent = Component<ToggleProps, ToggleState>;
367
+
368
+ const Toggle = Component.create<ToggleProps, ToggleState>`
369
+ <button onClick=${(function() { this.state!.active = !this.state!.active; }) satisfies EventHandler<ToggleComponent, MouseEvent>}>
370
+ ${(({ props, state }) => `${props.label}: ${state!.active ? 'on' : 'off'}`) satisfies RenderExpression<ToggleComponent>}
371
+ </button>
372
+ `.extend({
373
+ onCreate() { this.state = new ToggleState({ active: false }); }
361
374
  });
362
- type Home = InstanceType<typeof Home>;
375
+ ```
363
376
 
364
- // 1. Named const — cleanest for non-trivial handlers
365
- const onClick: EventHandler<Home, MouseEvent> = function(ev, self) {
366
- ev.preventDefault();
367
- self.close();
368
- };
377
+ Inside its own template, a component can't use `InstanceType<typeof Toggle>`: the type of `Toggle` depends on the template itself, so TypeScript reports a circular reference (TS7022).
378
+
379
+ In an arrow function, annotating the parameter is a lighter alternative. It types the argument but not `this`, so a `function` still needs `satisfies`:
380
+
381
+ ```ts
382
+ ${({ props, state }: ToggleComponent) => `${props.label}: ${state!.active ? 'on' : 'off'}`}
383
+ ```
384
+
385
+ #### Templates that call the component's own methods
369
386
 
370
- // 2. Inline with `satisfies` — checks + types the params without widening
371
- ${(({ state }) => state?.location) satisfies RenderExpression<Home>}
387
+ When the template calls the component's own methods, as in `(self) => self.renderItems()`, `Component<P, S, M>` doesn't have them. Declare them in a class and call `create` on it. The new component extends that class, so the class is the type to use:
372
388
 
373
- // 3. Bare annotation — lightest, just types the argument
374
- ${({ state }: Home) => state?.location}
389
+ ```ts
390
+ interface ListProps { items: string[]; handleSelect: (item: string) => void; }
391
+
392
+ class ListBase extends Component<ListProps> {
393
+ renderItems() {
394
+ return this.props.items.map((item) => this.partial`<li>${item}</li>`);
395
+ }
396
+ select(ev: MouseEvent) {
397
+ const li = (ev.target as HTMLElement).closest('li');
398
+ if (li) this.props.handleSelect(li.textContent!);
399
+ }
400
+ }
401
+
402
+ const List = ListBase.create`
403
+ <ul onClick=${(function(ev) { this.select(ev); }) satisfies EventHandler<ListBase, MouseEvent>}>
404
+ ${((self) => self.renderItems()) satisfies RenderExpression<ListBase>}
405
+ </ul>
406
+ `;
407
+
408
+ List.mount({ items: ['a', 'b'], handleSelect: (item) => console.log(item) }, document.body);
375
409
  ```
376
410
 
411
+ #### Functions passed to a child
412
+
377
413
  For a function passed to a child, neither helper fits — its type comes from the child's prop. Type it against that prop's declared type (rasti can't connect the attribute to the child, since both live inside the template string):
378
414
 
379
415
  ```ts
@@ -383,13 +419,14 @@ handleChange=${((checked) => model.toggleAll(checked)) satisfies ToggleAllProps[
383
419
 
384
420
  ### Known limitations
385
421
 
386
- - **Template interpolation callbacks are `any`**. Functions in `Component.create\`...\`` templates can't be inferred from the surrounding string — type them opt-in (see [Typing template interpolations](#typing-template-interpolations)).
422
+ - **Template interpolation callbacks are `any`**. Functions in ``Component.create`...` `` templates can't be inferred from the surrounding string — type them with `satisfies` (see [Typing template interpolations](#typing-template-interpolations)).
423
+ - **A component can't name its own type in its template**. `InstanceType<typeof X>` is circular there (TS7022). Use `Component<P, S, M>` (see [Typing template interpolations](#typing-template-interpolations)), or the class `create` is called on when the template calls its methods (see [Templates that call the component's own methods](#templates-that-call-the-components-own-methods)).
387
424
  - **`Model<A>` instance keys require declaration merging**. TypeScript can't add `A`'s keys to a `class extends Model<A>` automatically — see the `interface Todo extends TodoAttrs {}` pattern above.
388
425
  - **`this.$()` can return `null`**. It mirrors `querySelector`, so handle the empty case (`?.`) and pass a type argument to narrow the element: `this.$<HTMLInputElement>('input.edit')?.focus()`. `this.$$()` returns a `NodeListOf<HTMLElement>` (also narrowable).
389
426
  - **`this.model` / `this.state` are optional**. Both are `undefined` unless provided, so guard (`this.model?.foo`) or assert (`this.model!`) when you know one was passed. Both accept a Rasti `Model` or a model from another library (e.g. Backbone); Components subscribe to `change` events automatically when the object exposes `on`/`off`.
390
427
  - **`state` / `model` are raw generics, `props` is not**. `this.props` is *always* a `Model` built by rasti, so it's typed `Model<P> & P` (direct access to `P`'s keys). But `state` and `model` can be anything you provide — a Rasti `Model`, a Backbone model, a store, or a plain object — so they stay the raw generic. To read a typed `Model` state/model directly, define it as a named subclass with declaration merging and pass it as the `S`/`M` generic (see the `Scoreboard` example above) — no casts needed.
391
428
  - **Weak-type error on narrow props**. If a component's `P` has no required keys and you pass only options not declared in it, TypeScript reports *"has no properties in common"* (weak-type check). Fix: declare those options in `P` — non-reserved options become props at runtime.
392
- - **Instance fields set in `.extend` hooks need predeclaration**. `.extend` infers the instance type from the object's members only, so a field first assigned in `onCreate` (`this.router = ...`) isn't known. Predeclare it in the object: `router: null as unknown as Router`. For components with many instance fields, `class MyComponent extends Component<P, S>` is usually cleaner than `.extend`.
429
+ - **Instance fields set in `.extend` hooks need predeclaration**. `.extend` infers the instance type from the object's members only, so a field first assigned in `onCreate` (`this.router = ...`) isn't known. Predeclare it in the object: `router: null as unknown as Router`. For components with many instance fields, a class is usually cleaner than `.extend`: `declare router: Router` in the class body, assigned in `onCreate`, and `create` called on the class (see [Templates that call the component's own methods](#templates-that-call-the-components-own-methods)).
393
430
 
394
431
  ## Working with LLMs
395
432
 
package/dist/rasti.js CHANGED
@@ -3222,6 +3222,17 @@
3222
3222
  * renderChildren : () => 'Cancel'
3223
3223
  * }));
3224
3224
  * ```
3225
+ * - Called on a subclass, the new component extends it, so the template can use its methods.
3226
+ * ```javascript
3227
+ * class ListBase extends Component {
3228
+ * renderItems() {
3229
+ * return this.props.items.map(item => this.partial`<li>${item}</li>`);
3230
+ * }
3231
+ * }
3232
+ * const List = ListBase.create`
3233
+ * <ul>${(self) => self.renderItems()}</ul>
3234
+ * `;
3235
+ * ```
3225
3236
  * @static
3226
3237
  * @param {string|Function} strings - HTML template for the component or a function that mounts a sub component.
3227
3238
  * @param {...*} expressions - The expressions to be interpolated within the template.