{"version":3,"file":"Emitter.cjs","sources":["../src/Emitter.js"],"sourcesContent":["import validateListener from './utils/validateListener.js';\n\n/**\n * `Emitter` is a class that provides an easy way to implement the observer pattern \n * in your applications.  \n * It can be extended to create new classes that have the ability to emit and bind custom named events.   \n * Emitter is used by `Model` and `View` classes, which inherit from it to implement \n * event-driven functionality.\n * \n * ## Inverse of Control Pattern\n * \n * The Emitter class includes \"inverse of control\" methods (`listenTo`, `listenToOnce`, `stopListening`) \n * that allow an object to manage its own listening relationships. Instead of:\n * \n * ```javascript\n * // Traditional approach - harder to clean up\n * otherObject.on('change', this.myHandler);\n * otherObject.on('destroy', this.cleanup);\n * // Later you need to remember to clean up each listener\n * otherObject.off('change', this.myHandler);\n * otherObject.off('destroy', this.cleanup);\n * ```\n * \n * You can use:\n * \n * ```javascript\n * // Inverse of control - easier cleanup\n * this.listenTo(otherObject, 'change', this.myHandler);\n * this.listenTo(otherObject, 'destroy', this.cleanup);\n * // Later, clean up ALL listeners at once\n * this.stopListening(); // Removes all listening relationships\n * ```\n * \n * This pattern is particularly useful for preventing memory leaks and simplifying cleanup\n * in component lifecycle management.\n *\n * @module\n * @example\n * import { Emitter } from 'rasti';\n * // Custom cart\n * class ShoppingCart extends Emitter {\n *     constructor() {\n *         super();\n *         this.items = [];\n *     }\n *\n *     addItem(item) {\n *         this.items.push(item);\n *         // Emit a custom event called `itemAdded`.\n *         // Pass the added item as an argument to the event listener.\n *         this.emit('itemAdded', item);\n *     }\n * }\n * // Create an instance of ShoppingCart and Logger\n * const cart = new ShoppingCart();\n * // Listen to the `itemAdded` event and log the added item using the logger.\n * cart.on('itemAdded', (item) => {\n *     console.log(`Item added to cart: ${item.name} - Price: $${item.price}`);\n * });\n * // Simulate adding items to the cart\n * const item1 = { name : 'Smartphone', price : 1000 };\n * const item2 = { name : 'Headphones', price : 150 };\n *\n * cart.addItem(item1); // Output: \"Item added to cart: Smartphone - Price: $1000\"\n * cart.addItem(item2); // Output: \"Item added to cart: Headphones - Price: $150\"\n */\nexport default class Emitter {\n    /**\n     * Adds event listener.\n     * @param {string} type Type of the event (e.g. `change`).\n     * @param {Function} listener Callback function to be called when the event is emitted.\n     * @return {Function} A function to remove the listener.\n     * @example\n     * // Re render when model changes.\n     * this.model.on('change', this.render.bind(this));\n     */\n    on(type, listener) {\n        // Validate listener.\n        validateListener(listener);\n        // Create listeners object if it doesn't exist.\n        if (!this.listeners) this.listeners = {};\n        // Every type must have an array of listeners.\n        if (!this.listeners[type]) this.listeners[type] = [];\n        // Add listener to the array of listeners.\n        this.listeners[type].push(listener);\n        // Return a function to remove the listener.\n        return () => this.off(type, listener);\n    }\n\n    /**\n     * Adds event listener that executes once.\n     * @param {string} type Type of the event (e.g. `change`).\n     * @param {Function} listener Callback function to be called when the event is emitted.\n     * @return {Function} A function to remove the listener.\n     * @example\n     * // Log a message once when model changes.\n     * this.model.once('change', () => console.log('This will happen once'));\n     */\n    once(type, listener) {\n        // Validate listener.\n        validateListener(listener);\n        // Wrap listener to remove it after it is called.\n        const wrapper = (...args) => {\n            listener(...args);\n            this.off(type, wrapper);\n        };\n        // Add listener.\n        return this.on(type, wrapper);\n    }\n\n    /**\n     * Removes event listeners with flexible parameter combinations.\n     * @param {string} [type] Type of the event (e.g. `change`). If not provided, removes ALL listeners from this emitter.\n     * @param {Function} [listener] Specific callback function to remove. If not provided, removes all listeners for the specified type.\n     * \n     * **Behavior based on parameters:**\n     * - `off()` - Removes ALL listeners from this emitter\n     * - `off(type)` - Removes all listeners for the specified event type\n     * - `off(type, listener)` - Removes the specific listener for the specified event type\n     * \n     * @example\n     * // Remove all listeners from this emitter\n     * this.model.off();\n     * \n     * @example\n     * // Remove all 'change' event listeners\n     * this.model.off('change');\n     * \n     * @example\n     * // Remove specific listener for 'change' events\n     * const myListener = () => console.log('changed');\n     * this.model.on('change', myListener);\n     * this.model.off('change', myListener);\n     */\n    off(type, listener) {\n        // No listeners.\n        if (!this.listeners) return;\n        // No type provided, remove all listeners.\n        if (!type) {\n            delete this.listeners;\n            return;\n        }\n        // No listeners for specified type.\n        if (!this.listeners[type]) return;\n        // No listener provided, remove all listeners for specified type.\n        if (!listener) {\n            delete this.listeners[type];\n        } else {\n            // Remove specific listener.\n            this.listeners[type] = this.listeners[type].filter(fn => fn !== listener);\n            if (!this.listeners[type].length) delete this.listeners[type];\n        }\n        // Remove listeners object if it's empty.\n        if (!Object.keys(this.listeners).length) delete this.listeners;\n    }\n\n    /**\n     * Emits event of specified type. Listeners will receive specified arguments.\n     * @param {string} type Type of the event (e.g. `change`).\n     * @param {...any} [args] Optional arguments to be passed to listeners.\n     * @example\n     * // Emit validation error event with no arguments\n     * this.emit('invalid');\n     * \n     * @example\n     * // Emit change event with data\n     * this.emit('change', { field : 'name', value : 'John' });\n     */\n    emit(type, ...args) {\n        // No listeners.\n        if (!this.listeners || !this.listeners[type]) return;\n        // Call listeners. Use `slice` to make a copy and prevent errors when \n        // removing listeners inside a listener.\n        this.listeners[type].slice().forEach(fn => fn(...args));\n    }\n\n    /**\n     * Listen to an event of another emitter (Inverse of Control pattern).\n     * \n     * This method allows this object to manage its own listening relationships,\n     * making cleanup easier and preventing memory leaks. Instead of calling\n     * `otherEmitter.on()`, you call `this.listenTo(otherEmitter, ...)` which\n     * allows this object to track and clean up all its listeners at once.\n     * \n     * @param {Emitter} emitter The emitter to listen to.\n     * @param {string} type The type of the event to listen to.\n     * @param {Function} listener The listener to call when the event is emitted.\n     * @return {Function} A function to stop listening to the event.\n     * \n     * @example\n     * // Instead of: otherModel.on('change', this.render.bind(this));\n     * // Use: this.listenTo(otherModel, 'change', this.render.bind(this));\n     * // This way you can later call this.stopListening() to clean up all listeners\n     */\n    listenTo(emitter, type, listener) {\n        // Add listener to the emitter.\n        emitter.on(type, listener);\n        // Create listeningTo array if it doesn't exist.\n        if (!this.listeningTo) this.listeningTo = [];\n        // Add listener to the array of listeners.\n        this.listeningTo.push({ emitter, type, listener });\n        // Return a function to stop listening to the event.\n        return () => this.stopListening(emitter, type, listener);\n    }\n\n    /**\n     * Listen to an event of another emitter and remove the listener after it is called (Inverse of Control pattern).\n     * \n     * Similar to `listenTo()` but automatically removes the listener after the first execution,\n     * like `once()` but with the inverse of control benefits for cleanup management.\n     * \n     * @param {Emitter} emitter The emitter to listen to.\n     * @param {string} type The type of the event to listen to.\n     * @param {Function} listener The listener to call when the event is emitted.\n     * @return {Function} A function to stop listening to the event.\n     * \n     * @example\n     * // Listen once to another emitter's initialization event\n     * this.listenToOnce(otherModel, 'initialized', () => {\n     *     console.log('Other model initialized');\n     * });\n     */\n    listenToOnce(emitter, type, listener) {\n        validateListener(listener);\n        // Wrap listener to remove it after it is called.\n        const wrapper = (...args) => {\n            listener(...args);\n            this.stopListening(emitter, type, wrapper);\n        };\n        // Add listener.\n        return this.listenTo(emitter, type, wrapper);\n    }\n\n    /**\n     * Stop listening to events from other emitters (Inverse of Control pattern).\n     * \n     * This method provides flexible cleanup of listening relationships established with `listenTo()`.\n     * All parameters are optional, allowing different levels of cleanup granularity.\n     * \n     * @param {Emitter} [emitter] The emitter to stop listening to. If not provided, stops listening to ALL emitters.\n     * @param {string} [type] The type of event to stop listening to. If not provided, stops listening to all event types from the specified emitter.\n     * @param {Function} [listener] The specific listener to remove. If not provided, removes all listeners for the specified event type from the specified emitter.\n     * \n     * **Behavior based on parameters:**\n     * - `stopListening()` - Stops listening to ALL events from ALL emitters\n     * - `stopListening(emitter)` - Stops listening to all events from the specified emitter\n     * - `stopListening(emitter, type)` - Stops listening to the specified event type from the specified emitter\n     * - `stopListening(emitter, type, listener)` - Stops listening to the specific listener for the specific event from the specific emitter\n     * \n     * @example\n     * // Stop listening to all events from all emitters (complete cleanup)\n     * this.stopListening();\n     * \n     * @example\n     * // Stop listening to all events from a specific emitter\n     * this.stopListening(otherModel);\n     * \n     * @example\n     * // Stop listening to 'change' events from a specific emitter\n     * this.stopListening(otherModel, 'change');\n     * \n     * @example\n     * // Stop listening to a specific listener\n     * const myListener = () => console.log('changed');\n     * this.listenTo(otherModel, 'change', myListener);\n     * this.stopListening(otherModel, 'change', myListener);\n     */\n    stopListening(emitter, type, listener) {\n        // No listeningTo object.\n        if (!this.listeningTo) return;\n        // Remove listener from the array of listeners.\n        this.listeningTo = this.listeningTo.filter(item => {\n            if (\n                !emitter ||\n                (emitter === item.emitter && !type) ||\n                (emitter === item.emitter && type === item.type && !listener) ||\n                (emitter === item.emitter && type === item.type && listener === item.listener)\n            ) {\n                item.emitter.off(item.type, item.listener);\n                return false;\n            }\n            return true;\n        });\n        // Remove listeningTo object if it's empty.\n        if (!this.listeningTo.length) delete this.listeningTo;\n    }\n}\n"],"names":["validateListener"],"mappings":"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"}