rasti 4.1.3-alpha.1 → 4.1.4

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.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">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.4/docs/logo-dark.svg">
4
+ <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.4/docs/logo.svg" height="120">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -253,13 +253,13 @@ type CardComponent = Component<CardProps>;
253
253
 
254
254
  const Card = Component.create<CardProps>`
255
255
  <div class="card">
256
- <h2>${(({ props }) => props.title) satisfies RenderExpression<CardComponent>}</h2>
257
- ${(({ props }) => props.renderChildren?.()) satisfies RenderExpression<CardComponent>}
256
+ <h2>${({ props }: CardComponent) => props.title}</h2>
257
+ ${({ props }: CardComponent) => props.renderChildren?.()}
258
258
  </div>
259
259
  `;
260
260
  ```
261
261
 
262
- The interpolations are typed with `satisfies` (see [Typing template interpolations](#typing-template-interpolations)).
262
+ The interpolations are typed by annotating their parameter with the component type (see [Typing template interpolations](#typing-template-interpolations)).
263
263
 
264
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:
265
265
 
@@ -347,15 +347,15 @@ type S = ComponentState<Counter>;
347
347
 
348
348
  ### Typing template interpolations
349
349
 
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.
350
+ Functions inside a template are `any` — rasti can't infer them from the surrounding string. How to type one depends on what rasti calls it with, and that depends on where it sits in the template.
351
351
 
352
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.
353
353
 
354
- | Interpolation | What it is | Type to use |
354
+ | Interpolation | Called with | Type it with |
355
355
  |---|---|---|
356
- | Content `${fn}` or quoted attr `attr="${fn}"` | Run on render; `this` and the argument are the component | `RenderExpression<C>` |
357
- | Unquoted `onX=${fn}` | DOM handler, called `(event, component, matched)` | `EventHandler<C, E>` |
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 |
356
+ | Content `${fn}` or quoted attr `attr="${fn}"` | The component, as its argument and as `this` | An arrow annotated with the component type: `({ props }: C) => …` |
357
+ | Unquoted `onX=${fn}` | `(event, component, matched)`, with `this` the component | `satisfies EventHandler<C, E>` |
358
+ | Function passed to a child (`handler=${fn}`) | Whatever the child calls it with | `satisfies ChildProps['handler']` |
359
359
 
360
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.
361
361
 
@@ -367,7 +367,7 @@ type ToggleComponent = Component<ToggleProps, ToggleState>;
367
367
 
368
368
  const Toggle = Component.create<ToggleProps, ToggleState>`
369
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>}
370
+ ${({ props, state }: ToggleComponent) => `${props.label}: ${state!.active ? 'on' : 'off'}`}
371
371
  </button>
372
372
  `.extend({
373
373
  onCreate() { this.state = new ToggleState({ active: false }); }
@@ -376,10 +376,16 @@ const Toggle = Component.create<ToggleProps, ToggleState>`
376
376
 
377
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
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`:
379
+ **Content and quoted attributes** receive the component as their argument, so annotating the parameter types everything they read. A `function` that reads `this` instead needs `satisfies RenderExpression<C>`, which types `this` as well.
380
+
381
+ **Unquoted handlers** receive the event first, not the component. An annotated parameter still compiles there, since the template's expressions are `any`, and fails at runtime. `satisfies` checks the function against what rasti passes it:
380
382
 
381
383
  ```ts
382
- ${({ props, state }: ToggleComponent) => `${props.label}: ${state!.active ? 'on' : 'off'}`}
384
+ // Compiles, but the argument is the MouseEvent: `props` is undefined at runtime.
385
+ onClick=${({ props }: ToggleComponent) => console.log(props.label)}
386
+
387
+ // Rejected (TS2339): `props` does not exist on `MouseEvent`.
388
+ onClick=${(({ props }) => console.log(props.label)) satisfies EventHandler<ToggleComponent, MouseEvent>}
383
389
  ```
384
390
 
385
391
  #### Templates that call the component's own methods
@@ -401,7 +407,7 @@ class ListBase extends Component<ListProps> {
401
407
 
402
408
  const List = ListBase.create`
403
409
  <ul onClick=${(function(ev) { this.select(ev); }) satisfies EventHandler<ListBase, MouseEvent>}>
404
- ${((self) => self.renderItems()) satisfies RenderExpression<ListBase>}
410
+ ${(self: ListBase) => self.renderItems()}
405
411
  </ul>
406
412
  `;
407
413
 
@@ -419,7 +425,7 @@ handleChange=${((checked) => model.toggleAll(checked)) satisfies ToggleAllProps[
419
425
 
420
426
  ### Known limitations
421
427
 
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)).
428
+ - **Template interpolation callbacks are `any`**. Functions in ``Component.create`...` `` templates can't be inferred from the surrounding string — type them by where they sit in the template (see [Typing template interpolations](#typing-template-interpolations)).
423
429
  - **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)).
424
430
  - **`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.
425
431
  - **`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).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "4.1.3-alpha.1",
3
+ "version": "4.1.4",
4
4
  "description": "Modern MVC for building user interfaces",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
@@ -1,7 +1,12 @@
1
1
  import View, { ViewOptions, Resolvable } from './View.js';
2
2
  import Model from './Model.js';
3
3
 
4
- export interface ComponentReservedOptions<S = any, M = any> extends ViewOptions<M, Component<any, S, M>> {
4
+ /** `P` types `this` in the hooks. It goes last so `ComponentReservedOptions<S, M>` keeps state and model first. */
5
+ export interface ComponentReservedOptions<
6
+ S = any,
7
+ M = any,
8
+ P = Record<string, any>,
9
+ > extends ViewOptions<M, Component<P, S, M>> {
5
10
  /**
6
11
  * A unique key to identify the component.
7
12
  * Components with keys are recycled when the same key is found in the previous render.
@@ -14,22 +19,22 @@ export interface ComponentReservedOptions<S = any, M = any> extends ViewOptions<
14
19
  */
15
20
  state?: S;
16
21
  /** Lifecycle hook called at the end of the constructor. */
17
- onCreate?: (this: Component<any, S, M>, ...args: any[]) => void;
22
+ onCreate?: (this: Component<P, S, M>, ...args: any[]) => void;
18
23
  /** Lifecycle hook called when `model`, `state` or `props` emits `change`. */
19
- onChange?: (this: Component<any, S, M>, ...args: any[]) => void;
24
+ onChange?: (this: Component<P, S, M>, ...args: any[]) => void;
20
25
  /** Lifecycle hook called after the first render (client only). */
21
- onHydrate?: (this: Component<any, S, M>) => void;
26
+ onHydrate?: (this: Component<P, S, M>) => void;
22
27
  /** Lifecycle hook called at the start of `recycle`, before any recycling happens. */
23
- onBeforeRecycle?: (this: Component<any, S, M>) => void;
28
+ onBeforeRecycle?: (this: Component<P, S, M>) => void;
24
29
  /** Lifecycle hook called after the component is recycled and props are updated. */
25
- onRecycle?: (this: Component<any, S, M>) => void;
30
+ onRecycle?: (this: Component<P, S, M>) => void;
26
31
  /** Lifecycle hook called at the start of `render` on update. */
27
- onBeforeUpdate?: (this: Component<any, S, M>) => void;
32
+ onBeforeUpdate?: (this: Component<P, S, M>) => void;
28
33
  /** Lifecycle hook called at the end of `render` on update. */
29
- onUpdate?: (this: Component<any, S, M>) => void;
34
+ onUpdate?: (this: Component<P, S, M>) => void;
30
35
  }
31
36
 
32
- export type ComponentOptions<P = Record<string, any>, S = any, M = any> = P & ComponentReservedOptions<S, M>;
37
+ export type ComponentOptions<P = Record<string, any>, S = any, M = any> = P & ComponentReservedOptions<S, M, P>;
33
38
 
34
39
  /** Marker type for strings that are safe to inject as HTML without sanitization. */
35
40
  export interface SafeHTML {
@@ -185,7 +190,10 @@ declare class Component<P = Record<string, any>, S = any, M = any> extends View<
185
190
  * - DOM event handlers via camelCased attributes (`onClick=${handler}`), delegated to the root.
186
191
  * - Returning a component instance (or array of them) adds it as a child.
187
192
  * - Use `<${Sub}>…</${Sub}>` syntax for child component tags.
188
- * - Called on a subclass, the new component extends it, so the template can use its methods.
193
+ * - Called on a subclass, the new component extends it and the result is that subclass,
194
+ * so the template can use its methods. `P`, `S` and `M` type the result when `create`
195
+ * is called on `Component`. A subclass does not take them: declare the arguments on
196
+ * the class (`class X extends Component<P, S, M>`) and call `X.create`.
189
197
  *
190
198
  * @example
191
199
  * const Button = Component.create`
@@ -200,11 +208,9 @@ declare class Component<P = Record<string, any>, S = any, M = any> extends View<
200
208
  strings: string | TemplateStringsArray | ((...args: any[]) => any),
201
209
  ...expressions: any[]
202
210
  ): T;
203
- /**
204
- * Creates a component from a template, typing its props, state and model explicitly.
205
- * See the overload above for the template syntax.
206
- */
211
+ /** Types props, state and model. Only `Component.create` takes these arguments; see above. */
207
212
  static create<P = Record<string, any>, S = any, M = any>(
213
+ this: typeof Component,
208
214
  strings: string | TemplateStringsArray | ((...args: any[]) => any),
209
215
  ...expressions: any[]
210
216
  ): typeof Component<P, S, M>;