/** Kitten Component. A class that makes it easier to author hierarchies of connected components. Handles wiring up and disposal of event listeners for you and gives you an ergonomic, class-based way of writing maintainable applications that take advantage of Kitten’s Streaming HTML workflow while working with well-encapsulated, self-contained components in a clear hierarchy. Extend and instantiate this class to create your own components (components, fragments, layout components, and pages) and provide at least an override for the `html()` method, which is just a function that returns `kitten.html`. @example // index.page.ts // // Updates a persisted counter via + and - buttons. // // Make sure you `npm install --save-dev @small-web/kitten-types` to load the type information for the global `kitten` object. // Initialise the database with a persisted counter object. kitten.db.counter ??= { count: 0 } export default class CounterPage extends kitten.Page { // Counter is strongly typed via inference (see constructor). counter // The constructor is where we initialise state, including setting up any child components we want available at initial render. constructor () { super() this.counter = this.addChild(new Counter()) } override html () { // While this.counter is a reference to the Counter KittenComponent, and not it’s render function, Kitten knows to use its `component` property to get a reference to its render function while rendering it. If you want to be explicit about it (e.g., to visually differentiate stateful components from stateless ones in your kitten.html), you can write `<${this.counter.component}` manually, but it’s not necessary. return kitten.html`

Counter

<${this.counter} /> ` } } class Counter extends kitten.Component { override html () { return kitten.html`
${kitten.db.counter.count}
` } onUpdate (data: { value: number } ) { kitten.db.counter.count += data.value this.update() } } */ import type KittenPage from './KittenPage.ts'; import type { KittenPageEvent } from './KittenPage.ts'; import type { KittenRequest, KittenResponse } from '../types/types.d.ts'; import EventEmitter from 'node:events'; /** Type definitions. First one required due to following shenanigans: https://github.com/Microsoft/TypeScript/issues/20007#issuecomment-2255964704 */ export type JavaScriptFunction = (...args: any[]) => any; type Constructor = new (...args: any[]) => T; type BoundComponentRenderFunction = ((...args: Parameters) => Promise>) & { boundObject: T; }; export type KittenHtml = string | string[] | Promise; /** A record of an event listener registered via addEventHandler(), tracked so it can be automatically removed on disconnect. */ type ListenerRecord = { target: EventEmitter; eventName: string; handler: JavaScriptFunction; }; /** Helper: creates event handler name from event name. */ export declare function eventNameToEventHandlerName(eventName: string): string; export default class KittenComponent extends EventEmitter { #private; id: string; _listeners: Array; _children: Array; _isAttached: boolean; _isConnected: boolean; /** Override the constructor to initialise child components and save initial component state. Remember to call super() before anything else. */ constructor(); /** Overridable (hook) methods. Required: - html() - render function; outputs kitten.html. May be synchronous or asynchronous. Optional: - onConnect() - called when the page this component is on (or is) connects. - onDisconnect() - called when the page this component is on (or is) disconnects. - onAddToParent() - called when component is added to parent (page might not be connected yet). */ /** Override this function with your own function that returns kitten.html. */ html({ ...props }?: {}): KittenHtml; /** Optional hook: override this method to run custom logic when the component has been added to the component hierarchy via addChild() (attached to its parent and, thus, to the component hiearchy). At this point it will have a reference to its parent component, the page it’s on, and to any instance data that it might have, (at `this._parent`, `this._page`, and `this.`, respectively.) However, there is no guarantee that the page this component is attached to has connected to the client via its automatic WebSocket. For that, rely on the `onConnect()` handler instead. TODO: Is there any need to keep this around? */ onAddToParent(): void; /** Optional hook: override this method to provide custom logic for your app to be run when the page this component is on connects to the client via its WebSocket. This hook will get called not just for initially-rendered components when the page first connects but also for any components dynamically added to an already-connected page/component hierarchy after the fact. (So you can be sure that this handler will be called once when a component is fully initialised on a connected page. This is a good place to add event handlers or to start streaming updates to the client.) */ onConnect(_pageDetails?: { page?: KittenPage; request?: KittenRequest; response?: KittenResponse; }): void; /** Optional hook: override this method to run custom logic when the page this component is on disconnects from the client via its WebSocket. (This usually means the page the about to be unloaded, either because the person is nagivating away from it or reloading it.) */ onDisconnect(_pageDetails?: { page?: KittenPage; request?: KittenRequest; response?: KittenResponse; }): void; /** Returns a bound version of the render function that can be used in Kitten HTML templates. It’s always async so mixed sync/async component hierarchies in Kitten HTML are uniformly awaitable (the renderer relies on the wrapper’s AsyncFunction identity and on boundObject). */ get component(): BoundComponentRenderFunction; /** Reference to the page this component is attached to. You should ideally not use this. Instead dispatch an event from your component that your page can react to. */ get _page(): KittenPage; set _page(page: KittenPage); /** Reference to the parent component this component is attached to. You should ideally not use this. Instead dispatch an event from your component that your parent can listen for and react to. */ get _parent(): KittenComponent; set _parent(parent: KittenComponent); /** Adds child component to this one and returns a reference to it. Canonical usage: this.childComponent = this.addChild(new ChildComponent(some, instance, props)) Child components are entered into the event bubbling hierarchy and contain a reference to the page that they’re on. Note: the page the component is added to might not have connected yet. */ addChild(component: T): T; /** Removes a child and returns a reference to it. If child cannot be found, returns null. Usually called by the child itself when it is being removed. */ removeChild(component: T): T | null; /** Gets all components of a given type (useful when you have collections of child components that you want to render, say, in a list). */ childrenOfType(type: Constructor): T[]; /** Returns the first child component encountered of type T. Use when you know there is only one child of type T and you don’t want to a reference to it in your KittenComponent subclass. Returns undefined if a child of type T cannot be found. */ childOfType(type: Constructor): T | undefined; /** Add an event handler. Event listening and listener clean-up are automatically handled so the author doesn’t have to worry about implementing this finickety aspect manually. */ addEventHandler(target: EventEmitter, eventName: string, eventHandler: (this: this, ...args: any[]) => any): void; /** Helper: emits page-wide event on the page this component is on. */ emitPageEvent(eventName: string, ...args: any): void; /** Helper: emits session-wide event on the session this page belongs to. */ emitSessionEvent(eventName: string, ...args: any): void; /** Helper: emits global event on kitten.events. */ emitGlobalEvent(eventName: string, ...args: any): void; /** Helper: adds automatically garbage-collected listener for a page event. */ addPageEventHandler(eventName: string, eventHandler: (this: this, ...args: any[]) => any): void; /** Helper: adds automatically garbage-collected listener for a session event. */ addSessionEventHandler(eventName: string, eventHandler: (this: this, ...args: any[]) => any): void; /** Helper: adds automatically garbage-collected listener for a session event. */ addGlobalEventHandler(eventName: string, eventHandler: (this: this, ...args: any[]) => any): void; /** A helper for adding an event handler and streaming an updated version of the computer to the client. */ updateOnEvent(eventName: string): void; /** Helper for sending an updated version of this component to the page. Also handles removal of event listeners for itself and all its children so we don’t have any leaks. */ update(): Promise; /** Helper for removing this component from the live component hierachy. Also handles removal of event listeners for itself and all its children so we don’t have any leaks. */ remove(): void; /** Helper for sending arbitrary Kitten HTML to the page. */ sendToPage(html: KittenHtml): void; /** Helper that shows a toast. */ toast(html: KittenHtml): void; /** Routes an event to the correct component by ID by recursively visiting descendants. */ _routeEvent(kittenPageEvent: KittenPageEvent): boolean; _callEventHandler(kittenPageEvent: KittenPageEvent): void; /** The internal onConnect handler that gets called by Kitten. We use this to connect event handlers, listen for events, and inform connected components to do the same. */ _onConnect(page: KittenPage): void; /** The internal onDisconnect handler that gets called by Kitten. We use this to remove event listeners and and inform connected components to do the same. */ _onDisconnect(page: KittenPage): void; /** Introspection API. These properties help authors debug their running apps using the Kitten Shell (REPL) */ /** Displays component hierarchy, methods, and properties of the current component. */ $info(): void; /** Returns the “name” of this page: i.e., it’s class name. */ get $name(): string; /** Returns all properties on this component. */ get $properties(): string[]; /** The names of this component’s “public” properties. We use “public” very loosely to mean any property not starting with an underscore (to keep the display clean of internal properties). */ get $publicProperties(): string[]; /** The names of this component’s “internal” properties. We use “internal” very loosely to mean any property starting with an underscore (to keep the display clean of internal properties). */ get $internalProperties(): string[]; /** The methods of this component. */ get $methods(): string[]; /** Returns the index and names of the child components of this component. */ get $children(): string[]; /** Returns a pretty representation of the rough size. */ get $size(): string; /** Helper: returns a byte value in pretty, human-friendly terms. */ ___prettyBytes(bytes: number, decimalPlaces?: number): string; /** Returns the rough size of this object in bytes. */ get ___size(): number; /** Returns a recursive tree view of this component and its descendents. */ $tree(): string; /** (Internal) Returns the short ID for this component. The short ID is the first fragment of the UUID. This has 32bits of entropy which should be adequate for this use case. (The 50% collision threshold via the birthday problem formula – https://en.wikipedia.org/wiki/Birthday_problem – is ~77,163 components and at 1,000 components there is a 0.01% chance of collisions. At 100 components, the risk is 0%. If you have 1,000+ components on your page, you have bigger issues to worry about.) */ get ___shortId(): string; /** (Internal) Transform [class X extends Y] to X (Y) */ ___prettyName({ isTitle, reverse }?: { isTitle?: boolean | undefined; reverse?: boolean | undefined; }): string; /** Returns a reference to the component (in this component’s entire component tree) with the passed short ID, or undefined if not found. @param shortId - The first fragment of this component’s UUID, as shown in the output of the __tree() method. */ $componentWithId(shortId: string): KittenComponent | undefined; /** Returns a flattened array of all the children of the component hierarchy starting at this component (recursive). */ ___allChildrenFlattened(): Array; /** (Internal) Recursive tree view. */ ___tree(prefix?: string): string; } export {};