rasti 4.1.3 → 4.2.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/README.md +38 -18
- package/dist/rasti.js +7 -4
- package/dist/rasti.js.map +1 -1
- package/dist/rasti.min.js +1 -1
- package/dist/rasti.min.js.map +1 -1
- package/es/Component.js +7 -4
- package/es/Component.js.map +1 -1
- package/lib/Component.cjs +7 -4
- package/lib/Component.cjs.map +1 -1
- package/package.json +1 -1
- package/src/Component.js +7 -4
- package/types/Component.d.ts +11 -0
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.
|
|
4
|
-
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.2.0/docs/logo-dark.svg">
|
|
4
|
+
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.2.0/docs/logo.svg" height="120">
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
@@ -115,10 +115,11 @@ const Link = Component.create`
|
|
|
115
115
|
`;
|
|
116
116
|
|
|
117
117
|
// Create a Navigation component that renders Link components for each route.
|
|
118
|
+
// `html` builds a partial: a sub-template for lists and conditional blocks.
|
|
118
119
|
const Navigation = Component.create`
|
|
119
120
|
<nav>
|
|
120
|
-
${({ props,
|
|
121
|
-
({ label, href }) =>
|
|
121
|
+
${({ props, html }) => props.routes.map(
|
|
122
|
+
({ label, href }) => html`<${Link} href="${href}">${label}</${Link}>`
|
|
122
123
|
)}
|
|
123
124
|
</nav>
|
|
124
125
|
`;
|
|
@@ -253,13 +254,13 @@ type CardComponent = Component<CardProps>;
|
|
|
253
254
|
|
|
254
255
|
const Card = Component.create<CardProps>`
|
|
255
256
|
<div class="card">
|
|
256
|
-
<h2>${(
|
|
257
|
-
${(
|
|
257
|
+
<h2>${({ props }: CardComponent) => props.title}</h2>
|
|
258
|
+
${({ props }: CardComponent) => props.renderChildren?.()}
|
|
258
259
|
</div>
|
|
259
260
|
`;
|
|
260
261
|
```
|
|
261
262
|
|
|
262
|
-
The interpolations are typed with
|
|
263
|
+
The interpolations are typed by annotating their parameter with the component type (see [Typing template interpolations](#typing-template-interpolations)).
|
|
263
264
|
|
|
264
265
|
`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
266
|
|
|
@@ -347,15 +348,15 @@ type S = ComponentState<Counter>;
|
|
|
347
348
|
|
|
348
349
|
### Typing template interpolations
|
|
349
350
|
|
|
350
|
-
Functions inside a template are `any` — rasti can't infer them from the surrounding string.
|
|
351
|
+
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
352
|
|
|
352
353
|
> 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
354
|
|
|
354
|
-
| Interpolation |
|
|
355
|
+
| Interpolation | Called with | Type it with |
|
|
355
356
|
|---|---|---|
|
|
356
|
-
| Content `${fn}` or quoted attr `attr="${fn}"` |
|
|
357
|
-
| Unquoted `onX=${fn}` |
|
|
358
|
-
| Function passed to a child (`handler=${fn}`) |
|
|
357
|
+
| Content `${fn}` or quoted attr `attr="${fn}"` | The component, as its argument and as `this` | An arrow annotated with the component type: `({ props }: C) => …` |
|
|
358
|
+
| Unquoted `onX=${fn}` | `(event, component, matched)`, with `this` the component | `satisfies EventHandler<C, E>` |
|
|
359
|
+
| Function passed to a child (`handler=${fn}`) | Whatever the child calls it with | `satisfies ChildProps['handler']` |
|
|
359
360
|
|
|
360
361
|
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
362
|
|
|
@@ -367,7 +368,7 @@ type ToggleComponent = Component<ToggleProps, ToggleState>;
|
|
|
367
368
|
|
|
368
369
|
const Toggle = Component.create<ToggleProps, ToggleState>`
|
|
369
370
|
<button onClick=${(function() { this.state!.active = !this.state!.active; }) satisfies EventHandler<ToggleComponent, MouseEvent>}>
|
|
370
|
-
${(
|
|
371
|
+
${({ props, state }: ToggleComponent) => `${props.label}: ${state!.active ? 'on' : 'off'}`}
|
|
371
372
|
</button>
|
|
372
373
|
`.extend({
|
|
373
374
|
onCreate() { this.state = new ToggleState({ active: false }); }
|
|
@@ -376,10 +377,20 @@ const Toggle = Component.create<ToggleProps, ToggleState>`
|
|
|
376
377
|
|
|
377
378
|
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
|
|
|
379
|
-
|
|
380
|
+
**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
|
|
|
381
382
|
```ts
|
|
382
|
-
${({
|
|
383
|
+
${(function() { return this.props.label; }) satisfies RenderExpression<ToggleComponent>}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**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:
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
// Compiles, but the argument is the MouseEvent: `props` is undefined at runtime.
|
|
390
|
+
onClick=${({ props }: ToggleComponent) => console.log(props.label)}
|
|
391
|
+
|
|
392
|
+
// Rejected (TS2339): `props` does not exist on `MouseEvent`.
|
|
393
|
+
onClick=${(({ props }) => console.log(props.label)) satisfies EventHandler<ToggleComponent, MouseEvent>}
|
|
383
394
|
```
|
|
384
395
|
|
|
385
396
|
#### Templates that call the component's own methods
|
|
@@ -391,7 +402,7 @@ interface ListProps { items: string[]; handleSelect: (item: string) => void; }
|
|
|
391
402
|
|
|
392
403
|
class ListBase extends Component<ListProps> {
|
|
393
404
|
renderItems() {
|
|
394
|
-
return this.props.items.map((item) => this.
|
|
405
|
+
return this.props.items.map((item) => this.html`<li>${item}</li>`);
|
|
395
406
|
}
|
|
396
407
|
select(ev: MouseEvent) {
|
|
397
408
|
const li = (ev.target as HTMLElement).closest('li');
|
|
@@ -401,7 +412,7 @@ class ListBase extends Component<ListProps> {
|
|
|
401
412
|
|
|
402
413
|
const List = ListBase.create`
|
|
403
414
|
<ul onClick=${(function(ev) { this.select(ev); }) satisfies EventHandler<ListBase, MouseEvent>}>
|
|
404
|
-
${(
|
|
415
|
+
${(self: ListBase) => self.renderItems()}
|
|
405
416
|
</ul>
|
|
406
417
|
`;
|
|
407
418
|
|
|
@@ -417,9 +428,18 @@ For a function passed to a child, neither helper fits — its type comes from th
|
|
|
417
428
|
handleChange=${((checked) => model.toggleAll(checked)) satisfies ToggleAllProps['handleChange']}
|
|
418
429
|
```
|
|
419
430
|
|
|
431
|
+
A quoted value is the result of a function the parent runs, so what reaches the child is what that function returns. Annotate its return type with the child's prop: that checks the value, and it types the parameters of a callback returned by a thunk, which would otherwise be an implicit `any` (TS7006):
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
// where the child was created with Component.create<HeaderProps>`...`
|
|
435
|
+
handleAddTodo="${({ model }: AppComponent): HeaderProps['handleAddTodo'] => (title) => model!.addTodo(title)}"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`satisfies` on the returned callback does the same, `({ model }: AppComponent) => ((title) => model!.addTodo(title)) satisfies HeaderProps['handleAddTodo']`; the return type keeps the whole contract in the signature.
|
|
439
|
+
|
|
420
440
|
### Known limitations
|
|
421
441
|
|
|
422
|
-
- **Template interpolation callbacks are `any`**. Functions in ``Component.create`...` `` templates can't be inferred from the surrounding string — type them
|
|
442
|
+
- **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
443
|
- **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
444
|
- **`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
445
|
- **`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/dist/rasti.js
CHANGED
|
@@ -2445,6 +2445,8 @@
|
|
|
2445
2445
|
this.options = options;
|
|
2446
2446
|
// Bind `partial` method to `this`.
|
|
2447
2447
|
this.partial = this.partial.bind(this);
|
|
2448
|
+
// Expose `partial` as `html`, unless a subclass defines its own `html`.
|
|
2449
|
+
if (!('html' in this)) this.html = this.partial;
|
|
2448
2450
|
// Bind `onChange` method to `this`.
|
|
2449
2451
|
this.onChange = this.onChange.bind(this);
|
|
2450
2452
|
// Call lifecycle method.
|
|
@@ -2637,7 +2639,7 @@
|
|
|
2637
2639
|
* It will return a Partial object that preserves structure for position-based recycling.
|
|
2638
2640
|
* Components will be added as children by the parent component. Template strings literals
|
|
2639
2641
|
* will be marked as safe HTML to be rendered.
|
|
2640
|
-
* This method is bound to the component instance by default.
|
|
2642
|
+
* This method is bound to the component instance by default, and is also available as `this.html`.
|
|
2641
2643
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
2642
2644
|
* @param {...any} expressions - Template expressions.
|
|
2643
2645
|
* @return {Partial} Partial object containing strings and expressions.
|
|
@@ -3192,8 +3194,8 @@
|
|
|
3192
3194
|
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
3193
3195
|
* const Navigation = Component.create`
|
|
3194
3196
|
* <nav>
|
|
3195
|
-
* ${({ props,
|
|
3196
|
-
* item =>
|
|
3197
|
+
* ${({ props, html }) => props.items.map(
|
|
3198
|
+
* item => html`<${Button}>${item.label}</${Button}>`
|
|
3197
3199
|
* )}
|
|
3198
3200
|
* </nav>
|
|
3199
3201
|
* `;
|
|
@@ -3226,7 +3228,7 @@
|
|
|
3226
3228
|
* ```javascript
|
|
3227
3229
|
* class ListBase extends Component {
|
|
3228
3230
|
* renderItems() {
|
|
3229
|
-
* return this.props.items.map(item => this.
|
|
3231
|
+
* return this.props.items.map(item => this.html`<li>${item}</li>`);
|
|
3230
3232
|
* }
|
|
3231
3233
|
* }
|
|
3232
3234
|
* const List = ListBase.create`
|
|
@@ -3325,6 +3327,7 @@
|
|
|
3325
3327
|
* @property {Model} [model] A `Model` or any emitter object containing data and business logic. The component will listen to `change` events and call `onChange` lifecycle method.
|
|
3326
3328
|
* @property {Model} [state] A `Model` or any emitter object containing data and business logic, to be used as internal state. The component will listen to `change` events and call `onChange` lifecycle method.
|
|
3327
3329
|
* @property {Model} [props] Automatically created from any options not merged to the component instance. Contains props passed from parent component as a `Model`. The component will listen to `change` events on props and call `onChange` lifecycle method. When a component with a `key` is recycled during parent re-render, new props are automatically updated and any changes trigger a re-render.
|
|
3330
|
+
* @property {Function} html Alias of {@link #module_component__partial partial}, bound to the component instance. Not set when a subclass defines its own `html`.
|
|
3328
3331
|
* @see {@link #module_component_create Component.create}
|
|
3329
3332
|
* @example
|
|
3330
3333
|
* import { Component, Model } from 'rasti';
|