🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

rasti

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rasti - npm Package Compare versions

Comparing version
4.0.1
to
4.1.0-alpha.0
+238
types/Component.d.ts
import View, { ViewOptions } from './View.js';
import Model from './Model.js';
export interface ComponentReservedOptions<S = any, M = any> extends ViewOptions<M> {
/**
* A unique key to identify the component.
* Components with keys are recycled when the same key is found in the previous render.
* Unkeyed components are recycled based on type and position.
*/
key?: string;
/**
* 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.
*/
state?: S;
/** Lifecycle hook called at the end of the constructor. */
onCreate?: (...args: any[]) => void;
/** Lifecycle hook called when `model`, `state` or `props` emits `change`. */
onChange?: (...args: any[]) => void;
/** Lifecycle hook called after the first render (client only). */
onHydrate?: () => void;
/** Lifecycle hook called at the start of `recycle`, before any recycling happens. */
onBeforeRecycle?: () => void;
/** Lifecycle hook called after the component is recycled and props are updated. */
onRecycle?: () => void;
/** Lifecycle hook called at the start of `render` on update. */
onBeforeUpdate?: () => void;
/** Lifecycle hook called at the end of `render` on update. */
onUpdate?: () => void;
}
export type ComponentOptions<P = {}, S = any, M = any> = P & ComponentReservedOptions<S, M>;
/** Marker type for strings that are safe to inject as HTML without sanitization. */
export interface SafeHTML {
readonly __rastiSafeHTML: true;
toString(): string;
}
/** A partial template produced by `this.partial` — preserves structure for position-based recycling. */
export interface Partial {
strings: TemplateStringsArray;
expressions: any[];
}
/**
* Helper type for typing event handlers passed to component templates.
* `this` is bound to the component, and the handler also receives `(event, component, matched)`.
*
* @example
* const onClick: EventHandler<Todo, MouseEvent> = function(ev) {
* this.model.toggle();
* };
*/
export type EventHandler<C, E extends Event = Event> = (
this: C,
event: E,
component: C,
matched: Element,
) => void;
/**
* Helper type for typing render expressions inside component templates.
* Rasti calls the expression with the component as argument **and** binds `this` to it,
* so both arrow and `function` forms are covered.
*
* @example
* const renderTitle: RenderExpression<Todo> = ({ model }) => model.title;
* const renderTotal: RenderExpression<Todo> = function() { return this.props.total; };
*/
export type RenderExpression<C> = (this: C, component: C) => any;
/** Extracts the props type `P` from a Component subclass. */
export type Props<C> = C extends Component<infer P, any, any> ? P : never;
/** Extracts the state type `S` from a Component subclass. */
export type State<C> = C extends Component<any, infer S, any> ? S : never;
/** Extracts the model type `M` from a Component / View subclass. */
export type ComponentModel<C> = C extends Component<any, any, infer M> ? M : never;
/**
* Components are a special kind of `View` designed to be easily composable. Unlike views,
* which are render-agnostic, components have a specific set of rendering guidelines that
* allow for a more declarative development style.
*
* Components are defined with the {@link Component.create} static method, which takes a
* tagged template string or a function that returns another component.
*
* @example
* import { Component, Model } from 'rasti';
* const Timer = Component.create`
* <div>Seconds: <span>${({ model }) => model.seconds}</span></div>
* `;
* const model = new Model({ seconds: 0 });
* Timer.mount({ model }, document.body);
* setInterval(() => model.seconds++, 1000);
*/
declare class Component<P = {}, S = any, M = any> extends View<M> {
/**
* Mark a string as safe HTML to be rendered.
* Rasti marks string literals as safe automatically when a component is created or when
* using `this.partial`. Only use this manually when you're sure the string is safe.
*/
static markAsSafeHTML(value: string): SafeHTML;
/**
* Helper method used to extend a `Component`, creating a subclass.
* @param object Object with methods to add to the subclass, or a function that receives
* the parent prototype and returns such an object.
* @return The newly created Component subclass.
*/
static extend<T extends new (...args: any[]) => Component<any, any, any>>(
this: T,
object: object | ((proto: InstanceType<T>) => object),
): T;
/**
* Mount the component into the DOM.
*
* - **Normal mount** (default): renders as HTML and appends it to `el`.
* - **Hydration mode**: assumes the DOM already contains the component's HTML (SSR).
*
* If `el` is omitted, the component is instantiated but not mounted.
*
* @param options Passed to the constructor.
* @param el Target DOM element.
* @param hydrate If `true`, hydrate an existing SSR tree instead of rendering from scratch.
* @return The component instance.
*/
static mount<T extends new (...args: any[]) => Component<any, any, any>>(
this: T,
options?: ConstructorParameters<T>[0],
el?: Element,
hydrate?: boolean,
): InstanceType<T>;
/**
* Takes a tagged template string (or a function returning another component) and returns
* a new `Component` class.
*
* - The template outer tag and attributes define the view's root element.
* - Inner HTML becomes the view's template.
* - Function interpolations are evaluated on render, bound to the component instance.
* - DOM event handlers via camelCased attributes (`onClick=${handler}`), delegated to the root.
* - Returning a component instance (or array of them) adds it as a child.
* - Use `<${Sub}>…</${Sub}>` syntax for child component tags.
*
* @example
* const Button = Component.create`
* <button class="${({ props }) => props.className}"
* onClick=${function(event, self) { self.emit('click'); }}>
* ${({ props }) => props.label}
* </button>
* `;
*/
static create<P = Record<string, any>, S = any, M = any>(
strings: string | TemplateStringsArray | ((...args: any[]) => any),
...expressions: any[]
): typeof Component<P, S, M>;
/**
* Props passed from the parent component, stored as a `Model` for reactive updates.
* Accessible directly (`this.props.foo`) or via `Model` API (`this.props.get('foo')`).
*/
props: Model<P> & P;
/**
* 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.
*/
state?: S;
/** The original options object passed to the constructor. */
options: ComponentOptions<P, S, M>;
/** Template function returning the view's inner HTML. */
template: (...args: any[]) => string;
/**
* @param options Component options. Keys `model`, `state`, `key`, `onCreate`, `onChange`,
* `onHydrate`, `onBeforeRecycle`, `onRecycle`, `onBeforeUpdate`, `onUpdate`, `onDestroy`
* are merged into `this`. Any remaining options become `this.props`.
*/
constructor(options?: ComponentOptions<P, S, M>, ...args: any[]);
/**
* Tagged template helper bound to the component instance.
* Returns a `Partial` that preserves structure for position-based recycling.
* String literals are marked as safe HTML automatically.
*
* @example
* renderHeader() {
* return this.partial`<header><${Title}>${this.model.title}</${Title}></header>`;
* }
*/
partial(strings: TemplateStringsArray, ...expressions: any[]): Partial;
/**
* Subscribes to a `change` event on a model or emitter and invokes `onChange`.
* Cleaned up automatically on destroy. By default the component subscribes to
* `this.model`, `this.state` and `this.props`.
*/
subscribe(model: object, type?: string, listener?: (...args: any[]) => void): this;
/**
* Render the component.
*
* - **First render** (`this.el` absent): renders as a string inside a `DocumentFragment`
* and hydrates it. `onHydrate` is called.
* - **Update render** (`this.el` present): updates root attributes and interpolation content.
* `onBeforeUpdate` and `onUpdate` are called.
*/
render(): this;
/** Lifecycle. Called at the end of the constructor. Runs on both client and server. */
onCreate(...args: any[]): void;
/** Lifecycle. Called when model/state/props emit `change`. Default: triggers `render`. */
onChange(model: object, changed: object, ...args: any[]): void;
/** Lifecycle. Called after the first render hydrates the DOM. Client only. */
onHydrate(): void;
/** Lifecycle. Called at the start of `recycle`, before any recycling happens. */
onBeforeRecycle(): void;
/** Lifecycle. Called when the component is recycled and its props are updated. */
onRecycle(): void;
/** Lifecycle. Called at the start of `render` on update. */
onBeforeUpdate(): void;
/** Lifecycle. Called at the end of `render` on update. */
onUpdate(): void;
}
declare const _default: typeof Component;
export default _default;
export { Component };
export type EventMap = Record<string, (...args: any[]) => void>;
/**
* `Emitter` is a class that provides an easy way to implement the observer pattern
* in your applications.
*
* It can be extended to create new classes that have the ability to emit and bind custom named events.
* Emitter is used by `Model` and `View` classes, which inherit from it to implement
* event-driven functionality.
*
* ## Inverse of Control Pattern
*
* The Emitter class includes "inverse of control" methods (`listenTo`, `listenToOnce`, `stopListening`)
* that allow an object to manage its own listening relationships, making cleanup easier and
* preventing memory leaks.
*
* @example
* import { Emitter } from 'rasti';
* class ShoppingCart extends Emitter {
* constructor() {
* super();
* this.items = [];
* }
* addItem(item) {
* this.items.push(item);
* this.emit('itemAdded', item);
* }
* }
*/
export default class Emitter<E extends EventMap = EventMap> {
/** Registered event listeners, keyed by event type. */
listeners?: { [K in keyof E]?: Array<E[K]> };
/** Active `listenTo` subscriptions, tracked so `stopListening` can clean them up. */
listeningTo?: Array<{
emitter: Emitter<any>;
type: string;
listener: (...args: any[]) => void;
}>;
/**
* Adds event listener.
* @param type Type of the event (e.g. `change`).
* @param listener Callback function to be called when the event is emitted.
* @return A function to remove the listener.
* @example
* this.model.on('change', this.render.bind(this));
*/
on<K extends keyof E>(type: K, listener: E[K]): () => void;
/**
* Adds event listener that executes once.
* @param type Type of the event (e.g. `change`).
* @param listener Callback function to be called when the event is emitted.
* @return A function to remove the listener.
* @example
* this.model.once('change', () => console.log('This will happen once'));
*/
once<K extends keyof E>(type: K, listener: E[K]): () => void;
/**
* Removes event listeners with flexible parameter combinations.
*
* - `off()` - Removes ALL listeners from this emitter
* - `off(type)` - Removes all listeners for the specified event type
* - `off(type, listener)` - Removes the specific listener for the specified event type
*/
off(): void;
off<K extends keyof E>(type: K, listener?: E[K]): void;
/**
* Emits event of specified type. Listeners will receive specified arguments.
* @param type Type of the event (e.g. `change`).
* @param args Optional arguments to be passed to listeners.
* @example
* this.emit('change', { field: 'name', value: 'John' });
*/
emit<K extends keyof E>(type: K, ...args: Parameters<E[K]>): void;
/**
* Listen to an event of another emitter (Inverse of Control pattern).
*
* Allows this object to track and clean up all its listeners at once via `stopListening()`.
* @param emitter The emitter to listen to.
* @param type The type of the event to listen to.
* @param listener The listener to call when the event is emitted.
* @return A function to stop listening to the event.
* @example
* this.listenTo(otherModel, 'change', this.render.bind(this));
*/
listenTo(emitter: Emitter<any>, type: string, listener: (...args: any[]) => void): () => void;
/**
* Listen to an event of another emitter and remove the listener after it is called.
* Similar to `listenTo()` but the listener runs only once.
* @param emitter The emitter to listen to.
* @param type The type of the event to listen to.
* @param listener The listener to call when the event is emitted.
* @return A function to stop listening to the event.
*/
listenToOnce(emitter: Emitter<any>, type: string, listener: (...args: any[]) => void): () => void;
/**
* Stop listening to events from other emitters.
*
* - `stopListening()` - Stops listening to ALL events from ALL emitters
* - `stopListening(emitter)` - Stops listening to all events from the specified emitter
* - `stopListening(emitter, type)` - Stops listening to the specified event type from the specified emitter
* - `stopListening(emitter, type, listener)` - Stops the specific listener for the specific event
*/
stopListening(emitter?: Emitter<any>, type?: string, listener?: (...args: any[]) => void): void;
}
export { default as Emitter, EventMap } from './Emitter.js';
export { default as Model, ModelEvents, Attrs } from './Model.js';
export { default as View, ViewOptions } from './View.js';
export {
default as Component,
ComponentOptions,
ComponentReservedOptions,
SafeHTML,
Partial,
EventHandler,
RenderExpression,
Props,
State,
ComponentModel,
} from './Component.js';
import Emitter from './Emitter.js';
/** Extracts the attribute type `A` from a Model subclass. */
export type Attrs<M> = M extends Model<infer A> ? A : never;
export type ModelEvents<A> =
& {
change: (model: Model<A>, changed: Partial<A>, ...args: any[]) => void;
[event: string]: (...args: any[]) => void;
}
& {
[K in keyof A & string as `change:${K}`]: (model: Model<A>, value: A[K], ...args: any[]) => void;
};
/**
* - Orchestrates data and business logic.
* - Emits events when data changes.
*
* A `Model` manages an internal table of data attributes and triggers change events when
* any of its data is modified. Models may handle syncing data with a persistence layer.
*
* ## Construction Flow
* 1. `preinitialize()` is called with all constructor arguments
* 2. `this.defaults` are resolved (if function, it's called and bound to the model)
* 3. `parse()` is called with all constructor arguments to process the data
* 4. `this.attributes` is built by merging defaults and parsed data
* 5. Getters/setters are generated for each attribute to emit change events
*
* @example
* import { Model } from 'rasti';
* class User extends Model {
* preinitialize() {
* this.defaults = { name: '', email: '', role: 'user' };
* }
* }
*/
export default class Model<A = any> extends Emitter<ModelEvents<A>> {
/**
* Static property that defines a prefix for generated getters/setters.
* When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').
* @default ''
*/
static attributePrefix: string;
/**
* Default attributes for the model, merged into `this.attributes` during construction.
* Can be a plain object, or a function (called bound to the instance) that returns the
* defaults. Assign it on the prototype, or via `this.defaults` inside
* `preinitialize`.
*/
defaults?: Partial<A> | (() => Partial<A>);
/** Primary data object holding the model attributes. */
attributes: A;
/** Object containing previous attributes when a change occurs. */
previous: Partial<A>;
/**
* @param attributes Primary data object containing model attributes.
* @param args Additional arguments passed to `preinitialize` and `parse`.
*/
constructor(attributes?: Partial<A>, ...args: any[]);
/**
* Called before any instantiation logic runs for the Model.
* Receives all constructor arguments, allowing for flexible initialization patterns.
* Use this to set up `defaults`, configure the model, or handle custom constructor arguments.
* @example
* class User extends Model {
* preinitialize(attributes, options = {}) {
* this.defaults = { name: '', role: options.defaultRole || 'user' };
* this.apiEndpoint = options.apiEndpoint || '/users';
* }
* }
*/
preinitialize(attributes?: Partial<A>, ...args: any[]): void;
/**
* Generate getter/setter for the given attribute key to emit `change` events.
* Called internally by the constructor for each key in `this.attributes`.
* Override with an empty method if you don't want automatic getters/setters.
*/
defineAttribute(key: keyof A & string): void;
/**
* Get an attribute from `this.attributes`.
* @param key Attribute key.
* @return The attribute value.
*/
get<K extends keyof A>(key: K): A[K];
get(key: string): any;
/**
* Set one or more attributes into `this.attributes` and emit change events.
* Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.
* Additional arguments are passed to change event listeners.
*
* Emits `change` (listeners receive `(model, changedAttributes, ...args)`) and
* `change:<attribute>` (listeners receive `(model, newValue, ...args)`).
*
* @return This model instance for chaining.
* @example
* model.set('name', 'Alice');
* model.set({ name: 'Alice', age: 30 });
* model.set('name', 'Bob', { silent: false });
*/
set<K extends keyof A>(key: K, value: A[K], ...args: any[]): this;
set(attrs: Partial<A>, ...args: any[]): this;
/**
* Transforms and validates data before it becomes model attributes.
* Called during construction with all constructor arguments.
* Override this method to transform incoming data, create nested models, or handle different data formats.
*/
parse(data?: Partial<A>, ...args: any[]): Partial<A>;
/**
* Return object representation of the model to be used for JSON serialization.
* By default returns a copy of `this.attributes`.
*/
toJSON(): A;
}
import Emitter from './Emitter.js';
export interface ViewOptions<M = any> {
el?: HTMLElement | (() => HTMLElement);
tag?: string | (() => string);
attributes?: Record<string, any> | (() => Record<string, any>);
events?: Record<string, string | Function> | (() => Record<string, string | Function>);
model?: M;
template?: (...args: any[]) => string;
onDestroy?: (...args: any[]) => void;
}
/**
* - Listens for changes and renders the UI.
* - Handles user input and interactivity.
* - Sends captured input to the model.
*
* A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.
* Each `View` has a root element, `this.el`, used for event delegation. All element lookups are scoped to it.
* If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.
*
* @example
* import { View, Model } from 'rasti';
* class Timer extends View {
* constructor(options) {
* super(options);
* this.model = new Model({ seconds: 0 });
* this.model.on('change:seconds', this.render.bind(this));
* }
* template(model) {
* return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;
* }
* render() {
* if (this.template) this.el.innerHTML = this.template(this.model);
* return this;
* }
* }
*/
export default class View<M = any> extends Emitter {
/**
* Escape HTML entities in a string.
* Use this method to sanitize user-generated content before inserting it into the DOM.
*/
static sanitize(value: string): string;
/**
* Reset the unique ID counter to 0.
* Useful for server-side rendering scenarios to ensure generated unique IDs match the client,
* enabling seamless hydration of components.
*/
static resetUid(): void;
/** Root DOM element of the view. */
el: HTMLElement;
/** A model or any object containing data and business logic. */
model?: M;
/** Tag used to create the root element when `el` is not provided (default `div`). */
tag?: string | (() => string);
/** Attributes used to create the root element when `el` is not provided. */
attributes?: Record<string, any> | (() => Record<string, any>);
/** Declarative DOM event listeners in the form `{'event selector': listener}`. */
events?: Record<string, string | Function> | (() => Record<string, string | Function>);
/** Function returning the view's inner HTML, used by `render`. */
template?: (...args: any[]) => string;
/** Unique identifier for the view instance. */
uid: string;
/** Child views. Destroyed automatically when the parent is destroyed. */
children: View[];
/** Whether `destroy()` has been called on this view. */
destroyed: boolean;
/**
* Functions run on `destroy()`. Push cleanup callbacks here for external subscriptions
* Rasti doesn't manage (DOM events, timers, third-party libraries).
*/
destroyQueue: Array<() => void>;
/**
* @param options View options. The following keys are merged into the view instance:
* `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.
*/
constructor(options?: ViewOptions<M>, ...args: any[]);
/**
* If you define a preinitialize method, it will be invoked when the view is first created,
* before any instantiation logic is run. Receives all constructor arguments.
*/
preinitialize(options?: ViewOptions<M>, ...args: any[]): void;
/**
* Returns the first element that matches the selector, scoped to the view's root element
* (`this.el`), or `null` if none matches. Defaults to `HTMLElement`; pass a type argument
* to narrow (e.g. `this.$<HTMLInputElement>('input.edit')`).
*/
$<E extends Element = HTMLElement>(selector: string): E | null;
/**
* Returns a list of elements matching the selector, scoped to the view's root element
* (`this.el`). Defaults to `HTMLElement`; pass a type argument to narrow.
*/
$$<E extends Element = HTMLElement>(selector: string): NodeListOf<E>;
/**
* Destroy the view.
* Destroys children views, undelegates events, stops listening to events, calls `onDestroy`.
* @return This view for chaining.
*/
destroy(...args: any[]): this;
/**
* Lifecycle method called after the view is destroyed. Override with your cleanup code.
*/
onDestroy(...args: any[]): void;
/**
* Add a view as a child. Children are stored in `this.children` and destroyed when the parent is destroyed.
* @return The child view for chaining.
*/
addChild(child: View): View;
/** Call `destroy()` on children views. */
destroyChildren(): void;
/** Ensure the view has a unique id at `this.uid`. */
ensureUid(): void;
/**
* Ensure the view has a root element at `this.el`.
* Called from the constructor. Override for custom element-creation logic.
*/
ensureElement(): void;
/**
* Create a DOM element. Called from the constructor if `this.el` is undefined.
* @param tag Tag for the element (default `div`).
* @param attributes Attributes for the element.
*/
createElement(tag?: string, attributes?: Record<string, any>): HTMLElement;
/** Remove `this.el` from the DOM. */
removeElement(): this;
/**
* Provide declarative listeners for DOM events. The events object follows
* `{'event selector': 'listener'}` — listener may be a function or a method name on the view.
*
* Listener signature: `(event, view, matched)`.
*
* @example
* class Modal extends View {
* onClickOk() { this.close(); }
* }
* Modal.prototype.events = { 'click button.ok': 'onClickOk' };
*/
delegateEvents(events?: Record<string, string | Function>): this;
/** Removes all of the view's delegated events. */
undelegateEvents(): this;
/**
* Core render function. Override to populate the view's element (`this.el`) with HTML.
* Convention: always return `this`.
*/
render(): this;
}
+1
-1

@@ -1,2 +0,2 @@

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,function(t){"use strict";function e(t){if("function"!=typeof t){throw new TypeError("Event listener must be a function")}}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter(t=>t!==e),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach(t=>t(...e))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter(i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1)),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach(t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])});const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach(t=>{this.emit(`change:${t}`,this,i[t],...n)}),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach(e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))}),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach(t=>t()),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach(t=>t.destroy()),this.children=[]}ensureUid(){this.uid||(this.uid="r"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach(t=>s.setAttribute(t,e[t])),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();const s={};return Object.keys(t).forEach(i=>{const n=i.match(/^(\w+)(?:\s+(.+))*$/);if(!n){throw new Error(`Invalid event format: ${i}`)}const[,r,h]=n;let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push([h,o])}),Object.keys(s).forEach(t=>{const e=e=>{let i=e.target;for(;i;)i.matches&&s[t].forEach(([t,s])=>{(i===this.el&&!t||i!==this.el&&i.matches(t))&&s.call(this,e,this,i)}),i=i===this.el||e.cancelBubble?null:i.parentElement};this.delegatedEventListeners.push([t,e]),this.el.addEventListener(t,e)}),this}undelegateEvents(){return this.delegatedEventListeners.forEach(([t,e])=>{this.el.removeEventListener(t,e)}),this.delegatedEventListeners=[],this}render(){return this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"}[t]))}static resetUid(){h.uid=0}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}const c=["value","checked","selected"];let u=class{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach(i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))}),Object.keys(e).forEach(s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)}),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach(t=>{this.ref.removeAttribute(t),-1!==c.indexOf(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")}),Object.keys(s).forEach(t=>{const e=s[t];this.ref.setAttribute(t,e),-1!==c.indexOf(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)})}};class d{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}hasSingleComponent(){if(1!==this.tracked.size||1!==this.previous.size)return!1;const[t,e]=this.tracked.entries().next().value,[s,i]=this.previous.entries().next().value;return"0"===t&&"0"===s&&e===i}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&!e.key&&e.constructor===t.constructor?e:null}}const p=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=p.length;s<i;s++){const i=p[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function E(t,e,s=()=>!1,i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class g{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.shouldSkipFind=t.shouldSkipFind,this.shouldSkipSync=t.shouldSkipSync,this.tracker=new d}hydrate(t){const e=E(t,this.getStart(),this.shouldSkipFind),s=E(t,this.getEnd(),this.shouldSkipFind,e);this.ref=[e,s]}update(t,e){let s;const[i,n]=this.ref,r=i.nextSibling,h=r===n,o=!h&&r.nextSibling===n;if(h?n.parentNode.insertBefore(t,n):!o||1!==t.children.length||this.shouldSkipSync(r)||this.shouldSkipSync(t.firstChild)?(s=document.createComment(""),n.parentNode.insertBefore(s,n),n.parentNode.insertBefore(t,n)):f(r,t.firstChild),e(),s){if(this.ref[0].nextSibling===s)s.parentNode.removeChild(s);else{const t=document.createRange();t.setStartAfter(this.ref[0]),t.setEndAfter(s),t.deleteContents()}}}updateElement(t,e,s){const i=document.createComment("");t.parentNode.insertBefore(i,t.nextSibling),i.parentNode.insertBefore(e.firstChild,i.nextSibling),s(),t.nextSibling===i&&t.parentNode.removeChild(t),i.parentNode.removeChild(i)}}const m=t=>t.reduce((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function b(t){const e=[];return Object.keys(t).forEach(s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))}),e.join(" ")}let T,v,S,A;"undefined"!=typeof document&&(T=!!navigator.userAgent.match(/Chrome/),v=!!Element.prototype.moveBefore,S=!v||T,A=v&&T);const R=(t,e,s)=>{try{return"function"!=typeof t?t:t.call(e,e)}catch(t){if(s&&!t._rasti){let s;s=`Error in ${e.constructor.name}#${e.uid} expression`;const i=new Error(s,{cause:t});throw i._rasti=!0,i}throw t}},C=t=>!!(t&&t.dataset&&t.dataset[H.DATASET_ELEMENT]&&t.dataset[H.DATASET_ELEMENT].endsWith("-1")),$=t=>!(!t||!(t.dataset&&t.dataset[H.DATASET_ELEMENT]||t.querySelector&&t.querySelector(`[${H.ATTRIBUTE_ELEMENT}]`))),L=(t,e)=>t.reduce((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(H.PLACEHOLDER(i)),t),[]).join(""),x=(t,e)=>{const s=H.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(H.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(H.markAsSafeHTML(t.slice(o))),r},k=(t,e)=>t.reduce((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t},{}),O=(t,e,s)=>{const i={};return Object.keys(t).forEach(n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]}),i},N=(t,e,s=!1)=>{const i=H.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),(t,s)=>{const i=e[s];if(i&&i.prototype instanceof H){if(n.has(i))return n.get(i);n.set(i,t)}return t})),t.replace(new RegExp(`<(${i})([^>]*)/>|<(${i})([^>]*)>([\\s\\S]*?)</\\4>`,"g"),(t,s,i,n,r,h,o,l)=>{let c,u,d;if(r?(c=e[h],u=o):(c=void 0!==i?e[i]:s,u=n),!(c.prototype instanceof H))return t;if(r){const t=N(l,e,!0),s=_(t,e);d=x(s,e)}const p=D(u,e);return e.push(function(){const t=k(p,t=>R(t,this,"children options"));return d&&(t.renderChildren=()=>new a(d.map(t=>R(t,this,"children")))),c.mount(t)}),H.PLACEHOLDER(e.length-1)})},w=(t,e)=>{const s=H.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},M=(t,e,s)=>{const n=H.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;const r=t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`));if(!r){throw new Error("Invalid component template")}let h=0;return w(r[0],(t,r,o,a)=>{const l=0===h,c=++h;if(!l&&!o.match(new RegExp(n)))return t;const u=D(o,e),d=t=>`${t}-${c}`,p=s.length;s.push({getSelector:function(){return`[${H.ATTRIBUTE_ELEMENT}="${d(this.uid)}"]`},getAttributes:function(){const t=O(k(u,t=>R(t,this,"element attribute")),this.eventsManager,t=>H.ATTRIBUTE_EVENT(t,this.uid));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[H.ATTRIBUTE_ELEMENT]=d(this.uid),t}}),e.push(function(){const t=this.template.elements[p],e=t.getAttributes.call(this);return t.previousAttributes=e,H.markAsSafeHTML(b(e))});return`<${r} ${H.PLACEHOLDER(e.length-1)}${a}`})},_=(t,e)=>{const s=H.PLACEHOLDER("(?:\\d+)");return w(t,(t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=D(n,e),o=function(){return O(k(h,t=>R(t,this,"partial element attribute")),this.eventsManager,t=>H.ATTRIBUTE_EVENT(t,this.uid))};e.push(function(){const t=o.call(this);return H.markAsSafeHTML(b(t))});return`<${i} ${H.PLACEHOLDER(e.length-1)}${r}`})},P=(t,e,s)=>{const i=H.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return H.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return H.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push(function(){return this.template.interpolations[l]}),H.PLACEHOLDER(e.length-1)})},D=(t,e)=>{const s=H.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(?:${s}|([\\w-]+))(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/>|\\s*[>"']))+.))?\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=!!n;let l=void 0!==t?e[parseInt(t,10)]:s,c=void 0!==h?e[parseInt(h,10)]:o;a&&void 0===c&&(c=""),void 0!==c?i.push([l,c,a]):i.push([l])}return i},j=["key","state","onCreate","onChange","onHydrate","onBeforeRecycle","onRecycle","onBeforeUpdate","onUpdate"];class H extends h{constructor(t={}){super(...arguments),this.componentOptions=[],j.forEach(e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))});const e={};Object.keys(t).forEach(s=>{-1===this.viewOptions.indexOf(s)&&-1===this.componentOptions.indexOf(s)&&(e[s]=t[s])}),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach(s=>{const i=H.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n}),t}ensureElement(){if(this.eventsManager=new l,this.template=i(this.template,this),this.el){if(this.el=i(this.el,this),!this.el.parentNode){const t=`Hydration failed in ${this.constructor.name}#${this.uid}`;throw new Error(t)}this.toString(),this.hydrate(this.el.parentNode)}}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach(t=>{this[t]&&this.subscribe(this[t])}),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach((e,s)=>{0===s?(e.hydrate(t),this.el=e.ref):e.hydrate(this.el)}),this.template.interpolations.forEach(t=>t.hydrate(this.el)),this.children.forEach(t=>t.hydrate(this.el))),this.delegateEvents(),this.onHydrate.call(this),this}recycle(t){if(this.onBeforeRecycle.call(this),t){!function(t,e){const s=S&&document.activeElement&&e.contains(document.activeElement)?document.activeElement:null;s&&A&&s.blur(),t.parentNode[v?"moveBefore":"insertBefore"](e,t),t.parentNode.removeChild(t),s&&s!==document.activeElement&&e.contains(s)&&s.focus()}(E(t,H.MARKER_RECYCLED(this.uid),C),this.el)}return this}updateProps(t){return this.props.set(t),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${H.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=x(_(N(L(t,e).trim(),e),e),e).map(t=>R(t,this,"partial"));return new a(s)}renderTemplatePart(t,e,s){const i=R(t,this,"template part");if(null==i||!1===i||!0===i)return"";if(i instanceof o)return`${i}`;if(i instanceof H)return`${e(i,s)}`;if(i instanceof a){if(1===i.items.length)return this.renderTemplatePart(i.items[0],e,s);s.push();const t=i.items.map(t=>(s.increment(),this.renderTemplatePart(t,e,s))).join("");return s.pop(),t}if(Array.isArray(i)){s.pause();const t=m(i).map(t=>this.renderTemplatePart(t,e,s)).join("");return s.resume(),t}if(i instanceof g){const t=i.tracker;t.reset();const s=this.isContainer()?"":`\x3c!--${i.getStart()}--\x3e`,n=this.isContainer()?"":`\x3c!--${i.getEnd()}--\x3e`;return`${s}${this.renderTemplatePart(i.expression,e,t)}${n}`}return`${H.sanitize(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset();const t=(t,e)=>(e.track(t),this.addChild(t));return this.template.parts.map(e=>this.renderTemplatePart(e,t)).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.onBeforeUpdate.call(this),this.eventsManager.reset();const t=this.children;this.children=[];const e=[];return this.template.interpolations.forEach(s=>{const i=s.tracker;i.reset();const n=[],r=[],h=this.renderTemplatePart(s.expression,e=>{let s=e,h=null;return h=e.key?t.find(t=>t.key===e.key):i.findRecyclable(e),h?(s=h.getRecycledMarker(),r.push([h,e]),i.track(h)):(n.push(e),i.track(e)),s},i),o=([t,s],i)=>{e.push([t,s.props.toJSON()]),this.addChild(t).recycle(i),s.destroy()};if(i.hasSingleComponent())return void o(r[0],null);const a=y(h),l=t=>()=>{r.forEach(e=>o(e,t)),n.forEach(e=>this.addChild(e).hydrate(t))};this.isContainer()?s.updateElement(this.el,a,l(this.el.parentNode)):s.update(a,l(this.el))}),t.forEach(t=>{this.children.indexOf(t)<0&&t.destroy()}),e.forEach(([t,e])=>{t.updateProps(e)}),this.isContainer()?this.el=this.children[0].el:this.template.elements.forEach(t=>t.update()),this.eventsManager.hasPendingTypes()&&this.delegateEvents(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onBeforeRecycle(){}onRecycle(){}onBeforeUpdate(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?i.toString():e.append(y(i)),i.hydrate(e)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=x(P(M(N(L(t,e).trim(),e),e,s),e,i),e);return this.extend({source:null,template(){return{elements:s.map(t=>new u({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)})),interpolations:i.map(t=>new g({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,shouldSkipFind:C,shouldSkipSync:$})),parts:n}}})}}H.ATTRIBUTE_ELEMENT="data-rst-el",H.ATTRIBUTE_EVENT=(t,e)=>`data-rst-on-${t}-${e}`,H.DATASET_ELEMENT="rstEl",H.PLACEHOLDER=t=>`__RASTI_PLACEHOLDER_${t}__`,H.MARKER_RECYCLED=t=>`rst-r-${t}`,H.MARKER_START=t=>`rst-s-${t}`,H.MARKER_END=t=>`rst-e-${t}`;var B=H.create`<div></div>`;t.Component=B,t.Emitter=s,t.Model=n,t.View=h});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,function(t){"use strict";function e(t){if("function"!=typeof t){throw new TypeError("Event listener must be a function")}}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter(t=>t!==e),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach(t=>t(...e))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter(i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1)),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach(t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])});const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach(t=>{this.emit(`change:${t}`,this,i[t],...n)}),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach(e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))}),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach(t=>t()),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach(t=>t.destroy()),this.children=[]}ensureUid(){this.uid||(this.uid="r"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach(t=>s.setAttribute(t,e[t])),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();const s={};return Object.keys(t).forEach(i=>{const n=i.match(/^(\w+)(?:\s+(.+))*$/);if(!n){throw new Error(`Invalid event format: ${i}`)}const[,r,h]=n;let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push([h,o])}),Object.keys(s).forEach(t=>{const e=e=>{let i=e.target;for(;i;)i.matches&&s[t].forEach(([t,s])=>{(i===this.el&&!t||i!==this.el&&i.matches(t))&&s.call(this,e,this,i)}),i=i===this.el||e.cancelBubble?null:i.parentElement};this.delegatedEventListeners.push([t,e]),this.el.addEventListener(t,e)}),this}undelegateEvents(){return this.delegatedEventListeners.forEach(([t,e])=>{this.el.removeEventListener(t,e)}),this.delegatedEventListeners=[],this}render(){return this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"}[t]))}static resetUid(){h.uid=0}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}const c=["value","checked","selected"];let u=class{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach(i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))}),Object.keys(e).forEach(s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)}),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach(t=>{this.ref.removeAttribute(t),-1!==c.indexOf(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")}),Object.keys(s).forEach(t=>{const e=s[t];this.ref.setAttribute(t,e),-1!==c.indexOf(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)})}};class d{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}hasSingleComponent(){if(1!==this.tracked.size||1!==this.previous.size)return!1;const[t,e]=this.tracked.entries().next().value,[s,i]=this.previous.entries().next().value;return"0"===t&&"0"===s&&e===i}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&!e.key&&e.constructor===t.constructor?e:null}}const p=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=p.length;s<i;s++){const i=p[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function E(t,e,s=()=>!1,i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class g{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.shouldSkipFind=t.shouldSkipFind,this.shouldSkipSync=t.shouldSkipSync,this.tracker=new d}hydrate(t){const e=E(t,this.getStart(),this.shouldSkipFind),s=E(t,this.getEnd(),this.shouldSkipFind,e);this.ref=[e,s]}update(t,e){let s;const[i,n]=this.ref,r=i.nextSibling,h=r===n,o=!h&&r.nextSibling===n;if(h?n.parentNode.insertBefore(t,n):!o||1!==t.children.length||this.shouldSkipSync(r)||this.shouldSkipSync(t.firstChild)?(s=document.createComment(""),n.parentNode.insertBefore(s,n),n.parentNode.insertBefore(t,n)):f(r,t.firstChild),e(),s){if(this.ref[0].nextSibling===s)s.parentNode.removeChild(s);else{const t=document.createRange();t.setStartAfter(this.ref[0]),t.setEndAfter(s),t.deleteContents()}}}updateElement(t,e,s){const i=document.createComment("");t.parentNode.insertBefore(i,t.nextSibling),i.parentNode.insertBefore(e.firstChild,i.nextSibling),s(),t.nextSibling===i&&t.parentNode.removeChild(t),i.parentNode.removeChild(i)}}const m=t=>t.reduce((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function b(t){const e=[];return Object.keys(t).forEach(s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))}),e.join(" ")}let T,v,S,A;"undefined"!=typeof document&&(T=!!navigator.userAgent.match(/Chrome/),v=!!Element.prototype.moveBefore,S=!v||T,A=v&&T);const R=(t,e,s)=>{try{return"function"!=typeof t?t:t.call(e,e)}catch(t){if(s&&!t._rasti){let s;s=`Error in ${e.constructor.name}#${e.uid} expression`;const i=new Error(s,{cause:t});throw i._rasti=!0,i}throw t}},C=t=>!!(t&&t.dataset&&t.dataset[H.DATASET_ELEMENT]&&t.dataset[H.DATASET_ELEMENT].endsWith("-1")),$=t=>!(!t||!(t.dataset&&t.dataset[H.DATASET_ELEMENT]||t.querySelector&&t.querySelector(`[${H.ATTRIBUTE_ELEMENT}]`))),L=(t,e)=>t.reduce((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(H.PLACEHOLDER(i)),t),[]).join(""),x=(t,e)=>{const s=H.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(H.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(H.markAsSafeHTML(t.slice(o))),r},k=(t,e)=>t.reduce((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t},{}),O=(t,e,s)=>{const i={};return Object.keys(t).forEach(n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]}),i},N=(t,e,s=!1)=>{const i=H.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),(t,s)=>{const i=e[s];if(i&&i.prototype instanceof H){if(n.has(i))return n.get(i);n.set(i,t)}return t})),t.replace(new RegExp(`<(${i})([^>]*)/>|<(${i})([^>]*)>([\\s\\S]*?)</\\4>`,"g"),(t,s,i,n,r,h,o,l)=>{let c,u,d;if(r?(c=e[h],u=o):(c=void 0!==i?e[i]:s,u=n),!(c.prototype instanceof H))return t;if(r){const t=N(l,e,!0),s=_(t,e);d=x(s,e)}const p=D(u,e);return e.push(function(){const t=k(p,t=>R(t,this,"children options"));return d&&(t.renderChildren=()=>new a(d.map(t=>R(t,this,"children")))),c.mount(t)}),H.PLACEHOLDER(e.length-1)})},w=(t,e)=>{const s=H.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},M=(t,e,s)=>{const n=H.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;const r=t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`));if(!r){throw new Error("Invalid component template")}let h=0;return w(r[0],(t,r,o,a)=>{const l=0===h,c=++h;if(!l&&!o.match(new RegExp(n)))return t;const u=D(o,e),d=t=>`${t}-${c}`,p=s.length;s.push({getSelector:function(){return`[${H.ATTRIBUTE_ELEMENT}="${d(this.uid)}"]`},getAttributes:function(){const t=O(k(u,t=>R(t,this,"element attribute")),this.eventsManager,t=>H.ATTRIBUTE_EVENT(t,this.uid));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[H.ATTRIBUTE_ELEMENT]=d(this.uid),t}}),e.push(function(){const t=this.template.elements[p],e=t.getAttributes.call(this);return t.previousAttributes=e,H.markAsSafeHTML(b(e))});return`<${r} ${H.PLACEHOLDER(e.length-1)}${a}`})},_=(t,e)=>{const s=H.PLACEHOLDER("(?:\\d+)");return w(t,(t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=D(n,e),o=function(){return O(k(h,t=>R(t,this,"partial element attribute")),this.eventsManager,t=>H.ATTRIBUTE_EVENT(t,this.uid))};e.push(function(){const t=o.call(this);return H.markAsSafeHTML(b(t))});return`<${i} ${H.PLACEHOLDER(e.length-1)}${r}`})},P=(t,e,s)=>{const i=H.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return H.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return H.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push(function(){return this.template.interpolations[l]}),H.PLACEHOLDER(e.length-1)})},D=(t,e)=>{const s=H.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(?:${s}|([\\w-]+))(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/>|\\s*[>"']))+.))?\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=!!n;let l=void 0!==t?e[parseInt(t,10)]:s,c=void 0!==h?e[parseInt(h,10)]:o;a&&void 0===c&&(c=""),void 0!==c?i.push([l,c,a]):i.push([l])}return i},j=["key","state","onCreate","onChange","onHydrate","onBeforeRecycle","onRecycle","onBeforeUpdate","onUpdate"];class H extends h{constructor(t={}){super(...arguments),this.componentOptions=[],j.forEach(e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))});const e={};Object.keys(t).forEach(s=>{-1===this.viewOptions.indexOf(s)&&-1===this.componentOptions.indexOf(s)&&(e[s]=t[s])}),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach(s=>{const i=H.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n}),t}ensureElement(){if(this.eventsManager=new l,this.template=i(this.template,this),this.el){if(this.el=i(this.el,this),!this.el.parentNode){const t=`Hydration failed in ${this.constructor.name}#${this.uid}`;throw new Error(t)}this.toString(),this.hydrate(this.el.parentNode)}}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach(t=>{this[t]&&this.subscribe(this[t])}),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach((e,s)=>{0===s?(e.hydrate(t),this.el=e.ref):e.hydrate(this.el)}),this.template.interpolations.forEach(t=>t.hydrate(this.el)),this.children.forEach(t=>t.hydrate(this.el))),this.delegateEvents(),this.onHydrate.call(this),this}recycle(t){if(this.onBeforeRecycle.call(this),t){!function(t,e){const s=S&&document.activeElement&&e.contains(document.activeElement)?document.activeElement:null;s&&A&&s.blur(),t.parentNode[v?"moveBefore":"insertBefore"](e,t),t.parentNode.removeChild(t),s&&s!==document.activeElement&&e.contains(s)&&s.focus()}(E(t,H.MARKER_RECYCLED(this.uid),C),this.el)}return this}updateProps(t){return this.props.set(t),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${H.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=x(_(N(L(t,e).trim(),e),e),e).map(t=>R(t,this,"partial"));return new a(s)}renderTemplatePart(t,e,s){const i=R(t,this,"template part");if(null==i||!1===i||!0===i)return"";if(i instanceof o)return`${i}`;if(i instanceof H)return`${e(i,s)}`;if(i instanceof a){if(1===i.items.length)return this.renderTemplatePart(i.items[0],e,s);s.push();const t=i.items.map(t=>(s.increment(),this.renderTemplatePart(t,e,s))).join("");return s.pop(),t}if(Array.isArray(i)){s.pause();const t=m(i).map(t=>this.renderTemplatePart(t,e,s)).join("");return s.resume(),t}if(i instanceof g){const t=i.tracker;t.reset();const s=this.isContainer()?"":`\x3c!--${i.getStart()}--\x3e`,n=this.isContainer()?"":`\x3c!--${i.getEnd()}--\x3e`;return`${s}${this.renderTemplatePart(i.expression,e,t)}${n}`}return`${H.sanitize(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset();const t=(t,e)=>(e.track(t),this.addChild(t));return this.template.parts.map(e=>this.renderTemplatePart(e,t)).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.onBeforeUpdate.call(this),this.eventsManager.reset();const t=this.children;this.children=[];const e=[];return this.template.interpolations.forEach(s=>{const i=s.tracker;i.reset();const n=[],r=[],h=this.renderTemplatePart(s.expression,e=>{let s,h=e;return s=e.key?t.find(t=>t.key===e.key):i.findRecyclable(e),s?(h=s.getRecycledMarker(),r.push([s,e]),i.track(s)):(n.push(e),i.track(e)),h},i),o=([t,s],i)=>{e.push([t,s.props.toJSON()]),this.addChild(t).recycle(i),s.destroy()};if(i.hasSingleComponent())return void o(r[0],null);const a=y(h),l=t=>()=>{r.forEach(e=>o(e,t)),n.forEach(e=>this.addChild(e).hydrate(t))};this.isContainer()?s.updateElement(this.el,a,l(this.el.parentNode)):s.update(a,l(this.el))}),t.forEach(t=>{this.children.indexOf(t)<0&&t.destroy()}),e.forEach(([t,e])=>{t.updateProps(e)}),this.isContainer()?this.el=this.children[0].el:this.template.elements.forEach(t=>t.update()),this.eventsManager.hasPendingTypes()&&this.delegateEvents(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onBeforeRecycle(){}onRecycle(){}onBeforeUpdate(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?i.toString():e.append(y(i)),i.hydrate(e)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=x(P(M(N(L(t,e).trim(),e),e,s),e,i),e);return this.extend({source:null,template(){return{elements:s.map(t=>new u({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)})),interpolations:i.map(t=>new g({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,shouldSkipFind:C,shouldSkipSync:$})),parts:n}}})}}H.ATTRIBUTE_ELEMENT="data-rst-el",H.ATTRIBUTE_EVENT=(t,e)=>`data-rst-on-${t}-${e}`,H.DATASET_ELEMENT="rstEl",H.PLACEHOLDER=t=>`__RASTI_PLACEHOLDER_${t}__`,H.MARKER_RECYCLED=t=>`rst-r-${t}`,H.MARKER_START=t=>`rst-s-${t}`,H.MARKER_END=t=>`rst-e-${t}`;var B=H.create`<div></div>`;t.Component=B,t.Emitter=s,t.Model=n,t.View=h});
//# sourceMappingURL=rasti.min.js.map

@@ -943,3 +943,3 @@ import './Emitter.js';

let out = component;
let found = null;
let found;
// Check if child already exists by key.

@@ -946,0 +946,0 @@ if (component.key) {

@@ -32,3 +32,2 @@ import Emitter from './Emitter.js';

* @property {object} previous Object containing previous attributes when a change occurs.
* @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.
* @example

@@ -347,2 +346,5 @@ * import { Model } from 'rasti';

* Useful for avoiding naming conflicts or creating a consistent property naming convention.
* @static
* @memberof module:Model
* @name attributePrefix
* @type {string}

@@ -349,0 +351,0 @@ * @default ''

@@ -1,1 +0,1 @@

{"version":3,"file":"Model.js","sources":["../src/Model.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\n\n/**\n * - Orchestrates data and business logic.\n * - Emits events when data changes.\n * \n * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified. \n * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects \n * that contain all the necessary functions for manipulating their specific data. \n * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.\n * \n * ## Construction Flow\n * 1. `preinitialize()` is called with all constructor arguments\n * 2. `this.defaults` are resolved (if function, it's called and bound to the model)\n * 3. `parse()` is called with all constructor arguments to process the data\n * 4. `this.attributes` is built by merging defaults and parsed data\n * 5. Getters/setters are generated for each attribute to emit change events\n * \n * @module\n * @extends Emitter\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed to `preinitialize` and `parse` methods\n * @property {object|Function} defaults Default attributes for the model. If a function, it's called bound to the model instance to get defaults.\n * @property {object} previous Object containing previous attributes when a change occurs.\n * @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.\n * @example\n * import { Model } from 'rasti';\n * \n * // User model\n * class User extends Model {\n * preinitialize() {\n * this.defaults = { name : '', email : '', role : 'user' };\n * }\n * }\n * // Order model with nested User and custom methods\n * class Order extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = {\n * id : null,\n * total : 0,\n * status : 'pending',\n * user : null\n * };\n * \n * this.apiUrl = options.apiUrl || '/api/orders';\n * }\n *\n * parse(data, options = {}) {\n * const parsed = { ...data };\n * \n * // Convert user object to User model instance\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * \n * return parsed;\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * \n * async fetch() {\n * try {\n * const response = await fetch(`${this.apiUrl}/${this.id}`);\n * const data = await response.json();\n * \n * // Parse the fetched data and update model\n * const parsed = this.parse(data);\n * this.set(parsed, { source : 'fetch' });\n * \n * return this;\n * } catch (error) {\n * console.error('Failed to fetch order:', error);\n * throw error;\n * }\n * }\n * }\n * \n * // Create order with nested user data\n * const order = new Order({\n * id : 123,\n * total : 99.99,\n * user : { name : 'Alice', email : 'alice@example.com' }\n * });\n * \n * console.log(order.user instanceof User); // true\n * // Serialize with nested models\n * const json = order.toJSON();\n * console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }\n * \n * // Listen to fetch updates\n * order.on('change', (model, changed, options) => {\n * if (options?.source === 'fetch') {\n * console.log('Order updated from server:', changed);\n * }\n * });\n * \n * // Fetch latest data from server\n * await order.fetch();\n */\nexport default class Model extends Emitter {\n constructor() {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Set attributes object with defaults and passed attributes.\n this.attributes = Object.assign({}, getResult(this.defaults, this), this.parse.apply(this, arguments));\n // Object to store previous attributes when a change occurs.\n this.previous = {};\n // Generate getters/setters for every attribute.\n Object.keys(this.attributes).forEach(this.defineAttribute.bind(this));\n }\n\n /**\n * Called before any instantiation logic runs for the Model.\n * Receives all constructor arguments, allowing for flexible initialization patterns.\n * Use this to set up `defaults`, configure the model, or handle custom constructor arguments.\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed from the constructor\n * @example\n * class User extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = { name : '', role : options.defaultRole || 'user' };\n * this.apiEndpoint = options.apiEndpoint || '/users';\n * }\n * }\n * const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });\n */\n preinitialize() {}\n\n /**\n * Generate getter/setter for the given attribute key to emit `change` events.\n * The property name uses `attributePrefix` + key (e.g., with prefix 'attr_', key 'name' becomes 'attr_name').\n * Called internally by the constructor for each key in `this.attributes`.\n * Override with an empty method if you don't want automatic getters/setters.\n * \n * @param {string} key Attribute key from `this.attributes`\n * @example\n * // Custom prefix for all attributes\n * class PrefixedModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * const model = new PrefixedModel({ name: 'Alice' });\n * console.log(model.attr_name); // 'Alice'\n * \n * // Disable automatic getters/setters\n * class ManualModel extends Model {\n * defineAttribute() {\n * // Empty - no getters/setters generated\n * }\n * \n * getName() {\n * return this.get('name'); // Manual getter\n * }\n * }\n */\n defineAttribute(key) {\n Object.defineProperty(\n this,\n `${this.constructor.attributePrefix}${key}`,\n {\n get : () => this.get(key),\n set : (value) => { this.set(key, value); }\n }\n );\n }\n\n /**\n * Get an attribute from `this.attributes`.\n * This method is called internally by generated getters.\n * @param {string} key Attribute key.\n * @return {any} The attribute value.\n */\n get(key) {\n return this.attributes[key];\n }\n\n /**\n * Set one or more attributes into `this.attributes` and emit change events.\n * Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.\n * Additional arguments are passed to change event listeners, enabling custom behavior.\n * \n * @param {string|object} key Attribute key (string) or object containing key-value pairs\n * @param {*} [value] Attribute value (when key is string)\n * @param {...*} [args] Additional arguments passed to event listeners\n * @return {Model} This model instance for chaining\n * @emits change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...args)`\n * @emits change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...args)`\n * @example\n * // Basic usage\n * model.set('name', 'Alice');\n * model.set({ name : 'Alice', age : 30 });\n * \n * // With options for listeners\n * model.set('name', 'Bob', { silent : false, validate : true });\n * model.on('change:name', (model, value, options) => {\n * if (options?.validate) {\n * // Custom validation logic\n * }\n * });\n */\n set(key, value, ...rest) {\n let attrs, args;\n // Handle both `\"key\", value` and `{key: value}` style arguments.\n if (typeof key === 'object') {\n attrs = key;\n args = [value, ...rest];\n } else {\n attrs = { [key] : value };\n args = rest;\n }\n // Are we in a nested `set` call?\n // Calling a `set` inside a `change:attribute` or `change` event listener\n const changing = this._changing;\n this._changing = true;\n // Store changed attributes.\n const changed = {};\n // Store previous attributes.\n if (!changing) {\n this.previous = Object.assign({}, this.attributes);\n }\n // Set attributes.\n Object.keys(attrs).forEach(key => {\n // Use equality to determine if value changed.\n if (attrs[key] !== this.attributes[key]) {\n changed[key] = attrs[key];\n this.attributes[key] = attrs[key];\n }\n });\n\n const changedKeys = Object.keys(changed);\n // Pending `change` event arguments.\n if (changedKeys.length) this._pending = ['change', this, changed, ...args];\n // Emit `change:attribute` events.\n changedKeys.forEach(key => {\n this.emit(`change:${key}`, this, attrs[key], ...args);\n });\n // Don't emit `change` event until the end of the nested \n // `set` calls inside `change:attribute` event listeners.\n if (changing) return this;\n // Emit `change` events, that might be nested.\n while (this._pending) {\n const pendingChange = this._pending;\n this._pending = null;\n this.emit.apply(this, pendingChange);\n }\n // Reset flags.\n this._pending = null;\n this._changing = false;\n\n return this;\n }\n\n /**\n * Transforms and validates data before it becomes model attributes.\n * Called during construction with all constructor arguments, allowing flexible data processing.\n * Override this method to transform incoming data, create nested models, or handle different data formats.\n * \n * @param {object} [data={}] Primary data object to be parsed into attributes\n * @param {...*} [args] Additional arguments from constructor, useful for parsing options\n * @return {object} Processed data that will become the model's attributes\n * @example\n * // Transform nested objects into models\n * class User extends Model {}\n * class Order extends Model {\n * parse(data, options = {}) {\n * // Skip parsing if requested\n * if (options.raw) return data;\n * // Transform user data into User model\n * const parsed = { ...data };\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * return parsed;\n * }\n * }\n * \n * // Usage with parsing options\n * const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model\n * const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object\n */\n parse(data) {\n return data;\n }\n\n /**\n * Return object representation of the model to be used for JSON serialization.\n * By default returns a copy of `this.attributes`.\n * You can override this method to customize serialization behavior, such as calling `toJSON` recursively on nested Model instances.\n * @return {object} Object representation of the model to be used for JSON serialization.\n * @example\n * // Basic usage - returns a copy of model attributes:\n * const user = new Model({ name : 'Alice', age : 30 });\n * const json = user.toJSON();\n * console.log(json); // { name : 'Alice', age : 30 }\n * \n * // Override toJSON for recursive serialization of nested models:\n * class User extends Model {}\n * class Order extends Model {\n * parse(data) {\n * // Ensure user is always a User model\n * return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * }\n * const order = new Order({ id : 1, user : { name : 'Alice' } });\n * const json = order.toJSON();\n * console.log(json); // { id : 1, user : { name : 'Alice' } }\n */\n toJSON() {\n return Object.assign({}, this.attributes);\n }\n}\n\n/**\n * Static property that defines a prefix for generated getters/setters.\n * When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').\n * Useful for avoiding naming conflicts or creating a consistent property naming convention.\n * @type {string}\n * @default ''\n * @example\n * // Set prefix for all models of this class\n * class ApiModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * \n * const user = new ApiModel({ name : 'Alice', email : 'alice@example.com' });\n * console.log(user.attr_name); // 'Alice'\n * console.log(user.attr_email); // 'alice@example.com'\n * \n * // Still access via get/set methods without prefix\n * console.log(user.get('name')); // 'Alice'\n * user.set('name', 'Bob');\n * console.log(user.attr_name); // 'Bob'\n */\nModel.attributePrefix = '';\n"],"names":[],"mappings":";;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAM,KAAK,SAAS,OAAO,CAAC;AAC3C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,EAAE;AACf;AACA,QAAQ,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;AACjD;AACA,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9G;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,EAAE;AAC1B;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA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"}
{"version":3,"file":"Model.js","sources":["../src/Model.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\n\n/**\n * - Orchestrates data and business logic.\n * - Emits events when data changes.\n * \n * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified. \n * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects \n * that contain all the necessary functions for manipulating their specific data. \n * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.\n * \n * ## Construction Flow\n * 1. `preinitialize()` is called with all constructor arguments\n * 2. `this.defaults` are resolved (if function, it's called and bound to the model)\n * 3. `parse()` is called with all constructor arguments to process the data\n * 4. `this.attributes` is built by merging defaults and parsed data\n * 5. Getters/setters are generated for each attribute to emit change events\n * \n * @module\n * @extends Emitter\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed to `preinitialize` and `parse` methods\n * @property {object|Function} defaults Default attributes for the model. If a function, it's called bound to the model instance to get defaults.\n * @property {object} previous Object containing previous attributes when a change occurs.\n * @example\n * import { Model } from 'rasti';\n * \n * // User model\n * class User extends Model {\n * preinitialize() {\n * this.defaults = { name : '', email : '', role : 'user' };\n * }\n * }\n * // Order model with nested User and custom methods\n * class Order extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = {\n * id : null,\n * total : 0,\n * status : 'pending',\n * user : null\n * };\n * \n * this.apiUrl = options.apiUrl || '/api/orders';\n * }\n *\n * parse(data, options = {}) {\n * const parsed = { ...data };\n * \n * // Convert user object to User model instance\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * \n * return parsed;\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * \n * async fetch() {\n * try {\n * const response = await fetch(`${this.apiUrl}/${this.id}`);\n * const data = await response.json();\n * \n * // Parse the fetched data and update model\n * const parsed = this.parse(data);\n * this.set(parsed, { source : 'fetch' });\n * \n * return this;\n * } catch (error) {\n * console.error('Failed to fetch order:', error);\n * throw error;\n * }\n * }\n * }\n * \n * // Create order with nested user data\n * const order = new Order({\n * id : 123,\n * total : 99.99,\n * user : { name : 'Alice', email : 'alice@example.com' }\n * });\n * \n * console.log(order.user instanceof User); // true\n * // Serialize with nested models\n * const json = order.toJSON();\n * console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }\n * \n * // Listen to fetch updates\n * order.on('change', (model, changed, options) => {\n * if (options?.source === 'fetch') {\n * console.log('Order updated from server:', changed);\n * }\n * });\n * \n * // Fetch latest data from server\n * await order.fetch();\n */\nexport default class Model extends Emitter {\n constructor() {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Set attributes object with defaults and passed attributes.\n this.attributes = Object.assign({}, getResult(this.defaults, this), this.parse.apply(this, arguments));\n // Object to store previous attributes when a change occurs.\n this.previous = {};\n // Generate getters/setters for every attribute.\n Object.keys(this.attributes).forEach(this.defineAttribute.bind(this));\n }\n\n /**\n * Called before any instantiation logic runs for the Model.\n * Receives all constructor arguments, allowing for flexible initialization patterns.\n * Use this to set up `defaults`, configure the model, or handle custom constructor arguments.\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed from the constructor\n * @example\n * class User extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = { name : '', role : options.defaultRole || 'user' };\n * this.apiEndpoint = options.apiEndpoint || '/users';\n * }\n * }\n * const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });\n */\n preinitialize() {}\n\n /**\n * Generate getter/setter for the given attribute key to emit `change` events.\n * The property name uses `attributePrefix` + key (e.g., with prefix 'attr_', key 'name' becomes 'attr_name').\n * Called internally by the constructor for each key in `this.attributes`.\n * Override with an empty method if you don't want automatic getters/setters.\n * \n * @param {string} key Attribute key from `this.attributes`\n * @example\n * // Custom prefix for all attributes\n * class PrefixedModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * const model = new PrefixedModel({ name: 'Alice' });\n * console.log(model.attr_name); // 'Alice'\n * \n * // Disable automatic getters/setters\n * class ManualModel extends Model {\n * defineAttribute() {\n * // Empty - no getters/setters generated\n * }\n * \n * getName() {\n * return this.get('name'); // Manual getter\n * }\n * }\n */\n defineAttribute(key) {\n Object.defineProperty(\n this,\n `${this.constructor.attributePrefix}${key}`,\n {\n get : () => this.get(key),\n set : (value) => { this.set(key, value); }\n }\n );\n }\n\n /**\n * Get an attribute from `this.attributes`.\n * This method is called internally by generated getters.\n * @param {string} key Attribute key.\n * @return {any} The attribute value.\n */\n get(key) {\n return this.attributes[key];\n }\n\n /**\n * Set one or more attributes into `this.attributes` and emit change events.\n * Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.\n * Additional arguments are passed to change event listeners, enabling custom behavior.\n * \n * @param {string|object} key Attribute key (string) or object containing key-value pairs\n * @param {*} [value] Attribute value (when key is string)\n * @param {...*} [args] Additional arguments passed to event listeners\n * @return {Model} This model instance for chaining\n * @emits change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...args)`\n * @emits change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...args)`\n * @example\n * // Basic usage\n * model.set('name', 'Alice');\n * model.set({ name : 'Alice', age : 30 });\n * \n * // With options for listeners\n * model.set('name', 'Bob', { silent : false, validate : true });\n * model.on('change:name', (model, value, options) => {\n * if (options?.validate) {\n * // Custom validation logic\n * }\n * });\n */\n set(key, value, ...rest) {\n let attrs, args;\n // Handle both `\"key\", value` and `{key: value}` style arguments.\n if (typeof key === 'object') {\n attrs = key;\n args = [value, ...rest];\n } else {\n attrs = { [key] : value };\n args = rest;\n }\n // Are we in a nested `set` call?\n // Calling a `set` inside a `change:attribute` or `change` event listener\n const changing = this._changing;\n this._changing = true;\n // Store changed attributes.\n const changed = {};\n // Store previous attributes.\n if (!changing) {\n this.previous = Object.assign({}, this.attributes);\n }\n // Set attributes.\n Object.keys(attrs).forEach(key => {\n // Use equality to determine if value changed.\n if (attrs[key] !== this.attributes[key]) {\n changed[key] = attrs[key];\n this.attributes[key] = attrs[key];\n }\n });\n\n const changedKeys = Object.keys(changed);\n // Pending `change` event arguments.\n if (changedKeys.length) this._pending = ['change', this, changed, ...args];\n // Emit `change:attribute` events.\n changedKeys.forEach(key => {\n this.emit(`change:${key}`, this, attrs[key], ...args);\n });\n // Don't emit `change` event until the end of the nested \n // `set` calls inside `change:attribute` event listeners.\n if (changing) return this;\n // Emit `change` events, that might be nested.\n while (this._pending) {\n const pendingChange = this._pending;\n this._pending = null;\n this.emit.apply(this, pendingChange);\n }\n // Reset flags.\n this._pending = null;\n this._changing = false;\n\n return this;\n }\n\n /**\n * Transforms and validates data before it becomes model attributes.\n * Called during construction with all constructor arguments, allowing flexible data processing.\n * Override this method to transform incoming data, create nested models, or handle different data formats.\n * \n * @param {object} [data={}] Primary data object to be parsed into attributes\n * @param {...*} [args] Additional arguments from constructor, useful for parsing options\n * @return {object} Processed data that will become the model's attributes\n * @example\n * // Transform nested objects into models\n * class User extends Model {}\n * class Order extends Model {\n * parse(data, options = {}) {\n * // Skip parsing if requested\n * if (options.raw) return data;\n * // Transform user data into User model\n * const parsed = { ...data };\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * return parsed;\n * }\n * }\n * \n * // Usage with parsing options\n * const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model\n * const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object\n */\n parse(data) {\n return data;\n }\n\n /**\n * Return object representation of the model to be used for JSON serialization.\n * By default returns a copy of `this.attributes`.\n * You can override this method to customize serialization behavior, such as calling `toJSON` recursively on nested Model instances.\n * @return {object} Object representation of the model to be used for JSON serialization.\n * @example\n * // Basic usage - returns a copy of model attributes:\n * const user = new Model({ name : 'Alice', age : 30 });\n * const json = user.toJSON();\n * console.log(json); // { name : 'Alice', age : 30 }\n * \n * // Override toJSON for recursive serialization of nested models:\n * class User extends Model {}\n * class Order extends Model {\n * parse(data) {\n * // Ensure user is always a User model\n * return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * }\n * const order = new Order({ id : 1, user : { name : 'Alice' } });\n * const json = order.toJSON();\n * console.log(json); // { id : 1, user : { name : 'Alice' } }\n */\n toJSON() {\n return Object.assign({}, this.attributes);\n }\n}\n\n/**\n * Static property that defines a prefix for generated getters/setters.\n * When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').\n * Useful for avoiding naming conflicts or creating a consistent property naming convention.\n * @static\n * @memberof module:Model\n * @name attributePrefix\n * @type {string}\n * @default ''\n * @example\n * // Set prefix for all models of this class\n * class ApiModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * \n * const user = new ApiModel({ name : 'Alice', email : 'alice@example.com' });\n * console.log(user.attr_name); // 'Alice'\n * console.log(user.attr_email); // 'alice@example.com'\n * \n * // Still access via get/set methods without prefix\n * console.log(user.get('name')); // 'Alice'\n * user.set('name', 'Bob');\n * console.log(user.attr_name); // 'Bob'\n */\nModel.attributePrefix = '';\n"],"names":[],"mappings":";;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAM,KAAK,SAAS,OAAO,CAAC;AAC3C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,EAAE;AACf;AACA,QAAQ,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;AACjD;AACA,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9G;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,EAAE;AAC1B;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,eAAe,CAAC,GAAG,EAAE;AACzB,QAAQ,MAAM,CAAC,cAAc;AAC7B,YAAY,IAAI;AAChB,YAAY,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,GAAG,CAAC,CAAC;AACvD,YAAY;AACZ,gBAAgB,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACzC,gBAAgB,GAAG,GAAG,CAAC,KAAK,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AACzD;AACA,SAAS;AACT,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,GAAG,CAAC,GAAG,EAAE;AACb,QAAQ,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;AACnC,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE;AAC7B,QAAQ,IAAI,KAAK,EAAE,IAAI;AACvB;AACA,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AACrC,YAAY,KAAK,GAAG,GAAG;AACvB,YAAY,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC;AACnC,QAAQ,CAAC,MAAM;AACf,YAAY,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,KAAK,EAAE;AACrC,YAAY,IAAI,GAAG,IAAI;AACvB,QAAQ;AACR;AACA;AACA,QAAQ,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS;AACvC,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;AAC7B;AACA,QAAQ,MAAM,OAAO,GAAG,EAAE;AAC1B;AACA,QAAQ,IAAI,CAAC,QAAQ,EAAE;AACvB,YAAY,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC;AAC9D,QAAQ;AACR;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,IAAI;AAC1C;AACA,YAAY,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AACrD,gBAAgB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC;AACzC,gBAAgB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC;AACjD,YAAY;AACZ,QAAQ,CAAC,CAAC;;AAEV,QAAQ,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;AAChD;AACA,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;AAClF;AACA,QAAQ,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;AACnC,YAAY,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;AACjE,QAAQ,CAAC,CAAC;AACV;AACA;AACA,QAAQ,IAAI,QAAQ,EAAE,OAAO,IAAI;AACjC;AACA,QAAQ,OAAO,IAAI,CAAC,QAAQ,EAAE;AAC9B,YAAY,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ;AAC/C,YAAY,IAAI,CAAC,QAAQ,GAAG,IAAI;AAChC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC;AAChD,QAAQ;AACR;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,IAAI;AAC5B,QAAQ,IAAI,CAAC,SAAS,GAAG,KAAK;;AAE9B,QAAQ,OAAO,IAAI;AACnB,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,KAAK,CAAC,IAAI,EAAE;AAChB,QAAQ,OAAO,IAAI;AACnB,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,GAAG;AACb,QAAQ,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC;AACjD,IAAI;AACJ;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,CAAC,eAAe,GAAG,EAAE;;;;"}

@@ -36,3 +36,3 @@ import Emitter from './Emitter.js';

* @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
* @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @property {string} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @example

@@ -422,2 +422,4 @@ * import { View, Model } from 'rasti';

* @static
* @memberof module:View
* @name uid
* @type {number}

@@ -424,0 +426,0 @@ * @default 0

@@ -1,1 +0,1 @@

{"version":3,"file":"View.js","sources":["../src/View.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\nimport validateListener from './utils/validateListener.js';\nimport createDevelopmentErrorMessage from './utils/createDevelopmentErrorMessage.js';\nimport createProductionErrorMessage from './utils/createProductionErrorMessage.js';\nimport __DEV__ from './utils/dev.js';\n\n/*\n * These option keys will be extended on the view instance.\n */\nconst viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];\n\n/**\n * - Listens for changes and renders the UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.\n * However, views can also be independent and have no associated data. \n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.\n * @module\n * @extends Emitter\n * @param {object} options Object containing options. The following keys will be merged into the view instance: `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.\n * @property {node|Function} el Every view has a root DOM element stored at `this.el`. If not present, it will be created. If `this.el` is a function, it will be called to get the element at `this.ensureElement`, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {string|Function} tag If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. Default is `div`. If it is a function, it will be called to get the tag, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} attributes If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. If it is a function, it will be called to get the attributes object, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.\n * @property {object} model A model or any object containing data and business logic.\n * @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}. \n * @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.\n * @example\n * import { View, Model } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute to 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re-render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;\n * }\n *\n * render() {\n * if (this.template) {\n * this.el.innerHTML = this.template(this.model);\n * }\n * return this;\n * }\n * }\n * // Render view and append view's element into the body.\n * document.body.appendChild(new Timer().render().el);\n */\nexport default class View extends Emitter {\n constructor(options = {}) {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Store delegated event listeners,\n // so they can be unbound later.\n this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n this.children = [];\n // Mutable array to store handlers to be called on destroy.\n this.destroyQueue = [];\n this.viewOptions = [];\n // Extend \"this\" with options.\n viewOptions.forEach(key => {\n if (key in options) {\n this[key] = options[key];\n this.viewOptions.push(key);\n }\n });\n // Ensure that the view has a unique id at `this.uid`.\n this.ensureUid();\n // Ensure that the view has a root element at `this.el`.\n this.ensureElement();\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} options The view options.\n */\n preinitialize() {}\n\n /**\n * Returns the first element that matches the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */\n $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */\n $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n * @return {View} Return `this` for chaining.\n */\n destroy() {\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Stop listening to events.\n this.stopListening();\n // Unbind `this` events.\n this.off();\n // Call destroy queue.\n this.destroyQueue.forEach(fn => fn());\n this.destroyQueue = [];\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n // Set destroyed flag.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `onDestroy` lifecycle method is called after the view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */\n onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {View} child\n * @return {View}\n */\n addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */\n destroyChildren() {\n this.children.forEach(child => child.destroy());\n this.children = [];\n }\n\n /**\n * Ensure that the view has a unique id at `this.uid`.\n */\n ensureUid() {\n if (!this.uid) this.uid = `r${++View.uid}`;\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from the constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */\n ensureElement() {\n // Element is already present.\n if (this.el) {\n // If \"this.el\" is a function, call it to get the element.\n this.el = getResult(this.el, this);\n } else {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n const tag = getResult(this.tag, this);\n const attrs = getResult(this.attributes, this);\n this.el = this.createElement(tag, attrs);\n }\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from the constructor if `this.el` is undefined, to ensure\n * the view has a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attributes Attributes for the element.\n * @return {node} The created element.\n */\n createElement(tag = 'div', attributes = {}) {\n // Create DOM element.\n let el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attributes)\n .forEach(key => el.setAttribute(key, attributes[key]));\n\n return el;\n }\n\n /**\n * Remove `this.el` from the DOM.\n * @return {View} Return `this` for chaining.\n */\n removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events object is not provided, \n * it defaults to using `this.events`. If `this.events` is a function, it will be called to get the events object.\n * \n * The events object should follow the format `{'event selector': 'listener'}`:\n * - `event`: The type of event (e.g., 'click').\n * - `selector`: A CSS selector to match the event target. If omitted, the event is bound to the root element.\n * - `listener`: A function or a string representing a method name on the view. The method will be called with `this` bound to the view instance.\n * \n * By default, `delegateEvents` is called within the View's constructor. If you have a simple events object, \n * all of your DOM events will be connected automatically, and you will not need to call this function manually.\n * \n * All attached listeners are bound to the view, ensuring that `this` refers to the view object when the listeners are invoked.\n * When `delegateEvents` is called again, possibly with a different events object, all previous listeners are removed and delegated afresh.\n * \n * **Listener signature:** `(event, view, matched)`\n * - `event`: The native DOM event object.\n * - `view`: The current view instance (`this`).\n * - `matched`: The element that satisfies the selector. If no selector is provided, it will be the view's root element (`this.el`).\n *\n * If more than one ancestor between `event.target` and the view's root element matches the selector, the listener will be\n * invoked **once for each matched element** (from inner to outer).\n *\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to the root element.\n * @return {View} Returns `this` for chaining.\n * @example\n * // Using prototype (recommended for static events)\n * class Modal extends View {\n * onClickOk(event, view, matched) {\n * // matched === the button.ok element that was clicked\n * this.close();\n * }\n * \n * onClickCancel() {\n * this.destroy();\n * }\n * }\n * Modal.prototype.events = {\n * 'click button.ok': 'onClickOk',\n * 'click button.cancel': 'onClickCancel',\n * 'submit form': 'onSubmit'\n * };\n * \n * // Using a function for dynamic events\n * class DynamicView extends View {\n * events() {\n * return {\n * [`click .${this.model.buttonClass}`]: 'onButtonClick',\n * 'click': 'onRootClick'\n * };\n * }\n * }\n */\n delegateEvents(events) {\n if (!events) events = getResult(this.events, this);\n if (!events) return this;\n\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n const eventTypes = {};\n\n Object.keys(events).forEach(key => {\n const match = key.match(/^(\\w+)(?:\\s+(.+))*$/);\n\n if (!match) {\n const message = `Invalid event format: ${key}`;\n throw new Error(__DEV__ ? createDevelopmentErrorMessage(message) : createProductionErrorMessage(message));\n }\n // Extract type and selector from the event key.\n const [,type, selector] = match;\n\n let listener = events[key];\n // Listener may be a string representing a method name on the view, or a function.\n if (typeof listener === 'string') listener = this[listener];\n // Validate listener is a function.\n validateListener(listener);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push([selector, listener]);\n });\n\n Object.keys(eventTypes).forEach(type => {\n // Listener for the type of event.\n const typeListener = (event) => {\n let node = event.target;\n // Traverse ancestors until reaching the view root (`this.el`).\n while (node) {\n if (node.matches) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(([selector, listener]) => {\n if ((node === this.el && !selector) || (node !== this.el && node.matches(selector))) {\n listener.call(this, event, this, node);\n }\n });\n }\n // Continue traversing ancestors until reaching the view root (`this.el`) or stopping propagation.\n node = node === this.el || event.cancelBubble ? null : node.parentElement;\n }\n };\n // Store the type and listener in the delegated event listeners array.\n this.delegatedEventListeners.push([type, typeListener]);\n // Add the event listener to the element.\n this.el.addEventListener(type, typeListener);\n });\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. \n * Useful if you want to disable or remove a view from the DOM temporarily. \n * Called automatically when the view is destroyed and when `delegateEvents` is called again.\n * @return {View} Return `this` for chaining.\n */\n undelegateEvents() {\n this.delegatedEventListeners.forEach(([type, listener]) => {\n this.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `render` is the core function that your view should override, in order to populate its element (`this.el`), with the appropriate HTML. The convention is for `render` to always return `this`. \n * Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using {@link #module_component Component} instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline. \n * If you add any child views, you should call `this.destroyChildren` before re-rendering.\n * \n * @return {View} Returns `this` for chaining.\n * @example\n * class UserView extends View {\n * render() {\n * if (this.template) {\n * const model = this.model;\n * // Sanitize model attributes to prevent XSS attacks.\n * const safeData = {\n * name : View.sanitize(model.name),\n * email : View.sanitize(model.email),\n * bio : View.sanitize(model.bio)\n * };\n * this.el.innerHTML = this.template(safeData);\n * }\n * return this;\n * }\n * }\n */\n render() {\n return this;\n }\n\n /**\n * Escape HTML entities in a string.\n * Use this method to sanitize user-generated content before inserting it into the DOM.\n * Override this method to provide a custom escape function.\n * This method is inherited by {@link #module_component Component} and used to escape template interpolations.\n * @static\n * @param {string} value String to escape.\n * @return {string} Escaped string.\n */\n static sanitize(value) {\n return `${value}`.replace(/[&<>\"']/g, match => ({\n '&' : '&amp;',\n '<' : '&lt;',\n '>' : '&gt;',\n '\"' : '&quot;',\n '\\'' : '&#039;'\n }[match]));\n }\n\n /**\n * Reset the unique ID counter to 0.\n * This is useful for server-side rendering scenarios where you want to ensure that\n * the generated unique IDs match those on the client, enabling seamless hydration of components.\n * This method is inherited by {@link #module_component Component}.\n * @static\n */\n static resetUid() {\n View.uid = 0;\n }\n}\n\n/**\n * Counter for generating unique IDs for view instances. \n * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like \n * generating element IDs. \n * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration. \n * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated \n * unique IDs match those on the client, enabling seamless hydration of components. \n * @static\n * @type {number}\n * @default 0\n */\nView.uid = 0;\n"],"names":[],"mappings":"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"}
{"version":3,"file":"View.js","sources":["../src/View.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\nimport validateListener from './utils/validateListener.js';\nimport createDevelopmentErrorMessage from './utils/createDevelopmentErrorMessage.js';\nimport createProductionErrorMessage from './utils/createProductionErrorMessage.js';\nimport __DEV__ from './utils/dev.js';\n\n/*\n * These option keys will be extended on the view instance.\n */\nconst viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];\n\n/**\n * - Listens for changes and renders the UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.\n * However, views can also be independent and have no associated data. \n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.\n * @module\n * @extends Emitter\n * @param {object} options Object containing options. The following keys will be merged into the view instance: `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.\n * @property {node|Function} el Every view has a root DOM element stored at `this.el`. If not present, it will be created. If `this.el` is a function, it will be called to get the element at `this.ensureElement`, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {string|Function} tag If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. Default is `div`. If it is a function, it will be called to get the tag, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} attributes If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. If it is a function, it will be called to get the attributes object, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.\n * @property {object} model A model or any object containing data and business logic.\n * @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}. \n * @property {string} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.\n * @example\n * import { View, Model } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute to 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re-render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;\n * }\n *\n * render() {\n * if (this.template) {\n * this.el.innerHTML = this.template(this.model);\n * }\n * return this;\n * }\n * }\n * // Render view and append view's element into the body.\n * document.body.appendChild(new Timer().render().el);\n */\nexport default class View extends Emitter {\n constructor(options = {}) {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Store delegated event listeners,\n // so they can be unbound later.\n this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n this.children = [];\n // Mutable array to store handlers to be called on destroy.\n this.destroyQueue = [];\n this.viewOptions = [];\n // Extend \"this\" with options.\n viewOptions.forEach(key => {\n if (key in options) {\n this[key] = options[key];\n this.viewOptions.push(key);\n }\n });\n // Ensure that the view has a unique id at `this.uid`.\n this.ensureUid();\n // Ensure that the view has a root element at `this.el`.\n this.ensureElement();\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} options The view options.\n */\n preinitialize() {}\n\n /**\n * Returns the first element that matches the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */\n $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */\n $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n * @return {View} Return `this` for chaining.\n */\n destroy() {\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Stop listening to events.\n this.stopListening();\n // Unbind `this` events.\n this.off();\n // Call destroy queue.\n this.destroyQueue.forEach(fn => fn());\n this.destroyQueue = [];\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n // Set destroyed flag.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `onDestroy` lifecycle method is called after the view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */\n onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {View} child\n * @return {View}\n */\n addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */\n destroyChildren() {\n this.children.forEach(child => child.destroy());\n this.children = [];\n }\n\n /**\n * Ensure that the view has a unique id at `this.uid`.\n */\n ensureUid() {\n if (!this.uid) this.uid = `r${++View.uid}`;\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from the constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */\n ensureElement() {\n // Element is already present.\n if (this.el) {\n // If \"this.el\" is a function, call it to get the element.\n this.el = getResult(this.el, this);\n } else {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n const tag = getResult(this.tag, this);\n const attrs = getResult(this.attributes, this);\n this.el = this.createElement(tag, attrs);\n }\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from the constructor if `this.el` is undefined, to ensure\n * the view has a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attributes Attributes for the element.\n * @return {node} The created element.\n */\n createElement(tag = 'div', attributes = {}) {\n // Create DOM element.\n let el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attributes)\n .forEach(key => el.setAttribute(key, attributes[key]));\n\n return el;\n }\n\n /**\n * Remove `this.el` from the DOM.\n * @return {View} Return `this` for chaining.\n */\n removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events object is not provided, \n * it defaults to using `this.events`. If `this.events` is a function, it will be called to get the events object.\n * \n * The events object should follow the format `{'event selector': 'listener'}`:\n * - `event`: The type of event (e.g., 'click').\n * - `selector`: A CSS selector to match the event target. If omitted, the event is bound to the root element.\n * - `listener`: A function or a string representing a method name on the view. The method will be called with `this` bound to the view instance.\n * \n * By default, `delegateEvents` is called within the View's constructor. If you have a simple events object, \n * all of your DOM events will be connected automatically, and you will not need to call this function manually.\n * \n * All attached listeners are bound to the view, ensuring that `this` refers to the view object when the listeners are invoked.\n * When `delegateEvents` is called again, possibly with a different events object, all previous listeners are removed and delegated afresh.\n * \n * **Listener signature:** `(event, view, matched)`\n * - `event`: The native DOM event object.\n * - `view`: The current view instance (`this`).\n * - `matched`: The element that satisfies the selector. If no selector is provided, it will be the view's root element (`this.el`).\n *\n * If more than one ancestor between `event.target` and the view's root element matches the selector, the listener will be\n * invoked **once for each matched element** (from inner to outer).\n *\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to the root element.\n * @return {View} Returns `this` for chaining.\n * @example\n * // Using prototype (recommended for static events)\n * class Modal extends View {\n * onClickOk(event, view, matched) {\n * // matched === the button.ok element that was clicked\n * this.close();\n * }\n * \n * onClickCancel() {\n * this.destroy();\n * }\n * }\n * Modal.prototype.events = {\n * 'click button.ok': 'onClickOk',\n * 'click button.cancel': 'onClickCancel',\n * 'submit form': 'onSubmit'\n * };\n * \n * // Using a function for dynamic events\n * class DynamicView extends View {\n * events() {\n * return {\n * [`click .${this.model.buttonClass}`]: 'onButtonClick',\n * 'click': 'onRootClick'\n * };\n * }\n * }\n */\n delegateEvents(events) {\n if (!events) events = getResult(this.events, this);\n if (!events) return this;\n\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n const eventTypes = {};\n\n Object.keys(events).forEach(key => {\n const match = key.match(/^(\\w+)(?:\\s+(.+))*$/);\n\n if (!match) {\n const message = `Invalid event format: ${key}`;\n throw new Error(__DEV__ ? createDevelopmentErrorMessage(message) : createProductionErrorMessage(message));\n }\n // Extract type and selector from the event key.\n const [,type, selector] = match;\n\n let listener = events[key];\n // Listener may be a string representing a method name on the view, or a function.\n if (typeof listener === 'string') listener = this[listener];\n // Validate listener is a function.\n validateListener(listener);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push([selector, listener]);\n });\n\n Object.keys(eventTypes).forEach(type => {\n // Listener for the type of event.\n const typeListener = (event) => {\n let node = event.target;\n // Traverse ancestors until reaching the view root (`this.el`).\n while (node) {\n if (node.matches) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(([selector, listener]) => {\n if ((node === this.el && !selector) || (node !== this.el && node.matches(selector))) {\n listener.call(this, event, this, node);\n }\n });\n }\n // Continue traversing ancestors until reaching the view root (`this.el`) or stopping propagation.\n node = node === this.el || event.cancelBubble ? null : node.parentElement;\n }\n };\n // Store the type and listener in the delegated event listeners array.\n this.delegatedEventListeners.push([type, typeListener]);\n // Add the event listener to the element.\n this.el.addEventListener(type, typeListener);\n });\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. \n * Useful if you want to disable or remove a view from the DOM temporarily. \n * Called automatically when the view is destroyed and when `delegateEvents` is called again.\n * @return {View} Return `this` for chaining.\n */\n undelegateEvents() {\n this.delegatedEventListeners.forEach(([type, listener]) => {\n this.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `render` is the core function that your view should override, in order to populate its element (`this.el`), with the appropriate HTML. The convention is for `render` to always return `this`. \n * Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using {@link #module_component Component} instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline. \n * If you add any child views, you should call `this.destroyChildren` before re-rendering.\n * \n * @return {View} Returns `this` for chaining.\n * @example\n * class UserView extends View {\n * render() {\n * if (this.template) {\n * const model = this.model;\n * // Sanitize model attributes to prevent XSS attacks.\n * const safeData = {\n * name : View.sanitize(model.name),\n * email : View.sanitize(model.email),\n * bio : View.sanitize(model.bio)\n * };\n * this.el.innerHTML = this.template(safeData);\n * }\n * return this;\n * }\n * }\n */\n render() {\n return this;\n }\n\n /**\n * Escape HTML entities in a string.\n * Use this method to sanitize user-generated content before inserting it into the DOM.\n * Override this method to provide a custom escape function.\n * This method is inherited by {@link #module_component Component} and used to escape template interpolations.\n * @static\n * @param {string} value String to escape.\n * @return {string} Escaped string.\n */\n static sanitize(value) {\n return `${value}`.replace(/[&<>\"']/g, match => ({\n '&' : '&amp;',\n '<' : '&lt;',\n '>' : '&gt;',\n '\"' : '&quot;',\n '\\'' : '&#039;'\n }[match]));\n }\n\n /**\n * Reset the unique ID counter to 0.\n * This is useful for server-side rendering scenarios where you want to ensure that\n * the generated unique IDs match those on the client, enabling seamless hydration of components.\n * This method is inherited by {@link #module_component Component}.\n * @static\n */\n static resetUid() {\n View.uid = 0;\n }\n}\n\n/**\n * Counter for generating unique IDs for view instances. \n * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like \n * generating element IDs. \n * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration. \n * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated \n * unique IDs match those on the client, enabling seamless hydration of components. \n * @static\n * @memberof module:View\n * @name uid\n * @type {number}\n * @default 0\n */\nView.uid = 0;\n"],"names":[],"mappings":"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"}

@@ -945,3 +945,3 @@ 'use strict';

let out = component;
let found = null;
let found;
// Check if child already exists by key.

@@ -948,0 +948,0 @@ if (component.key) {

@@ -34,3 +34,2 @@ 'use strict';

* @property {object} previous Object containing previous attributes when a change occurs.
* @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.
* @example

@@ -349,2 +348,5 @@ * import { Model } from 'rasti';

* Useful for avoiding naming conflicts or creating a consistent property naming convention.
* @static
* @memberof module:Model
* @name attributePrefix
* @type {string}

@@ -351,0 +353,0 @@ * @default ''

@@ -1,1 +0,1 @@

{"version":3,"file":"Model.cjs","sources":["../src/Model.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\n\n/**\n * - Orchestrates data and business logic.\n * - Emits events when data changes.\n * \n * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified. \n * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects \n * that contain all the necessary functions for manipulating their specific data. \n * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.\n * \n * ## Construction Flow\n * 1. `preinitialize()` is called with all constructor arguments\n * 2. `this.defaults` are resolved (if function, it's called and bound to the model)\n * 3. `parse()` is called with all constructor arguments to process the data\n * 4. `this.attributes` is built by merging defaults and parsed data\n * 5. Getters/setters are generated for each attribute to emit change events\n * \n * @module\n * @extends Emitter\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed to `preinitialize` and `parse` methods\n * @property {object|Function} defaults Default attributes for the model. If a function, it's called bound to the model instance to get defaults.\n * @property {object} previous Object containing previous attributes when a change occurs.\n * @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.\n * @example\n * import { Model } from 'rasti';\n * \n * // User model\n * class User extends Model {\n * preinitialize() {\n * this.defaults = { name : '', email : '', role : 'user' };\n * }\n * }\n * // Order model with nested User and custom methods\n * class Order extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = {\n * id : null,\n * total : 0,\n * status : 'pending',\n * user : null\n * };\n * \n * this.apiUrl = options.apiUrl || '/api/orders';\n * }\n *\n * parse(data, options = {}) {\n * const parsed = { ...data };\n * \n * // Convert user object to User model instance\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * \n * return parsed;\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * \n * async fetch() {\n * try {\n * const response = await fetch(`${this.apiUrl}/${this.id}`);\n * const data = await response.json();\n * \n * // Parse the fetched data and update model\n * const parsed = this.parse(data);\n * this.set(parsed, { source : 'fetch' });\n * \n * return this;\n * } catch (error) {\n * console.error('Failed to fetch order:', error);\n * throw error;\n * }\n * }\n * }\n * \n * // Create order with nested user data\n * const order = new Order({\n * id : 123,\n * total : 99.99,\n * user : { name : 'Alice', email : 'alice@example.com' }\n * });\n * \n * console.log(order.user instanceof User); // true\n * // Serialize with nested models\n * const json = order.toJSON();\n * console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }\n * \n * // Listen to fetch updates\n * order.on('change', (model, changed, options) => {\n * if (options?.source === 'fetch') {\n * console.log('Order updated from server:', changed);\n * }\n * });\n * \n * // Fetch latest data from server\n * await order.fetch();\n */\nexport default class Model extends Emitter {\n constructor() {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Set attributes object with defaults and passed attributes.\n this.attributes = Object.assign({}, getResult(this.defaults, this), this.parse.apply(this, arguments));\n // Object to store previous attributes when a change occurs.\n this.previous = {};\n // Generate getters/setters for every attribute.\n Object.keys(this.attributes).forEach(this.defineAttribute.bind(this));\n }\n\n /**\n * Called before any instantiation logic runs for the Model.\n * Receives all constructor arguments, allowing for flexible initialization patterns.\n * Use this to set up `defaults`, configure the model, or handle custom constructor arguments.\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed from the constructor\n * @example\n * class User extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = { name : '', role : options.defaultRole || 'user' };\n * this.apiEndpoint = options.apiEndpoint || '/users';\n * }\n * }\n * const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });\n */\n preinitialize() {}\n\n /**\n * Generate getter/setter for the given attribute key to emit `change` events.\n * The property name uses `attributePrefix` + key (e.g., with prefix 'attr_', key 'name' becomes 'attr_name').\n * Called internally by the constructor for each key in `this.attributes`.\n * Override with an empty method if you don't want automatic getters/setters.\n * \n * @param {string} key Attribute key from `this.attributes`\n * @example\n * // Custom prefix for all attributes\n * class PrefixedModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * const model = new PrefixedModel({ name: 'Alice' });\n * console.log(model.attr_name); // 'Alice'\n * \n * // Disable automatic getters/setters\n * class ManualModel extends Model {\n * defineAttribute() {\n * // Empty - no getters/setters generated\n * }\n * \n * getName() {\n * return this.get('name'); // Manual getter\n * }\n * }\n */\n defineAttribute(key) {\n Object.defineProperty(\n this,\n `${this.constructor.attributePrefix}${key}`,\n {\n get : () => this.get(key),\n set : (value) => { this.set(key, value); }\n }\n );\n }\n\n /**\n * Get an attribute from `this.attributes`.\n * This method is called internally by generated getters.\n * @param {string} key Attribute key.\n * @return {any} The attribute value.\n */\n get(key) {\n return this.attributes[key];\n }\n\n /**\n * Set one or more attributes into `this.attributes` and emit change events.\n * Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.\n * Additional arguments are passed to change event listeners, enabling custom behavior.\n * \n * @param {string|object} key Attribute key (string) or object containing key-value pairs\n * @param {*} [value] Attribute value (when key is string)\n * @param {...*} [args] Additional arguments passed to event listeners\n * @return {Model} This model instance for chaining\n * @emits change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...args)`\n * @emits change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...args)`\n * @example\n * // Basic usage\n * model.set('name', 'Alice');\n * model.set({ name : 'Alice', age : 30 });\n * \n * // With options for listeners\n * model.set('name', 'Bob', { silent : false, validate : true });\n * model.on('change:name', (model, value, options) => {\n * if (options?.validate) {\n * // Custom validation logic\n * }\n * });\n */\n set(key, value, ...rest) {\n let attrs, args;\n // Handle both `\"key\", value` and `{key: value}` style arguments.\n if (typeof key === 'object') {\n attrs = key;\n args = [value, ...rest];\n } else {\n attrs = { [key] : value };\n args = rest;\n }\n // Are we in a nested `set` call?\n // Calling a `set` inside a `change:attribute` or `change` event listener\n const changing = this._changing;\n this._changing = true;\n // Store changed attributes.\n const changed = {};\n // Store previous attributes.\n if (!changing) {\n this.previous = Object.assign({}, this.attributes);\n }\n // Set attributes.\n Object.keys(attrs).forEach(key => {\n // Use equality to determine if value changed.\n if (attrs[key] !== this.attributes[key]) {\n changed[key] = attrs[key];\n this.attributes[key] = attrs[key];\n }\n });\n\n const changedKeys = Object.keys(changed);\n // Pending `change` event arguments.\n if (changedKeys.length) this._pending = ['change', this, changed, ...args];\n // Emit `change:attribute` events.\n changedKeys.forEach(key => {\n this.emit(`change:${key}`, this, attrs[key], ...args);\n });\n // Don't emit `change` event until the end of the nested \n // `set` calls inside `change:attribute` event listeners.\n if (changing) return this;\n // Emit `change` events, that might be nested.\n while (this._pending) {\n const pendingChange = this._pending;\n this._pending = null;\n this.emit.apply(this, pendingChange);\n }\n // Reset flags.\n this._pending = null;\n this._changing = false;\n\n return this;\n }\n\n /**\n * Transforms and validates data before it becomes model attributes.\n * Called during construction with all constructor arguments, allowing flexible data processing.\n * Override this method to transform incoming data, create nested models, or handle different data formats.\n * \n * @param {object} [data={}] Primary data object to be parsed into attributes\n * @param {...*} [args] Additional arguments from constructor, useful for parsing options\n * @return {object} Processed data that will become the model's attributes\n * @example\n * // Transform nested objects into models\n * class User extends Model {}\n * class Order extends Model {\n * parse(data, options = {}) {\n * // Skip parsing if requested\n * if (options.raw) return data;\n * // Transform user data into User model\n * const parsed = { ...data };\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * return parsed;\n * }\n * }\n * \n * // Usage with parsing options\n * const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model\n * const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object\n */\n parse(data) {\n return data;\n }\n\n /**\n * Return object representation of the model to be used for JSON serialization.\n * By default returns a copy of `this.attributes`.\n * You can override this method to customize serialization behavior, such as calling `toJSON` recursively on nested Model instances.\n * @return {object} Object representation of the model to be used for JSON serialization.\n * @example\n * // Basic usage - returns a copy of model attributes:\n * const user = new Model({ name : 'Alice', age : 30 });\n * const json = user.toJSON();\n * console.log(json); // { name : 'Alice', age : 30 }\n * \n * // Override toJSON for recursive serialization of nested models:\n * class User extends Model {}\n * class Order extends Model {\n * parse(data) {\n * // Ensure user is always a User model\n * return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * }\n * const order = new Order({ id : 1, user : { name : 'Alice' } });\n * const json = order.toJSON();\n * console.log(json); // { id : 1, user : { name : 'Alice' } }\n */\n toJSON() {\n return Object.assign({}, this.attributes);\n }\n}\n\n/**\n * Static property that defines a prefix for generated getters/setters.\n * When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').\n * Useful for avoiding naming conflicts or creating a consistent property naming convention.\n * @type {string}\n * @default ''\n * @example\n * // Set prefix for all models of this class\n * class ApiModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * \n * const user = new ApiModel({ name : 'Alice', email : 'alice@example.com' });\n * console.log(user.attr_name); // 'Alice'\n * console.log(user.attr_email); // 'alice@example.com'\n * \n * // Still access via get/set methods without prefix\n * console.log(user.get('name')); // 'Alice'\n * user.set('name', 'Bob');\n * console.log(user.attr_name); // 'Bob'\n */\nModel.attributePrefix = '';\n"],"names":["getResult"],"mappings":";;;;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAM,KAAK,SAAS,OAAO,CAAC;AAC3C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,EAAE;AACf;AACA,QAAQ,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;AACjD;AACA,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAEA,eAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9G;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,EAAE;AAC1B;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA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"}
{"version":3,"file":"Model.cjs","sources":["../src/Model.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\n\n/**\n * - Orchestrates data and business logic.\n * - Emits events when data changes.\n * \n * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified. \n * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects \n * that contain all the necessary functions for manipulating their specific data. \n * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.\n * \n * ## Construction Flow\n * 1. `preinitialize()` is called with all constructor arguments\n * 2. `this.defaults` are resolved (if function, it's called and bound to the model)\n * 3. `parse()` is called with all constructor arguments to process the data\n * 4. `this.attributes` is built by merging defaults and parsed data\n * 5. Getters/setters are generated for each attribute to emit change events\n * \n * @module\n * @extends Emitter\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed to `preinitialize` and `parse` methods\n * @property {object|Function} defaults Default attributes for the model. If a function, it's called bound to the model instance to get defaults.\n * @property {object} previous Object containing previous attributes when a change occurs.\n * @example\n * import { Model } from 'rasti';\n * \n * // User model\n * class User extends Model {\n * preinitialize() {\n * this.defaults = { name : '', email : '', role : 'user' };\n * }\n * }\n * // Order model with nested User and custom methods\n * class Order extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = {\n * id : null,\n * total : 0,\n * status : 'pending',\n * user : null\n * };\n * \n * this.apiUrl = options.apiUrl || '/api/orders';\n * }\n *\n * parse(data, options = {}) {\n * const parsed = { ...data };\n * \n * // Convert user object to User model instance\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * \n * return parsed;\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * \n * async fetch() {\n * try {\n * const response = await fetch(`${this.apiUrl}/${this.id}`);\n * const data = await response.json();\n * \n * // Parse the fetched data and update model\n * const parsed = this.parse(data);\n * this.set(parsed, { source : 'fetch' });\n * \n * return this;\n * } catch (error) {\n * console.error('Failed to fetch order:', error);\n * throw error;\n * }\n * }\n * }\n * \n * // Create order with nested user data\n * const order = new Order({\n * id : 123,\n * total : 99.99,\n * user : { name : 'Alice', email : 'alice@example.com' }\n * });\n * \n * console.log(order.user instanceof User); // true\n * // Serialize with nested models\n * const json = order.toJSON();\n * console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }\n * \n * // Listen to fetch updates\n * order.on('change', (model, changed, options) => {\n * if (options?.source === 'fetch') {\n * console.log('Order updated from server:', changed);\n * }\n * });\n * \n * // Fetch latest data from server\n * await order.fetch();\n */\nexport default class Model extends Emitter {\n constructor() {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Set attributes object with defaults and passed attributes.\n this.attributes = Object.assign({}, getResult(this.defaults, this), this.parse.apply(this, arguments));\n // Object to store previous attributes when a change occurs.\n this.previous = {};\n // Generate getters/setters for every attribute.\n Object.keys(this.attributes).forEach(this.defineAttribute.bind(this));\n }\n\n /**\n * Called before any instantiation logic runs for the Model.\n * Receives all constructor arguments, allowing for flexible initialization patterns.\n * Use this to set up `defaults`, configure the model, or handle custom constructor arguments.\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed from the constructor\n * @example\n * class User extends Model {\n * preinitialize(attributes, options = {}) {\n * this.defaults = { name : '', role : options.defaultRole || 'user' };\n * this.apiEndpoint = options.apiEndpoint || '/users';\n * }\n * }\n * const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });\n */\n preinitialize() {}\n\n /**\n * Generate getter/setter for the given attribute key to emit `change` events.\n * The property name uses `attributePrefix` + key (e.g., with prefix 'attr_', key 'name' becomes 'attr_name').\n * Called internally by the constructor for each key in `this.attributes`.\n * Override with an empty method if you don't want automatic getters/setters.\n * \n * @param {string} key Attribute key from `this.attributes`\n * @example\n * // Custom prefix for all attributes\n * class PrefixedModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * const model = new PrefixedModel({ name: 'Alice' });\n * console.log(model.attr_name); // 'Alice'\n * \n * // Disable automatic getters/setters\n * class ManualModel extends Model {\n * defineAttribute() {\n * // Empty - no getters/setters generated\n * }\n * \n * getName() {\n * return this.get('name'); // Manual getter\n * }\n * }\n */\n defineAttribute(key) {\n Object.defineProperty(\n this,\n `${this.constructor.attributePrefix}${key}`,\n {\n get : () => this.get(key),\n set : (value) => { this.set(key, value); }\n }\n );\n }\n\n /**\n * Get an attribute from `this.attributes`.\n * This method is called internally by generated getters.\n * @param {string} key Attribute key.\n * @return {any} The attribute value.\n */\n get(key) {\n return this.attributes[key];\n }\n\n /**\n * Set one or more attributes into `this.attributes` and emit change events.\n * Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.\n * Additional arguments are passed to change event listeners, enabling custom behavior.\n * \n * @param {string|object} key Attribute key (string) or object containing key-value pairs\n * @param {*} [value] Attribute value (when key is string)\n * @param {...*} [args] Additional arguments passed to event listeners\n * @return {Model} This model instance for chaining\n * @emits change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...args)`\n * @emits change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...args)`\n * @example\n * // Basic usage\n * model.set('name', 'Alice');\n * model.set({ name : 'Alice', age : 30 });\n * \n * // With options for listeners\n * model.set('name', 'Bob', { silent : false, validate : true });\n * model.on('change:name', (model, value, options) => {\n * if (options?.validate) {\n * // Custom validation logic\n * }\n * });\n */\n set(key, value, ...rest) {\n let attrs, args;\n // Handle both `\"key\", value` and `{key: value}` style arguments.\n if (typeof key === 'object') {\n attrs = key;\n args = [value, ...rest];\n } else {\n attrs = { [key] : value };\n args = rest;\n }\n // Are we in a nested `set` call?\n // Calling a `set` inside a `change:attribute` or `change` event listener\n const changing = this._changing;\n this._changing = true;\n // Store changed attributes.\n const changed = {};\n // Store previous attributes.\n if (!changing) {\n this.previous = Object.assign({}, this.attributes);\n }\n // Set attributes.\n Object.keys(attrs).forEach(key => {\n // Use equality to determine if value changed.\n if (attrs[key] !== this.attributes[key]) {\n changed[key] = attrs[key];\n this.attributes[key] = attrs[key];\n }\n });\n\n const changedKeys = Object.keys(changed);\n // Pending `change` event arguments.\n if (changedKeys.length) this._pending = ['change', this, changed, ...args];\n // Emit `change:attribute` events.\n changedKeys.forEach(key => {\n this.emit(`change:${key}`, this, attrs[key], ...args);\n });\n // Don't emit `change` event until the end of the nested \n // `set` calls inside `change:attribute` event listeners.\n if (changing) return this;\n // Emit `change` events, that might be nested.\n while (this._pending) {\n const pendingChange = this._pending;\n this._pending = null;\n this.emit.apply(this, pendingChange);\n }\n // Reset flags.\n this._pending = null;\n this._changing = false;\n\n return this;\n }\n\n /**\n * Transforms and validates data before it becomes model attributes.\n * Called during construction with all constructor arguments, allowing flexible data processing.\n * Override this method to transform incoming data, create nested models, or handle different data formats.\n * \n * @param {object} [data={}] Primary data object to be parsed into attributes\n * @param {...*} [args] Additional arguments from constructor, useful for parsing options\n * @return {object} Processed data that will become the model's attributes\n * @example\n * // Transform nested objects into models\n * class User extends Model {}\n * class Order extends Model {\n * parse(data, options = {}) {\n * // Skip parsing if requested\n * if (options.raw) return data;\n * // Transform user data into User model\n * const parsed = { ...data };\n * if (data.user && !(data.user instanceof User)) {\n * parsed.user = new User(data.user);\n * }\n * return parsed;\n * }\n * }\n * \n * // Usage with parsing options\n * const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model\n * const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object\n */\n parse(data) {\n return data;\n }\n\n /**\n * Return object representation of the model to be used for JSON serialization.\n * By default returns a copy of `this.attributes`.\n * You can override this method to customize serialization behavior, such as calling `toJSON` recursively on nested Model instances.\n * @return {object} Object representation of the model to be used for JSON serialization.\n * @example\n * // Basic usage - returns a copy of model attributes:\n * const user = new Model({ name : 'Alice', age : 30 });\n * const json = user.toJSON();\n * console.log(json); // { name : 'Alice', age : 30 }\n * \n * // Override toJSON for recursive serialization of nested models:\n * class User extends Model {}\n * class Order extends Model {\n * parse(data) {\n * // Ensure user is always a User model\n * return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };\n * }\n * \n * toJSON() {\n * const result = {};\n * for (const [key, value] of Object.entries(this.attributes)) {\n * if (value instanceof Model) {\n * result[key] = value.toJSON();\n * } else {\n * result[key] = value;\n * }\n * }\n * return result;\n * }\n * }\n * const order = new Order({ id : 1, user : { name : 'Alice' } });\n * const json = order.toJSON();\n * console.log(json); // { id : 1, user : { name : 'Alice' } }\n */\n toJSON() {\n return Object.assign({}, this.attributes);\n }\n}\n\n/**\n * Static property that defines a prefix for generated getters/setters.\n * When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').\n * Useful for avoiding naming conflicts or creating a consistent property naming convention.\n * @static\n * @memberof module:Model\n * @name attributePrefix\n * @type {string}\n * @default ''\n * @example\n * // Set prefix for all models of this class\n * class ApiModel extends Model {\n * static attributePrefix = 'attr_';\n * }\n * \n * const user = new ApiModel({ name : 'Alice', email : 'alice@example.com' });\n * console.log(user.attr_name); // 'Alice'\n * console.log(user.attr_email); // 'alice@example.com'\n * \n * // Still access via get/set methods without prefix\n * console.log(user.get('name')); // 'Alice'\n * user.set('name', 'Bob');\n * console.log(user.attr_name); // 'Bob'\n */\nModel.attributePrefix = '';\n"],"names":["getResult"],"mappings":";;;;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAM,KAAK,SAAS,OAAO,CAAC;AAC3C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,EAAE;AACf;AACA,QAAQ,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;AACjD;AACA,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAEA,eAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9G;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,EAAE;AAC1B;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA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"}

@@ -38,3 +38,3 @@ 'use strict';

* @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
* @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @property {string} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @example

@@ -424,2 +424,4 @@ * import { View, Model } from 'rasti';

* @static
* @memberof module:View
* @name uid
* @type {number}

@@ -426,0 +428,0 @@ * @default 0

@@ -1,1 +0,1 @@

{"version":3,"file":"View.cjs","sources":["../src/View.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\nimport validateListener from './utils/validateListener.js';\nimport createDevelopmentErrorMessage from './utils/createDevelopmentErrorMessage.js';\nimport createProductionErrorMessage from './utils/createProductionErrorMessage.js';\nimport __DEV__ from './utils/dev.js';\n\n/*\n * These option keys will be extended on the view instance.\n */\nconst viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];\n\n/**\n * - Listens for changes and renders the UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.\n * However, views can also be independent and have no associated data. \n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.\n * @module\n * @extends Emitter\n * @param {object} options Object containing options. The following keys will be merged into the view instance: `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.\n * @property {node|Function} el Every view has a root DOM element stored at `this.el`. If not present, it will be created. If `this.el` is a function, it will be called to get the element at `this.ensureElement`, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {string|Function} tag If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. Default is `div`. If it is a function, it will be called to get the tag, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} attributes If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. If it is a function, it will be called to get the attributes object, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.\n * @property {object} model A model or any object containing data and business logic.\n * @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}. \n * @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.\n * @example\n * import { View, Model } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute to 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re-render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;\n * }\n *\n * render() {\n * if (this.template) {\n * this.el.innerHTML = this.template(this.model);\n * }\n * return this;\n * }\n * }\n * // Render view and append view's element into the body.\n * document.body.appendChild(new Timer().render().el);\n */\nexport default class View extends Emitter {\n constructor(options = {}) {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Store delegated event listeners,\n // so they can be unbound later.\n this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n this.children = [];\n // Mutable array to store handlers to be called on destroy.\n this.destroyQueue = [];\n this.viewOptions = [];\n // Extend \"this\" with options.\n viewOptions.forEach(key => {\n if (key in options) {\n this[key] = options[key];\n this.viewOptions.push(key);\n }\n });\n // Ensure that the view has a unique id at `this.uid`.\n this.ensureUid();\n // Ensure that the view has a root element at `this.el`.\n this.ensureElement();\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} options The view options.\n */\n preinitialize() {}\n\n /**\n * Returns the first element that matches the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */\n $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */\n $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n * @return {View} Return `this` for chaining.\n */\n destroy() {\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Stop listening to events.\n this.stopListening();\n // Unbind `this` events.\n this.off();\n // Call destroy queue.\n this.destroyQueue.forEach(fn => fn());\n this.destroyQueue = [];\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n // Set destroyed flag.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `onDestroy` lifecycle method is called after the view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */\n onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {View} child\n * @return {View}\n */\n addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */\n destroyChildren() {\n this.children.forEach(child => child.destroy());\n this.children = [];\n }\n\n /**\n * Ensure that the view has a unique id at `this.uid`.\n */\n ensureUid() {\n if (!this.uid) this.uid = `r${++View.uid}`;\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from the constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */\n ensureElement() {\n // Element is already present.\n if (this.el) {\n // If \"this.el\" is a function, call it to get the element.\n this.el = getResult(this.el, this);\n } else {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n const tag = getResult(this.tag, this);\n const attrs = getResult(this.attributes, this);\n this.el = this.createElement(tag, attrs);\n }\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from the constructor if `this.el` is undefined, to ensure\n * the view has a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attributes Attributes for the element.\n * @return {node} The created element.\n */\n createElement(tag = 'div', attributes = {}) {\n // Create DOM element.\n let el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attributes)\n .forEach(key => el.setAttribute(key, attributes[key]));\n\n return el;\n }\n\n /**\n * Remove `this.el` from the DOM.\n * @return {View} Return `this` for chaining.\n */\n removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events object is not provided, \n * it defaults to using `this.events`. If `this.events` is a function, it will be called to get the events object.\n * \n * The events object should follow the format `{'event selector': 'listener'}`:\n * - `event`: The type of event (e.g., 'click').\n * - `selector`: A CSS selector to match the event target. If omitted, the event is bound to the root element.\n * - `listener`: A function or a string representing a method name on the view. The method will be called with `this` bound to the view instance.\n * \n * By default, `delegateEvents` is called within the View's constructor. If you have a simple events object, \n * all of your DOM events will be connected automatically, and you will not need to call this function manually.\n * \n * All attached listeners are bound to the view, ensuring that `this` refers to the view object when the listeners are invoked.\n * When `delegateEvents` is called again, possibly with a different events object, all previous listeners are removed and delegated afresh.\n * \n * **Listener signature:** `(event, view, matched)`\n * - `event`: The native DOM event object.\n * - `view`: The current view instance (`this`).\n * - `matched`: The element that satisfies the selector. If no selector is provided, it will be the view's root element (`this.el`).\n *\n * If more than one ancestor between `event.target` and the view's root element matches the selector, the listener will be\n * invoked **once for each matched element** (from inner to outer).\n *\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to the root element.\n * @return {View} Returns `this` for chaining.\n * @example\n * // Using prototype (recommended for static events)\n * class Modal extends View {\n * onClickOk(event, view, matched) {\n * // matched === the button.ok element that was clicked\n * this.close();\n * }\n * \n * onClickCancel() {\n * this.destroy();\n * }\n * }\n * Modal.prototype.events = {\n * 'click button.ok': 'onClickOk',\n * 'click button.cancel': 'onClickCancel',\n * 'submit form': 'onSubmit'\n * };\n * \n * // Using a function for dynamic events\n * class DynamicView extends View {\n * events() {\n * return {\n * [`click .${this.model.buttonClass}`]: 'onButtonClick',\n * 'click': 'onRootClick'\n * };\n * }\n * }\n */\n delegateEvents(events) {\n if (!events) events = getResult(this.events, this);\n if (!events) return this;\n\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n const eventTypes = {};\n\n Object.keys(events).forEach(key => {\n const match = key.match(/^(\\w+)(?:\\s+(.+))*$/);\n\n if (!match) {\n const message = `Invalid event format: ${key}`;\n throw new Error(__DEV__ ? createDevelopmentErrorMessage(message) : createProductionErrorMessage(message));\n }\n // Extract type and selector from the event key.\n const [,type, selector] = match;\n\n let listener = events[key];\n // Listener may be a string representing a method name on the view, or a function.\n if (typeof listener === 'string') listener = this[listener];\n // Validate listener is a function.\n validateListener(listener);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push([selector, listener]);\n });\n\n Object.keys(eventTypes).forEach(type => {\n // Listener for the type of event.\n const typeListener = (event) => {\n let node = event.target;\n // Traverse ancestors until reaching the view root (`this.el`).\n while (node) {\n if (node.matches) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(([selector, listener]) => {\n if ((node === this.el && !selector) || (node !== this.el && node.matches(selector))) {\n listener.call(this, event, this, node);\n }\n });\n }\n // Continue traversing ancestors until reaching the view root (`this.el`) or stopping propagation.\n node = node === this.el || event.cancelBubble ? null : node.parentElement;\n }\n };\n // Store the type and listener in the delegated event listeners array.\n this.delegatedEventListeners.push([type, typeListener]);\n // Add the event listener to the element.\n this.el.addEventListener(type, typeListener);\n });\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. \n * Useful if you want to disable or remove a view from the DOM temporarily. \n * Called automatically when the view is destroyed and when `delegateEvents` is called again.\n * @return {View} Return `this` for chaining.\n */\n undelegateEvents() {\n this.delegatedEventListeners.forEach(([type, listener]) => {\n this.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `render` is the core function that your view should override, in order to populate its element (`this.el`), with the appropriate HTML. The convention is for `render` to always return `this`. \n * Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using {@link #module_component Component} instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline. \n * If you add any child views, you should call `this.destroyChildren` before re-rendering.\n * \n * @return {View} Returns `this` for chaining.\n * @example\n * class UserView extends View {\n * render() {\n * if (this.template) {\n * const model = this.model;\n * // Sanitize model attributes to prevent XSS attacks.\n * const safeData = {\n * name : View.sanitize(model.name),\n * email : View.sanitize(model.email),\n * bio : View.sanitize(model.bio)\n * };\n * this.el.innerHTML = this.template(safeData);\n * }\n * return this;\n * }\n * }\n */\n render() {\n return this;\n }\n\n /**\n * Escape HTML entities in a string.\n * Use this method to sanitize user-generated content before inserting it into the DOM.\n * Override this method to provide a custom escape function.\n * This method is inherited by {@link #module_component Component} and used to escape template interpolations.\n * @static\n * @param {string} value String to escape.\n * @return {string} Escaped string.\n */\n static sanitize(value) {\n return `${value}`.replace(/[&<>\"']/g, match => ({\n '&' : '&amp;',\n '<' : '&lt;',\n '>' : '&gt;',\n '\"' : '&quot;',\n '\\'' : '&#039;'\n }[match]));\n }\n\n /**\n * Reset the unique ID counter to 0.\n * This is useful for server-side rendering scenarios where you want to ensure that\n * the generated unique IDs match those on the client, enabling seamless hydration of components.\n * This method is inherited by {@link #module_component Component}.\n * @static\n */\n static resetUid() {\n View.uid = 0;\n }\n}\n\n/**\n * Counter for generating unique IDs for view instances. \n * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like \n * generating element IDs. \n * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration. \n * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated \n * unique IDs match those on the client, enabling seamless hydration of components. \n * @static\n * @type {number}\n * @default 0\n */\nView.uid = 0;\n"],"names":["getResult","__DEV__","createDevelopmentErrorMessage","createProductionErrorMessage","validateListener"],"mappings":"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"}
{"version":3,"file":"View.cjs","sources":["../src/View.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\nimport validateListener from './utils/validateListener.js';\nimport createDevelopmentErrorMessage from './utils/createDevelopmentErrorMessage.js';\nimport createProductionErrorMessage from './utils/createProductionErrorMessage.js';\nimport __DEV__ from './utils/dev.js';\n\n/*\n * These option keys will be extended on the view instance.\n */\nconst viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];\n\n/**\n * - Listens for changes and renders the UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render data from a specific model or multiple models.\n * However, views can also be independent and have no associated data. \n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to `div`) and `this.attributes`.\n * @module\n * @extends Emitter\n * @param {object} options Object containing options. The following keys will be merged into the view instance: `el`, `tag`, `attributes`, `events`, `model`, `template`, `onDestroy`.\n * @property {node|Function} el Every view has a root DOM element stored at `this.el`. If not present, it will be created. If `this.el` is a function, it will be called to get the element at `this.ensureElement`, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {string|Function} tag If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. Default is `div`. If it is a function, it will be called to get the tag, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} attributes If `this.el` is not present, an element will be created using `this.tag` and `this.attributes`. If it is a function, it will be called to get the attributes object, bound to the view instance. See {@link module_view__ensureelement View.ensureElement}.\n * @property {object|Function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.\n * @property {object} model A model or any object containing data and business logic.\n * @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}. \n * @property {string} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.\n * @example\n * import { View, Model } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute to 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re-render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;\n * }\n *\n * render() {\n * if (this.template) {\n * this.el.innerHTML = this.template(this.model);\n * }\n * return this;\n * }\n * }\n * // Render view and append view's element into the body.\n * document.body.appendChild(new Timer().render().el);\n */\nexport default class View extends Emitter {\n constructor(options = {}) {\n super();\n // Call preinitialize.\n this.preinitialize.apply(this, arguments);\n // Store delegated event listeners,\n // so they can be unbound later.\n this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n this.children = [];\n // Mutable array to store handlers to be called on destroy.\n this.destroyQueue = [];\n this.viewOptions = [];\n // Extend \"this\" with options.\n viewOptions.forEach(key => {\n if (key in options) {\n this[key] = options[key];\n this.viewOptions.push(key);\n }\n });\n // Ensure that the view has a unique id at `this.uid`.\n this.ensureUid();\n // Ensure that the view has a root element at `this.el`.\n this.ensureElement();\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} options The view options.\n */\n preinitialize() {}\n\n /**\n * Returns the first element that matches the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */\n $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */\n $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n * @return {View} Return `this` for chaining.\n */\n destroy() {\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Stop listening to events.\n this.stopListening();\n // Unbind `this` events.\n this.off();\n // Call destroy queue.\n this.destroyQueue.forEach(fn => fn());\n this.destroyQueue = [];\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n // Set destroyed flag.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `onDestroy` lifecycle method is called after the view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */\n onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {View} child\n * @return {View}\n */\n addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */\n destroyChildren() {\n this.children.forEach(child => child.destroy());\n this.children = [];\n }\n\n /**\n * Ensure that the view has a unique id at `this.uid`.\n */\n ensureUid() {\n if (!this.uid) this.uid = `r${++View.uid}`;\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from the constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */\n ensureElement() {\n // Element is already present.\n if (this.el) {\n // If \"this.el\" is a function, call it to get the element.\n this.el = getResult(this.el, this);\n } else {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n const tag = getResult(this.tag, this);\n const attrs = getResult(this.attributes, this);\n this.el = this.createElement(tag, attrs);\n }\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from the constructor if `this.el` is undefined, to ensure\n * the view has a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attributes Attributes for the element.\n * @return {node} The created element.\n */\n createElement(tag = 'div', attributes = {}) {\n // Create DOM element.\n let el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attributes)\n .forEach(key => el.setAttribute(key, attributes[key]));\n\n return el;\n }\n\n /**\n * Remove `this.el` from the DOM.\n * @return {View} Return `this` for chaining.\n */\n removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events object is not provided, \n * it defaults to using `this.events`. If `this.events` is a function, it will be called to get the events object.\n * \n * The events object should follow the format `{'event selector': 'listener'}`:\n * - `event`: The type of event (e.g., 'click').\n * - `selector`: A CSS selector to match the event target. If omitted, the event is bound to the root element.\n * - `listener`: A function or a string representing a method name on the view. The method will be called with `this` bound to the view instance.\n * \n * By default, `delegateEvents` is called within the View's constructor. If you have a simple events object, \n * all of your DOM events will be connected automatically, and you will not need to call this function manually.\n * \n * All attached listeners are bound to the view, ensuring that `this` refers to the view object when the listeners are invoked.\n * When `delegateEvents` is called again, possibly with a different events object, all previous listeners are removed and delegated afresh.\n * \n * **Listener signature:** `(event, view, matched)`\n * - `event`: The native DOM event object.\n * - `view`: The current view instance (`this`).\n * - `matched`: The element that satisfies the selector. If no selector is provided, it will be the view's root element (`this.el`).\n *\n * If more than one ancestor between `event.target` and the view's root element matches the selector, the listener will be\n * invoked **once for each matched element** (from inner to outer).\n *\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to the root element.\n * @return {View} Returns `this` for chaining.\n * @example\n * // Using prototype (recommended for static events)\n * class Modal extends View {\n * onClickOk(event, view, matched) {\n * // matched === the button.ok element that was clicked\n * this.close();\n * }\n * \n * onClickCancel() {\n * this.destroy();\n * }\n * }\n * Modal.prototype.events = {\n * 'click button.ok': 'onClickOk',\n * 'click button.cancel': 'onClickCancel',\n * 'submit form': 'onSubmit'\n * };\n * \n * // Using a function for dynamic events\n * class DynamicView extends View {\n * events() {\n * return {\n * [`click .${this.model.buttonClass}`]: 'onButtonClick',\n * 'click': 'onRootClick'\n * };\n * }\n * }\n */\n delegateEvents(events) {\n if (!events) events = getResult(this.events, this);\n if (!events) return this;\n\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n const eventTypes = {};\n\n Object.keys(events).forEach(key => {\n const match = key.match(/^(\\w+)(?:\\s+(.+))*$/);\n\n if (!match) {\n const message = `Invalid event format: ${key}`;\n throw new Error(__DEV__ ? createDevelopmentErrorMessage(message) : createProductionErrorMessage(message));\n }\n // Extract type and selector from the event key.\n const [,type, selector] = match;\n\n let listener = events[key];\n // Listener may be a string representing a method name on the view, or a function.\n if (typeof listener === 'string') listener = this[listener];\n // Validate listener is a function.\n validateListener(listener);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push([selector, listener]);\n });\n\n Object.keys(eventTypes).forEach(type => {\n // Listener for the type of event.\n const typeListener = (event) => {\n let node = event.target;\n // Traverse ancestors until reaching the view root (`this.el`).\n while (node) {\n if (node.matches) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(([selector, listener]) => {\n if ((node === this.el && !selector) || (node !== this.el && node.matches(selector))) {\n listener.call(this, event, this, node);\n }\n });\n }\n // Continue traversing ancestors until reaching the view root (`this.el`) or stopping propagation.\n node = node === this.el || event.cancelBubble ? null : node.parentElement;\n }\n };\n // Store the type and listener in the delegated event listeners array.\n this.delegatedEventListeners.push([type, typeListener]);\n // Add the event listener to the element.\n this.el.addEventListener(type, typeListener);\n });\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. \n * Useful if you want to disable or remove a view from the DOM temporarily. \n * Called automatically when the view is destroyed and when `delegateEvents` is called again.\n * @return {View} Return `this` for chaining.\n */\n undelegateEvents() {\n this.delegatedEventListeners.forEach(([type, listener]) => {\n this.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `render` is the core function that your view should override, in order to populate its element (`this.el`), with the appropriate HTML. The convention is for `render` to always return `this`. \n * Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using {@link #module_component Component} instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline. \n * If you add any child views, you should call `this.destroyChildren` before re-rendering.\n * \n * @return {View} Returns `this` for chaining.\n * @example\n * class UserView extends View {\n * render() {\n * if (this.template) {\n * const model = this.model;\n * // Sanitize model attributes to prevent XSS attacks.\n * const safeData = {\n * name : View.sanitize(model.name),\n * email : View.sanitize(model.email),\n * bio : View.sanitize(model.bio)\n * };\n * this.el.innerHTML = this.template(safeData);\n * }\n * return this;\n * }\n * }\n */\n render() {\n return this;\n }\n\n /**\n * Escape HTML entities in a string.\n * Use this method to sanitize user-generated content before inserting it into the DOM.\n * Override this method to provide a custom escape function.\n * This method is inherited by {@link #module_component Component} and used to escape template interpolations.\n * @static\n * @param {string} value String to escape.\n * @return {string} Escaped string.\n */\n static sanitize(value) {\n return `${value}`.replace(/[&<>\"']/g, match => ({\n '&' : '&amp;',\n '<' : '&lt;',\n '>' : '&gt;',\n '\"' : '&quot;',\n '\\'' : '&#039;'\n }[match]));\n }\n\n /**\n * Reset the unique ID counter to 0.\n * This is useful for server-side rendering scenarios where you want to ensure that\n * the generated unique IDs match those on the client, enabling seamless hydration of components.\n * This method is inherited by {@link #module_component Component}.\n * @static\n */\n static resetUid() {\n View.uid = 0;\n }\n}\n\n/**\n * Counter for generating unique IDs for view instances. \n * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like \n * generating element IDs. \n * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration. \n * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated \n * unique IDs match those on the client, enabling seamless hydration of components. \n * @static\n * @memberof module:View\n * @name uid\n * @type {number}\n * @default 0\n */\nView.uid = 0;\n"],"names":["getResult","__DEV__","createDevelopmentErrorMessage","createProductionErrorMessage","validateListener"],"mappings":"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"}
{
"name": "rasti",
"version": "4.0.1",
"version": "4.1.0-alpha.0",
"description": "Modern MVC for building user interfaces",

@@ -8,5 +8,6 @@ "type": "module",

"module": "es/index.js",
"browser": "dist/rasti.min.js",
"types": "types/index.d.ts",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./es/index.js",

@@ -17,4 +18,10 @@ "require": "./lib/index.cjs",

"./*": {
"import": "./es/*",
"require": "./lib/*"
"types": "./types/*.d.ts",
"import": "./es/*.js",
"require": "./lib/*.cjs"
},
"./*.js": {
"types": "./types/*.d.ts",
"import": "./es/*.js",
"require": "./lib/*.cjs"
}

@@ -26,12 +33,19 @@ },

"lib",
"src"
"src",
"types"
],
"directories": {
"example": "example",
"test": "test",
"doc": "docs"
},
"scripts": {
"clean": "node -e \"const fs=require('fs');['lib','dist','es'].forEach(d=>fs.rmSync(d,{recursive:true,force:true}))\"",
"build": "rollup -c",
"prepare": "npm run clean && npm run test && npm run build",
"posttest": "npm run lint",
"test": "mocha --require jsdom-global/register --reporter nyan test/*.js",
"test:types": "tsd",
"posttest": "npm run lint && npm run test:types",
"lint": "eslint src test",
"test": "mocha --require jsdom-global/register --reporter nyan test/*.js",
"version": "node -e \"const fs=require('fs');const {execSync}=require('child_process');const v=require('./package.json').version;const re=/rasti@(v)?\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?/g;const files=['README.md', 'docs/AGENTS.md', 'example/todo/index.html'];files.forEach(f=>{const t=fs.readFileSync(f,'utf8');fs.writeFileSync(f,t.replace(re,(m,p)=>'rasti@'+(p||'')+v));});execSync('git add '+files.join(' '),{stdio:'inherit'});\"",
"build": "rollup -c",
"clean": "node -e \"const fs=require('fs');['lib','dist','es'].forEach(d=>fs.rmSync(d,{recursive:true,force:true}))\"",
"version": "node scripts/bump-cdn-links.js && node scripts/release-changelog.js",
"docs:api": "jsdoc2md --module-index-format grouped --helper jsdoc2md/helper.cjs --partial jsdoc2md/header.hbs --partial jsdoc2md/sig-link.hbs --partial jsdoc2md/sig-link-html.hbs --partial jsdoc2md/sig-link-parent.hbs --partial jsdoc2md/link.hbs --files 'src/**/*.js' > docs/api.md"

@@ -60,20 +74,16 @@ },

"devDependencies": {
"@eslint/js": "^9.34.0",
"@rollup/plugin-replace": "^6.0.3",
"@rollup/plugin-terser": "^1.0.0",
"chai": "^6.0.1",
"eslint": "^9.34.0",
"glob": "^13.0.6",
"globals": "^16.3.0",
"jsdoc-to-markdown": "^9.1.2",
"jsdom": "^29.0.1",
"@eslint/js": "10.0.1",
"@rollup/plugin-replace": "6.0.3",
"@rollup/plugin-terser": "1.0.0",
"chai": "6.2.2",
"eslint": "10.4.1",
"glob": "13.0.6",
"globals": "17.6.0",
"jsdoc-to-markdown": "9.1.3",
"jsdom": "29.1.1",
"jsdom-global": "3.0.2",
"mocha": "^11.7.5",
"rollup": "^4.60.1"
},
"directories": {
"example": "example",
"test": "test",
"doc": "docs"
"mocha": "11.7.6",
"rollup": "4.61.0",
"tsd": "0.33.0"
}
}
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.1/docs/logo-dark.svg">
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.1/docs/logo.svg" height="120">
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.0/docs/logo-dark.svg">
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.0/docs/logo.svg" height="120">
</picture>

@@ -16,3 +16,3 @@ </p>

[![Travis (.com)](https://img.shields.io/travis/com/8tentaculos/rasti)](https://app.travis-ci.com/8tentaculos/rasti)
[![CI](https://github.com/8tentaculos/rasti/actions/workflows/ci.yml/badge.svg)](https://github.com/8tentaculos/rasti/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/rasti.svg)](https://www.npmjs.com/package/rasti)

@@ -22,2 +22,3 @@ [![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/rasti)](https://unpkg.com/rasti/dist/rasti.min.js)

[![jsDelivr hits (npm)](https://img.shields.io/jsdelivr/npm/hm/rasti)](https://www.jsdelivr.com/package/npm/rasti)
[![license](https://img.shields.io/npm/l/rasti.svg)](https://github.com/8tentaculos/rasti/blob/master/LICENSE)

@@ -40,2 +41,4 @@ ## Key Features

Built on modern web standards, no tooling required.
- **TypeScript Support** 🧩
Ships with type definitions for strict typing of models, views, components, props, and events.

@@ -185,3 +188,3 @@ ## Getting Started

- **Just the Right Abstraction**
Keeps you close to the DOM with no over-engineering. Fully hackable, if you're curious about how something works, just check the source code.
Keeps you close to the DOM with no over-engineering. Fully hackable — if you're curious about how something works, just check the source code.

@@ -196,2 +199,84 @@ ## Example

## TypeScript
**Rasti** ships with TypeScript declarations out of the box. The types are bundled in the package and resolved automatically.
### Components
Pass generics explicitly to type the resulting class:
```ts
const Header = Component.create<{ handleAddTodo: (title: string) => void }>`
<header>...</header>
`;
new Header({ handleAddTodo: (t) => console.log(t) }); // ✅
// With a typed model:
const App = Component.create<{}, any, AppModel>`<main>...</main>`;
App.mount({ model: new AppModel() }, document.body);
```
Without generics, `Component.create` stays permissive (parity with JS):
```ts
const Plain = Component.create`<div></div>`;
new Plain({ anything: 'goes' }); // ✅
```
### Models
Type the attributes with `Model<Attrs>`. Use **declaration merging** to surface the auto-generated getters/setters as instance properties:
```ts
import { Model } from 'rasti';
interface TodoAttrs { title: string; completed: boolean; }
class Todo extends Model<TodoAttrs> {
preinitialize() {
this.defaults = { title: '', completed: false };
}
toggle() { this.completed = !this.completed; }
}
interface Todo extends TodoAttrs {} // Exposes this.title, this.completed
const t = new Todo({ title: 'x' });
t.title.toUpperCase(); // ✅
t.on('change:completed', (m, value) => value && /* boolean */ console.log('done'));
```
### Helper types
```ts
import {
EventHandler,
RenderExpression,
Attrs,
Props,
State,
ComponentModel,
} from 'rasti';
// Typed event handler with `this` bound to the component
const onClick: EventHandler<Counter, MouseEvent> = function(ev) {
this.props.label;
};
// Typed render expression (`(component) => any`)
const renderLabel: RenderExpression<Counter> = ({ props }) => props.label;
// Extract types from existing classes
type T = Attrs<Todo>; // TodoAttrs
type P = Props<Counter>; // CounterProps
type S = State<Counter>; // CounterState
```
### Known limitations
- **Template interpolation callbacks are `any`**. Functions inside `Component.create\`...\`` template literals (`${({ model }) => ...}`, `onClick=${function() { this.x }}`) cannot be inferred from the surrounding template. To type them, annotate explicitly: `function(this: MyComponent, ev) { ... }` or `({ model }: MyComponent) => ...`.
- **`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.
- **`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).
- **`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`.
## Working with LLMs

@@ -217,2 +302,1 @@

Contributions are welcome! Share feature ideas or report bugs on our [GitHub Issues page](https://github.com/8tentaculos/rasti/issues).

@@ -936,3 +936,3 @@ import View from './View.js';

let out = component;
let found = null;
let found;
// Check if child already exists by key.

@@ -939,0 +939,0 @@ if (component.key) {

@@ -26,3 +26,2 @@ import Emitter from './Emitter.js';

* @property {object} previous Object containing previous attributes when a change occurs.
* @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.
* @example

@@ -341,2 +340,5 @@ * import { Model } from 'rasti';

* Useful for avoiding naming conflicts or creating a consistent property naming convention.
* @static
* @memberof module:Model
* @name attributePrefix
* @type {string}

@@ -343,0 +345,0 @@ * @default ''

@@ -34,3 +34,3 @@ import Emitter from './Emitter.js';

* @property {Function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
* @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @property {string} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
* @example

@@ -420,2 +420,4 @@ * import { View, Model } from 'rasti';

* @static
* @memberof module:View
* @name uid
* @type {number}

@@ -422,0 +424,0 @@ * @default 0

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display