rasti 4.0.0 → 4.1.0-alpha.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 +92 -8
- package/dist/rasti.js +48 -44
- 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 +15 -18
- package/es/Component.js.map +1 -1
- package/es/Model.js +3 -1
- package/es/Model.js.map +1 -1
- package/es/View.js +33 -28
- package/es/View.js.map +1 -1
- package/lib/Component.cjs +15 -18
- package/lib/Component.cjs.map +1 -1
- package/lib/Model.cjs +3 -1
- package/lib/Model.cjs.map +1 -1
- package/lib/View.cjs +33 -28
- package/lib/View.cjs.map +1 -1
- package/package.json +36 -26
- package/src/Component.js +13 -16
- package/src/Model.js +3 -1
- package/src/View.js +33 -25
- package/types/Component.d.ts +238 -0
- package/types/Emitter.d.ts +111 -0
- package/types/Model.d.ts +123 -0
- package/types/View.d.ts +173 -0
- package/types/index.d.ts +15 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import View, { ViewOptions } from './View.js';
|
|
2
|
+
import Model from './Model.js';
|
|
3
|
+
|
|
4
|
+
export interface ComponentReservedOptions<S = any, M = any> extends ViewOptions<M> {
|
|
5
|
+
/**
|
|
6
|
+
* A unique key to identify the component.
|
|
7
|
+
* Components with keys are recycled when the same key is found in the previous render.
|
|
8
|
+
* Unkeyed components are recycled based on type and position.
|
|
9
|
+
*/
|
|
10
|
+
key?: string;
|
|
11
|
+
/**
|
|
12
|
+
* A `Model` or any emitter object containing data and business logic, to be used as internal
|
|
13
|
+
* state. The component will listen to `change` events and call `onChange` lifecycle method.
|
|
14
|
+
*/
|
|
15
|
+
state?: S;
|
|
16
|
+
/** Lifecycle hook called at the end of the constructor. */
|
|
17
|
+
onCreate?: (...args: any[]) => void;
|
|
18
|
+
/** Lifecycle hook called when `model`, `state` or `props` emits `change`. */
|
|
19
|
+
onChange?: (...args: any[]) => void;
|
|
20
|
+
/** Lifecycle hook called after the first render (client only). */
|
|
21
|
+
onHydrate?: () => void;
|
|
22
|
+
/** Lifecycle hook called at the start of `recycle`, before any recycling happens. */
|
|
23
|
+
onBeforeRecycle?: () => void;
|
|
24
|
+
/** Lifecycle hook called after the component is recycled and props are updated. */
|
|
25
|
+
onRecycle?: () => void;
|
|
26
|
+
/** Lifecycle hook called at the start of `render` on update. */
|
|
27
|
+
onBeforeUpdate?: () => void;
|
|
28
|
+
/** Lifecycle hook called at the end of `render` on update. */
|
|
29
|
+
onUpdate?: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ComponentOptions<P = {}, S = any, M = any> = P & ComponentReservedOptions<S, M>;
|
|
33
|
+
|
|
34
|
+
/** Marker type for strings that are safe to inject as HTML without sanitization. */
|
|
35
|
+
export interface SafeHTML {
|
|
36
|
+
readonly __rastiSafeHTML: true;
|
|
37
|
+
toString(): string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A partial template produced by `this.partial` — preserves structure for position-based recycling. */
|
|
41
|
+
export interface Partial {
|
|
42
|
+
strings: TemplateStringsArray;
|
|
43
|
+
expressions: any[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper type for typing event handlers passed to component templates.
|
|
48
|
+
* `this` is bound to the component, and the handler also receives `(event, component, matched)`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const onClick: EventHandler<Todo, MouseEvent> = function(ev) {
|
|
52
|
+
* this.model.toggle();
|
|
53
|
+
* };
|
|
54
|
+
*/
|
|
55
|
+
export type EventHandler<C, E extends Event = Event> = (
|
|
56
|
+
this: C,
|
|
57
|
+
event: E,
|
|
58
|
+
component: C,
|
|
59
|
+
matched: Element,
|
|
60
|
+
) => void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Helper type for typing render expressions inside component templates.
|
|
64
|
+
* Rasti calls the expression with the component as argument **and** binds `this` to it,
|
|
65
|
+
* so both arrow and `function` forms are covered.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const renderTitle: RenderExpression<Todo> = ({ model }) => model.title;
|
|
69
|
+
* const renderTotal: RenderExpression<Todo> = function() { return this.props.total; };
|
|
70
|
+
*/
|
|
71
|
+
export type RenderExpression<C> = (this: C, component: C) => any;
|
|
72
|
+
|
|
73
|
+
/** Extracts the props type `P` from a Component subclass. */
|
|
74
|
+
export type Props<C> = C extends Component<infer P, any, any> ? P : never;
|
|
75
|
+
/** Extracts the state type `S` from a Component subclass. */
|
|
76
|
+
export type State<C> = C extends Component<any, infer S, any> ? S : never;
|
|
77
|
+
/** Extracts the model type `M` from a Component / View subclass. */
|
|
78
|
+
export type ComponentModel<C> = C extends Component<any, any, infer M> ? M : never;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Components are a special kind of `View` designed to be easily composable. Unlike views,
|
|
82
|
+
* which are render-agnostic, components have a specific set of rendering guidelines that
|
|
83
|
+
* allow for a more declarative development style.
|
|
84
|
+
*
|
|
85
|
+
* Components are defined with the {@link Component.create} static method, which takes a
|
|
86
|
+
* tagged template string or a function that returns another component.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* import { Component, Model } from 'rasti';
|
|
90
|
+
* const Timer = Component.create`
|
|
91
|
+
* <div>Seconds: <span>${({ model }) => model.seconds}</span></div>
|
|
92
|
+
* `;
|
|
93
|
+
* const model = new Model({ seconds: 0 });
|
|
94
|
+
* Timer.mount({ model }, document.body);
|
|
95
|
+
* setInterval(() => model.seconds++, 1000);
|
|
96
|
+
*/
|
|
97
|
+
declare class Component<P = {}, S = any, M = any> extends View<M> {
|
|
98
|
+
/**
|
|
99
|
+
* Mark a string as safe HTML to be rendered.
|
|
100
|
+
* Rasti marks string literals as safe automatically when a component is created or when
|
|
101
|
+
* using `this.partial`. Only use this manually when you're sure the string is safe.
|
|
102
|
+
*/
|
|
103
|
+
static markAsSafeHTML(value: string): SafeHTML;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Helper method used to extend a `Component`, creating a subclass.
|
|
107
|
+
* @param object Object with methods to add to the subclass, or a function that receives
|
|
108
|
+
* the parent prototype and returns such an object.
|
|
109
|
+
* @return The newly created Component subclass.
|
|
110
|
+
*/
|
|
111
|
+
static extend<T extends new (...args: any[]) => Component<any, any, any>>(
|
|
112
|
+
this: T,
|
|
113
|
+
object: object | ((proto: InstanceType<T>) => object),
|
|
114
|
+
): T;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mount the component into the DOM.
|
|
118
|
+
*
|
|
119
|
+
* - **Normal mount** (default): renders as HTML and appends it to `el`.
|
|
120
|
+
* - **Hydration mode**: assumes the DOM already contains the component's HTML (SSR).
|
|
121
|
+
*
|
|
122
|
+
* If `el` is omitted, the component is instantiated but not mounted.
|
|
123
|
+
*
|
|
124
|
+
* @param options Passed to the constructor.
|
|
125
|
+
* @param el Target DOM element.
|
|
126
|
+
* @param hydrate If `true`, hydrate an existing SSR tree instead of rendering from scratch.
|
|
127
|
+
* @return The component instance.
|
|
128
|
+
*/
|
|
129
|
+
static mount<T extends new (...args: any[]) => Component<any, any, any>>(
|
|
130
|
+
this: T,
|
|
131
|
+
options?: ConstructorParameters<T>[0],
|
|
132
|
+
el?: Element,
|
|
133
|
+
hydrate?: boolean,
|
|
134
|
+
): InstanceType<T>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Takes a tagged template string (or a function returning another component) and returns
|
|
138
|
+
* a new `Component` class.
|
|
139
|
+
*
|
|
140
|
+
* - The template outer tag and attributes define the view's root element.
|
|
141
|
+
* - Inner HTML becomes the view's template.
|
|
142
|
+
* - Function interpolations are evaluated on render, bound to the component instance.
|
|
143
|
+
* - DOM event handlers via camelCased attributes (`onClick=${handler}`), delegated to the root.
|
|
144
|
+
* - Returning a component instance (or array of them) adds it as a child.
|
|
145
|
+
* - Use `<${Sub}>…</${Sub}>` syntax for child component tags.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* const Button = Component.create`
|
|
149
|
+
* <button class="${({ props }) => props.className}"
|
|
150
|
+
* onClick=${function(event, self) { self.emit('click'); }}>
|
|
151
|
+
* ${({ props }) => props.label}
|
|
152
|
+
* </button>
|
|
153
|
+
* `;
|
|
154
|
+
*/
|
|
155
|
+
static create<P = Record<string, any>, S = any, M = any>(
|
|
156
|
+
strings: string | TemplateStringsArray | ((...args: any[]) => any),
|
|
157
|
+
...expressions: any[]
|
|
158
|
+
): typeof Component<P, S, M>;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Props passed from the parent component, stored as a `Model` for reactive updates.
|
|
162
|
+
* Accessible directly (`this.props.foo`) or via `Model` API (`this.props.get('foo')`).
|
|
163
|
+
*/
|
|
164
|
+
props: Model<P> & P;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A `Model` or any emitter object containing data and business logic, to be used as internal
|
|
168
|
+
* state. The component will listen to `change` events and call `onChange` lifecycle method.
|
|
169
|
+
*/
|
|
170
|
+
state?: S;
|
|
171
|
+
|
|
172
|
+
/** The original options object passed to the constructor. */
|
|
173
|
+
options: ComponentOptions<P, S, M>;
|
|
174
|
+
|
|
175
|
+
/** Template function returning the view's inner HTML. */
|
|
176
|
+
template: (...args: any[]) => string;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param options Component options. Keys `model`, `state`, `key`, `onCreate`, `onChange`,
|
|
180
|
+
* `onHydrate`, `onBeforeRecycle`, `onRecycle`, `onBeforeUpdate`, `onUpdate`, `onDestroy`
|
|
181
|
+
* are merged into `this`. Any remaining options become `this.props`.
|
|
182
|
+
*/
|
|
183
|
+
constructor(options?: ComponentOptions<P, S, M>, ...args: any[]);
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Tagged template helper bound to the component instance.
|
|
187
|
+
* Returns a `Partial` that preserves structure for position-based recycling.
|
|
188
|
+
* String literals are marked as safe HTML automatically.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* renderHeader() {
|
|
192
|
+
* return this.partial`<header><${Title}>${this.model.title}</${Title}></header>`;
|
|
193
|
+
* }
|
|
194
|
+
*/
|
|
195
|
+
partial(strings: TemplateStringsArray, ...expressions: any[]): Partial;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Subscribes to a `change` event on a model or emitter and invokes `onChange`.
|
|
199
|
+
* Cleaned up automatically on destroy. By default the component subscribes to
|
|
200
|
+
* `this.model`, `this.state` and `this.props`.
|
|
201
|
+
*/
|
|
202
|
+
subscribe(model: object, type?: string, listener?: (...args: any[]) => void): this;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Render the component.
|
|
206
|
+
*
|
|
207
|
+
* - **First render** (`this.el` absent): renders as a string inside a `DocumentFragment`
|
|
208
|
+
* and hydrates it. `onHydrate` is called.
|
|
209
|
+
* - **Update render** (`this.el` present): updates root attributes and interpolation content.
|
|
210
|
+
* `onBeforeUpdate` and `onUpdate` are called.
|
|
211
|
+
*/
|
|
212
|
+
render(): this;
|
|
213
|
+
|
|
214
|
+
/** Lifecycle. Called at the end of the constructor. Runs on both client and server. */
|
|
215
|
+
onCreate(...args: any[]): void;
|
|
216
|
+
|
|
217
|
+
/** Lifecycle. Called when model/state/props emit `change`. Default: triggers `render`. */
|
|
218
|
+
onChange(model: object, changed: object, ...args: any[]): void;
|
|
219
|
+
|
|
220
|
+
/** Lifecycle. Called after the first render hydrates the DOM. Client only. */
|
|
221
|
+
onHydrate(): void;
|
|
222
|
+
|
|
223
|
+
/** Lifecycle. Called at the start of `recycle`, before any recycling happens. */
|
|
224
|
+
onBeforeRecycle(): void;
|
|
225
|
+
|
|
226
|
+
/** Lifecycle. Called when the component is recycled and its props are updated. */
|
|
227
|
+
onRecycle(): void;
|
|
228
|
+
|
|
229
|
+
/** Lifecycle. Called at the start of `render` on update. */
|
|
230
|
+
onBeforeUpdate(): void;
|
|
231
|
+
|
|
232
|
+
/** Lifecycle. Called at the end of `render` on update. */
|
|
233
|
+
onUpdate(): void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
declare const _default: typeof Component;
|
|
237
|
+
export default _default;
|
|
238
|
+
export { Component };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export type EventMap = Record<string, (...args: any[]) => void>;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `Emitter` is a class that provides an easy way to implement the observer pattern
|
|
5
|
+
* in your applications.
|
|
6
|
+
*
|
|
7
|
+
* It can be extended to create new classes that have the ability to emit and bind custom named events.
|
|
8
|
+
* Emitter is used by `Model` and `View` classes, which inherit from it to implement
|
|
9
|
+
* event-driven functionality.
|
|
10
|
+
*
|
|
11
|
+
* ## Inverse of Control Pattern
|
|
12
|
+
*
|
|
13
|
+
* The Emitter class includes "inverse of control" methods (`listenTo`, `listenToOnce`, `stopListening`)
|
|
14
|
+
* that allow an object to manage its own listening relationships, making cleanup easier and
|
|
15
|
+
* preventing memory leaks.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { Emitter } from 'rasti';
|
|
19
|
+
* class ShoppingCart extends Emitter {
|
|
20
|
+
* constructor() {
|
|
21
|
+
* super();
|
|
22
|
+
* this.items = [];
|
|
23
|
+
* }
|
|
24
|
+
* addItem(item) {
|
|
25
|
+
* this.items.push(item);
|
|
26
|
+
* this.emit('itemAdded', item);
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
export default class Emitter<E extends EventMap = EventMap> {
|
|
31
|
+
/** Registered event listeners, keyed by event type. */
|
|
32
|
+
listeners?: { [K in keyof E]?: Array<E[K]> };
|
|
33
|
+
/** Active `listenTo` subscriptions, tracked so `stopListening` can clean them up. */
|
|
34
|
+
listeningTo?: Array<{
|
|
35
|
+
emitter: Emitter<any>;
|
|
36
|
+
type: string;
|
|
37
|
+
listener: (...args: any[]) => void;
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Adds event listener.
|
|
42
|
+
* @param type Type of the event (e.g. `change`).
|
|
43
|
+
* @param listener Callback function to be called when the event is emitted.
|
|
44
|
+
* @return A function to remove the listener.
|
|
45
|
+
* @example
|
|
46
|
+
* this.model.on('change', this.render.bind(this));
|
|
47
|
+
*/
|
|
48
|
+
on<K extends keyof E>(type: K, listener: E[K]): () => void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds event listener that executes once.
|
|
52
|
+
* @param type Type of the event (e.g. `change`).
|
|
53
|
+
* @param listener Callback function to be called when the event is emitted.
|
|
54
|
+
* @return A function to remove the listener.
|
|
55
|
+
* @example
|
|
56
|
+
* this.model.once('change', () => console.log('This will happen once'));
|
|
57
|
+
*/
|
|
58
|
+
once<K extends keyof E>(type: K, listener: E[K]): () => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Removes event listeners with flexible parameter combinations.
|
|
62
|
+
*
|
|
63
|
+
* - `off()` - Removes ALL listeners from this emitter
|
|
64
|
+
* - `off(type)` - Removes all listeners for the specified event type
|
|
65
|
+
* - `off(type, listener)` - Removes the specific listener for the specified event type
|
|
66
|
+
*/
|
|
67
|
+
off(): void;
|
|
68
|
+
off<K extends keyof E>(type: K, listener?: E[K]): void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Emits event of specified type. Listeners will receive specified arguments.
|
|
72
|
+
* @param type Type of the event (e.g. `change`).
|
|
73
|
+
* @param args Optional arguments to be passed to listeners.
|
|
74
|
+
* @example
|
|
75
|
+
* this.emit('change', { field: 'name', value: 'John' });
|
|
76
|
+
*/
|
|
77
|
+
emit<K extends keyof E>(type: K, ...args: Parameters<E[K]>): void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Listen to an event of another emitter (Inverse of Control pattern).
|
|
81
|
+
*
|
|
82
|
+
* Allows this object to track and clean up all its listeners at once via `stopListening()`.
|
|
83
|
+
* @param emitter The emitter to listen to.
|
|
84
|
+
* @param type The type of the event to listen to.
|
|
85
|
+
* @param listener The listener to call when the event is emitted.
|
|
86
|
+
* @return A function to stop listening to the event.
|
|
87
|
+
* @example
|
|
88
|
+
* this.listenTo(otherModel, 'change', this.render.bind(this));
|
|
89
|
+
*/
|
|
90
|
+
listenTo(emitter: Emitter<any>, type: string, listener: (...args: any[]) => void): () => void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Listen to an event of another emitter and remove the listener after it is called.
|
|
94
|
+
* Similar to `listenTo()` but the listener runs only once.
|
|
95
|
+
* @param emitter The emitter to listen to.
|
|
96
|
+
* @param type The type of the event to listen to.
|
|
97
|
+
* @param listener The listener to call when the event is emitted.
|
|
98
|
+
* @return A function to stop listening to the event.
|
|
99
|
+
*/
|
|
100
|
+
listenToOnce(emitter: Emitter<any>, type: string, listener: (...args: any[]) => void): () => void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stop listening to events from other emitters.
|
|
104
|
+
*
|
|
105
|
+
* - `stopListening()` - Stops listening to ALL events from ALL emitters
|
|
106
|
+
* - `stopListening(emitter)` - Stops listening to all events from the specified emitter
|
|
107
|
+
* - `stopListening(emitter, type)` - Stops listening to the specified event type from the specified emitter
|
|
108
|
+
* - `stopListening(emitter, type, listener)` - Stops the specific listener for the specific event
|
|
109
|
+
*/
|
|
110
|
+
stopListening(emitter?: Emitter<any>, type?: string, listener?: (...args: any[]) => void): void;
|
|
111
|
+
}
|
package/types/Model.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Emitter from './Emitter.js';
|
|
2
|
+
|
|
3
|
+
/** Extracts the attribute type `A` from a Model subclass. */
|
|
4
|
+
export type Attrs<M> = M extends Model<infer A> ? A : never;
|
|
5
|
+
|
|
6
|
+
export type ModelEvents<A> =
|
|
7
|
+
& {
|
|
8
|
+
change: (model: Model<A>, changed: Partial<A>, ...args: any[]) => void;
|
|
9
|
+
[event: string]: (...args: any[]) => void;
|
|
10
|
+
}
|
|
11
|
+
& {
|
|
12
|
+
[K in keyof A & string as `change:${K}`]: (model: Model<A>, value: A[K], ...args: any[]) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* - Orchestrates data and business logic.
|
|
17
|
+
* - Emits events when data changes.
|
|
18
|
+
*
|
|
19
|
+
* A `Model` manages an internal table of data attributes and triggers change events when
|
|
20
|
+
* any of its data is modified. Models may handle syncing data with a persistence layer.
|
|
21
|
+
*
|
|
22
|
+
* ## Construction Flow
|
|
23
|
+
* 1. `preinitialize()` is called with all constructor arguments
|
|
24
|
+
* 2. `this.defaults` are resolved (if function, it's called and bound to the model)
|
|
25
|
+
* 3. `parse()` is called with all constructor arguments to process the data
|
|
26
|
+
* 4. `this.attributes` is built by merging defaults and parsed data
|
|
27
|
+
* 5. Getters/setters are generated for each attribute to emit change events
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* import { Model } from 'rasti';
|
|
31
|
+
* class User extends Model {
|
|
32
|
+
* preinitialize() {
|
|
33
|
+
* this.defaults = { name: '', email: '', role: 'user' };
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
export default class Model<A = any> extends Emitter<ModelEvents<A>> {
|
|
38
|
+
/**
|
|
39
|
+
* Static property that defines a prefix for generated getters/setters.
|
|
40
|
+
* When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').
|
|
41
|
+
* @default ''
|
|
42
|
+
*/
|
|
43
|
+
static attributePrefix: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default attributes for the model, merged into `this.attributes` during construction.
|
|
47
|
+
* Can be a plain object, or a function (called bound to the instance) that returns the
|
|
48
|
+
* defaults. Assign it on the prototype, or via `this.defaults` inside
|
|
49
|
+
* `preinitialize`.
|
|
50
|
+
*/
|
|
51
|
+
defaults?: Partial<A> | (() => Partial<A>);
|
|
52
|
+
|
|
53
|
+
/** Primary data object holding the model attributes. */
|
|
54
|
+
attributes: A;
|
|
55
|
+
|
|
56
|
+
/** Object containing previous attributes when a change occurs. */
|
|
57
|
+
previous: Partial<A>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param attributes Primary data object containing model attributes.
|
|
61
|
+
* @param args Additional arguments passed to `preinitialize` and `parse`.
|
|
62
|
+
*/
|
|
63
|
+
constructor(attributes?: Partial<A>, ...args: any[]);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Called before any instantiation logic runs for the Model.
|
|
67
|
+
* Receives all constructor arguments, allowing for flexible initialization patterns.
|
|
68
|
+
* Use this to set up `defaults`, configure the model, or handle custom constructor arguments.
|
|
69
|
+
* @example
|
|
70
|
+
* class User extends Model {
|
|
71
|
+
* preinitialize(attributes, options = {}) {
|
|
72
|
+
* this.defaults = { name: '', role: options.defaultRole || 'user' };
|
|
73
|
+
* this.apiEndpoint = options.apiEndpoint || '/users';
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
*/
|
|
77
|
+
preinitialize(attributes?: Partial<A>, ...args: any[]): void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate getter/setter for the given attribute key to emit `change` events.
|
|
81
|
+
* Called internally by the constructor for each key in `this.attributes`.
|
|
82
|
+
* Override with an empty method if you don't want automatic getters/setters.
|
|
83
|
+
*/
|
|
84
|
+
defineAttribute(key: keyof A & string): void;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get an attribute from `this.attributes`.
|
|
88
|
+
* @param key Attribute key.
|
|
89
|
+
* @return The attribute value.
|
|
90
|
+
*/
|
|
91
|
+
get<K extends keyof A>(key: K): A[K];
|
|
92
|
+
get(key: string): any;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Set one or more attributes into `this.attributes` and emit change events.
|
|
96
|
+
* Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.
|
|
97
|
+
* Additional arguments are passed to change event listeners.
|
|
98
|
+
*
|
|
99
|
+
* Emits `change` (listeners receive `(model, changedAttributes, ...args)`) and
|
|
100
|
+
* `change:<attribute>` (listeners receive `(model, newValue, ...args)`).
|
|
101
|
+
*
|
|
102
|
+
* @return This model instance for chaining.
|
|
103
|
+
* @example
|
|
104
|
+
* model.set('name', 'Alice');
|
|
105
|
+
* model.set({ name: 'Alice', age: 30 });
|
|
106
|
+
* model.set('name', 'Bob', { silent: false });
|
|
107
|
+
*/
|
|
108
|
+
set<K extends keyof A>(key: K, value: A[K], ...args: any[]): this;
|
|
109
|
+
set(attrs: Partial<A>, ...args: any[]): this;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Transforms and validates data before it becomes model attributes.
|
|
113
|
+
* Called during construction with all constructor arguments.
|
|
114
|
+
* Override this method to transform incoming data, create nested models, or handle different data formats.
|
|
115
|
+
*/
|
|
116
|
+
parse(data?: Partial<A>, ...args: any[]): Partial<A>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Return object representation of the model to be used for JSON serialization.
|
|
120
|
+
* By default returns a copy of `this.attributes`.
|
|
121
|
+
*/
|
|
122
|
+
toJSON(): A;
|
|
123
|
+
}
|
package/types/View.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import Emitter from './Emitter.js';
|
|
2
|
+
|
|
3
|
+
export interface ViewOptions<M = any> {
|
|
4
|
+
el?: HTMLElement | (() => HTMLElement);
|
|
5
|
+
tag?: string | (() => string);
|
|
6
|
+
attributes?: Record<string, any> | (() => Record<string, any>);
|
|
7
|
+
events?: Record<string, string | Function> | (() => Record<string, string | Function>);
|
|
8
|
+
model?: M;
|
|
9
|
+
template?: (...args: any[]) => string;
|
|
10
|
+
onDestroy?: (...args: any[]) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* - Listens for changes and renders the UI.
|
|
15
|
+
* - Handles user input and interactivity.
|
|
16
|
+
* - Sends captured input to the model.
|
|
17
|
+
*
|
|
18
|
+
* A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.
|
|
19
|
+
* Each `View` has a root element, `this.el`, used for event delegation. All element lookups are scoped to it.
|
|
20
|
+
* If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { View, Model } from 'rasti';
|
|
24
|
+
* class Timer extends View {
|
|
25
|
+
* constructor(options) {
|
|
26
|
+
* super(options);
|
|
27
|
+
* this.model = new Model({ seconds: 0 });
|
|
28
|
+
* this.model.on('change:seconds', this.render.bind(this));
|
|
29
|
+
* }
|
|
30
|
+
* template(model) {
|
|
31
|
+
* return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;
|
|
32
|
+
* }
|
|
33
|
+
* render() {
|
|
34
|
+
* if (this.template) this.el.innerHTML = this.template(this.model);
|
|
35
|
+
* return this;
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*/
|
|
39
|
+
export default class View<M = any> extends Emitter {
|
|
40
|
+
/**
|
|
41
|
+
* Escape HTML entities in a string.
|
|
42
|
+
* Use this method to sanitize user-generated content before inserting it into the DOM.
|
|
43
|
+
*/
|
|
44
|
+
static sanitize(value: string): string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reset the unique ID counter to 0.
|
|
48
|
+
* Useful for server-side rendering scenarios to ensure generated unique IDs match the client,
|
|
49
|
+
* enabling seamless hydration of components.
|
|
50
|
+
*/
|
|
51
|
+
static resetUid(): void;
|
|
52
|
+
|
|
53
|
+
/** Root DOM element of the view. */
|
|
54
|
+
el: HTMLElement;
|
|
55
|
+
|
|
56
|
+
/** A model or any object containing data and business logic. */
|
|
57
|
+
model?: M;
|
|
58
|
+
|
|
59
|
+
/** Tag used to create the root element when `el` is not provided (default `div`). */
|
|
60
|
+
tag?: string | (() => string);
|
|
61
|
+
|
|
62
|
+
/** Attributes used to create the root element when `el` is not provided. */
|
|
63
|
+
attributes?: Record<string, any> | (() => Record<string, any>);
|
|
64
|
+
|
|
65
|
+
/** Declarative DOM event listeners in the form `{'event selector': listener}`. */
|
|
66
|
+
events?: Record<string, string | Function> | (() => Record<string, string | Function>);
|
|
67
|
+
|
|
68
|
+
/** Function returning the view's inner HTML, used by `render`. */
|
|
69
|
+
template?: (...args: any[]) => string;
|
|
70
|
+
|
|
71
|
+
/** Unique identifier for the view instance. */
|
|
72
|
+
uid: string;
|
|
73
|
+
|
|
74
|
+
/** Child views. Destroyed automatically when the parent is destroyed. */
|
|
75
|
+
children: View[];
|
|
76
|
+
|
|
77
|
+
/** Whether `destroy()` has been called on this view. */
|
|
78
|
+
destroyed: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Functions run on `destroy()`. Push cleanup callbacks here for external subscriptions
|
|
82
|
+
* Rasti doesn't manage (DOM events, timers, third-party libraries).
|
|
83
|
+
*/
|
|
84
|
+
destroyQueue: Array<() => void>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param options View options. The following keys are merged into the view instance:
|
|
88
|
+
* `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.
|
|
89
|
+
*/
|
|
90
|
+
constructor(options?: ViewOptions<M>, ...args: any[]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* If you define a preinitialize method, it will be invoked when the view is first created,
|
|
94
|
+
* before any instantiation logic is run. Receives all constructor arguments.
|
|
95
|
+
*/
|
|
96
|
+
preinitialize(options?: ViewOptions<M>, ...args: any[]): void;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns the first element that matches the selector, scoped to the view's root element
|
|
100
|
+
* (`this.el`), or `null` if none matches. Defaults to `HTMLElement`; pass a type argument
|
|
101
|
+
* to narrow (e.g. `this.$<HTMLInputElement>('input.edit')`).
|
|
102
|
+
*/
|
|
103
|
+
$<E extends Element = HTMLElement>(selector: string): E | null;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns a list of elements matching the selector, scoped to the view's root element
|
|
107
|
+
* (`this.el`). Defaults to `HTMLElement`; pass a type argument to narrow.
|
|
108
|
+
*/
|
|
109
|
+
$$<E extends Element = HTMLElement>(selector: string): NodeListOf<E>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Destroy the view.
|
|
113
|
+
* Destroys children views, undelegates events, stops listening to events, calls `onDestroy`.
|
|
114
|
+
* @return This view for chaining.
|
|
115
|
+
*/
|
|
116
|
+
destroy(...args: any[]): this;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Lifecycle method called after the view is destroyed. Override with your cleanup code.
|
|
120
|
+
*/
|
|
121
|
+
onDestroy(...args: any[]): void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Add a view as a child. Children are stored in `this.children` and destroyed when the parent is destroyed.
|
|
125
|
+
* @return The child view for chaining.
|
|
126
|
+
*/
|
|
127
|
+
addChild(child: View): View;
|
|
128
|
+
|
|
129
|
+
/** Call `destroy()` on children views. */
|
|
130
|
+
destroyChildren(): void;
|
|
131
|
+
|
|
132
|
+
/** Ensure the view has a unique id at `this.uid`. */
|
|
133
|
+
ensureUid(): void;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Ensure the view has a root element at `this.el`.
|
|
137
|
+
* Called from the constructor. Override for custom element-creation logic.
|
|
138
|
+
*/
|
|
139
|
+
ensureElement(): void;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a DOM element. Called from the constructor if `this.el` is undefined.
|
|
143
|
+
* @param tag Tag for the element (default `div`).
|
|
144
|
+
* @param attributes Attributes for the element.
|
|
145
|
+
*/
|
|
146
|
+
createElement(tag?: string, attributes?: Record<string, any>): HTMLElement;
|
|
147
|
+
|
|
148
|
+
/** Remove `this.el` from the DOM. */
|
|
149
|
+
removeElement(): this;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Provide declarative listeners for DOM events. The events object follows
|
|
153
|
+
* `{'event selector': 'listener'}` — listener may be a function or a method name on the view.
|
|
154
|
+
*
|
|
155
|
+
* Listener signature: `(event, view, matched)`.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* class Modal extends View {
|
|
159
|
+
* onClickOk() { this.close(); }
|
|
160
|
+
* }
|
|
161
|
+
* Modal.prototype.events = { 'click button.ok': 'onClickOk' };
|
|
162
|
+
*/
|
|
163
|
+
delegateEvents(events?: Record<string, string | Function>): this;
|
|
164
|
+
|
|
165
|
+
/** Removes all of the view's delegated events. */
|
|
166
|
+
undelegateEvents(): this;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Core render function. Override to populate the view's element (`this.el`) with HTML.
|
|
170
|
+
* Convention: always return `this`.
|
|
171
|
+
*/
|
|
172
|
+
render(): this;
|
|
173
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { default as Emitter, EventMap } from './Emitter.js';
|
|
2
|
+
export { default as Model, ModelEvents, Attrs } from './Model.js';
|
|
3
|
+
export { default as View, ViewOptions } from './View.js';
|
|
4
|
+
export {
|
|
5
|
+
default as Component,
|
|
6
|
+
ComponentOptions,
|
|
7
|
+
ComponentReservedOptions,
|
|
8
|
+
SafeHTML,
|
|
9
|
+
Partial,
|
|
10
|
+
EventHandler,
|
|
11
|
+
RenderExpression,
|
|
12
|
+
Props,
|
|
13
|
+
State,
|
|
14
|
+
ComponentModel,
|
|
15
|
+
} from './Component.js';
|