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 +61 -24
- package/dist/rasti.js +11 -0
- package/dist/rasti.js.map +1 -1
- package/dist/rasti.min.js.map +1 -1
- package/es/Component.js +11 -0
- package/es/Component.js.map +1 -1
- package/lib/Component.cjs +11 -0
- package/lib/Component.cjs.map +1 -1
- package/package.json +1 -1
- package/src/Component.js +11 -0
- package/types/Component.d.ts +10 -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.1.3-alpha.
|
|
4
|
-
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.3-alpha.
|
|
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
|
-
|
|
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.
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
375
|
+
```
|
|
363
376
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
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
|
|
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,
|
|
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.
|