{"id":24380495,"url":"https://github.com/solenya-group/solenya","last_synced_at":"2025-09-10T15:45:05.403Z","repository":{"id":57323873,"uuid":"113654144","full_name":"solenya-group/solenya","owner":"solenya-group","description":"mega-powerful micro-framework","archived":false,"fork":false,"pushed_at":"2020-05-04T23:59:29.000Z","size":743,"stargazers_count":81,"open_issues_count":4,"forks_count":0,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-06-13T00:51:26.229Z","etag":null,"topics":["front-end-framework","micro-framework","typescript","typestyle","virtual-dom"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/solenya-group.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-09T08:23:17.000Z","updated_at":"2024-01-03T14:15:23.000Z","dependencies_parsed_at":"2022-09-09T09:21:35.378Z","dependency_job_id":null,"html_url":"https://github.com/solenya-group/solenya","commit_stats":null,"previous_names":["pickle-ts/pickle"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/solenya-group/solenya","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solenya-group%2Fsolenya","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solenya-group%2Fsolenya/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solenya-group%2Fsolenya/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solenya-group%2Fsolenya/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solenya-group","download_url":"https://codeload.github.com/solenya-group/solenya/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solenya-group%2Fsolenya/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264502073,"owners_count":23618551,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["front-end-framework","micro-framework","typescript","typestyle","virtual-dom"],"created_at":"2025-01-19T08:19:32.120Z","updated_at":"2025-07-09T17:04:03.664Z","avatar_url":"https://github.com/solenya-group.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Solenya\n\nSolenya is the web framework for you if you like **conceptual simplicity**. The heart of a solenya application is **your object model**. The solenya API lets you easily translate your object model to the DOM, the cloud, and local storage.  \n\n![solenya flow diagram](solenya-architecture.png \"Solenya Flow Diagram\")\n\nHere's the [motivation](#motivation) for Solenya. But if you just want to learn it, you can skip that and just keep reading.\n\n# Samples\n\n  * Solenya website: https://www.solenya.org\n\n# Contact\n\nOpen a github issue, or email codingbenjamin@gmail.com.\n\n# First Code Sample\n\nHere's a counter component in solenya:\n\n```typescript\nexport class Counter extends Component\n{\n    count = 0\n\n    view () {\n        return div (\n            button ({ onclick: () =\u003e this.add (1) }, \"+\"),\n            this.count\n        )\n    }\n\n    add (x: number) {\n        this.update(() =\u003e this.count += x) \n    }\n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Fcounter.ts)\n\nIn solenya, application state lives in your components — in this case `count`.\n\nComponents can optionally implement a `view` method, which is a pure non side effecting function of the component's state. Views are rendered with a virtual DOM, such that the real DOM is efficiently patched with only the changes since the last update.\n\nComponents update their state exclusively via the their `update` method, which will automatically refresh the view. It really is that simple. In fact, simplicity is the defining characteristic of solenya.\n\n# Dependencies\n\nSolenya is small: see and understand the source code for yourself. Its power comes from its simplicity, composability, and integration with other great libraries.\n\nIn the following diagram, higher layers have a dependency on lower layers.\n\n![solenya flow diagram](solenya-layers.png \"Solenya Layers\")\n\n The 3rd party libraries Solenya has a dependency on are:\n\n * `typestyle` is used to express css styles in typescript\n * `class-validator` is used by the solenya validator\n * `class-tranformer` is used to serialize solenya components\n * `history` is used by the solenya router\n * *web-animations (polyfill: `web-animations-js`) is used by the solenya-animation package, though you can also use any other 3rd party animation library\n\nAll dependencies between npm packages are **peer dependencies**.\n\n`Solenya` works with all major browsers, including IE11 if you install the `es6-shim`.\n\n# Installation\n\n`npm install solenya`\n\nDepending on your npm setup, you may have to explicitly install the peer dependencies listed above.\n\n# Table of Contents\n\n- [Solenya](#solenya)\n- [Samples](#samples)\n- [Contact](#contact)\n- [First Code Sample](#first-code-sample)\n- [Dependencies](#dependencies)\n- [Installation](#installation)\n- [State, View and Updates](#state-view-and-updates)\n- [Composition](#composition)\n- [Component Initialization](#component-initialization)\n- [Updates](#updates)\n- [HTML Helpers](#html-helpers)\n- [Style](#style)\n  * [Important Gotcha](#important-gotcha)\n- [Forms](#forms)\n  * [Validation](#validation)\n- ['this' Rules](#this-rules)\n- [Routing](#routing)\n  - [Navigation](#navigation)\n  - [Initialisation and Browser History](#initialisation-and-browser-history)\n  - [Navigation Links](#navigation-links)\n- [Child-To-Parent Communication](#child-to-parent-communication)\n  * [Callback Communication](#callback-communication)\n    * [todoMVC](#todomvc)\n  * [Parent Interface Communication](#parent-interface-communication)\n  * [Update Communication](#update-communication)\n- [Interacting with the DOM](#interacting-with-the-dom)\n  * [onRefreshed](#onRefreshed)\n  * [Lifecycle Events](#lifecycle-events)\n  * [DOM Keys](#dom-keys)    \n  * [Animating a List](#animating-a-list)\n- [App](#app)\n- [Time Travel](#time-travel)\n- [Serialization](#serialization)\n  * [Property Serialization](#property-serialization)\n  * [Circular references](#circular-references)\n  * [Keep your component state small](#keep-your-component-state-small)\n- [Hot Module Reloading](#hot-module-reloading)\n- [Async](#async)\n- [Data Properties](#data-properties)\n  * [Data Keys](#data-keys)\n  * [Data Labels](#data-labels)\n- [Merging Attributes](#merging-attributes)\n- [Motivation](#motivation)\n  * [Simplicity](#Simplicity)\n  * [Everything expressed in a single, powerful language](#everything-expressed-in-a-single-powerful-language)\n  * [Intrinsic State Management](#intrinsic-state-management)\n- [API Reference](#api-reference)\n  * [Component Class API](#component-class-api)\n    * [Component View Members](#component-view-members)\n    * [Component Initialization Members](#component-initialization-members)\n    * [Component Update Members](#component-update-members)\n    * [Component Tree Members](#component-tree-members)\n  * [App Class API](#app-class-api)\n    * [App Initialization Members](#app-initialization-members)\n    * [App Serialization Members](#app-serialization-members)\n  \n# State, View and Updates\n\nA solenya app outputs a virtual DOM node as a pure function of its state. On each update, the previous root virtual DOM node is compared with the new one, and the actual DOM is efficiently patched with the change. DOM events can trigger updates, which result in a view refresh, forming a cyclic relationship between the state and the view.\n\nComponents help you factor your app into reusable parts, or parts with separate concerns. An app has a reference to your root component.\n\nComponents are designed to be serializable. When autosave or time travel is on, your component tree is serialized on each update. This provides a unified approach to time travel debugging, hot module reloading, transactions, and undo/redo. Serialization is covered in more detail in the serialization section.\n\n![solenya flow diagram](solenya-flow-diagram.png \"Solenya Flow Diagram\")\n\nThis flow diagram represents updating a GrandChild2 component. In this case, we have a very simple application, where the view tree mirrors the component tree. As an application gets larger, the view tree will typically only represent a subset of the component tree. For example, in an application with wizard steps, the component tree would probably have *every* wizard step, while the view tree would only have the *current* wizard step. \n\n# Composition\n\nComposition is straightforward with `solenya`, allowing you to factor your application into a tree of smaller components:\n\n```typescript\nexport class TwinCounters extends Component\n{\n    counter1 = new Counter ()\n    counter2 = new Counter ()\n\n    view () {\n        return div (\n            this.counter1.view (),\n            this.counter2.view ()\n        )\n    }\n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Fcomposition.ts)\n\nComponents must have parameterless constructors, though they may include *optional* arguments. This small design restriction enables `class-transformer`'s deserializer to work.\n\nChild components should be created in the constructors, field initialisers, and updates of their parents.\n\nYou can specific arrays of components, recursive components, or even arrays of recursive components. Here's from one of the samples. The type annotation is required to allow deserialization to work for arrays, as typescript erases the type of the property after compilation.\n\n```typescript\nexport class Tree extends Component\n{    \n    @Type (() =\u003e Tree) trees: Tree[] = []\n    ...\n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Ftree.ts)\n\n# Component Initialization\n\nYou component's life begins with a constructor call. As described later, the deserializer will still call your constructor, but then set the object's properties. That's why your constructor's arguments must be optional.\n\nWhen your app is first created or deserialized, and after every update, a depth-first traversal occurs, where `attached` is called on every component not already attached:\n\n```typescript\n   attached (deserialised: boolean) {\n       ...\n   }\n```\nOn the traversal, child components are identified by being properties or property array elements of the parent. As such each child will get attached, and have its `parent` and `app` properties set (except the root component which obviously has an undefined `parent`). This enables updates to a child to bubble up to the root component.\n\n# Updates\n\nAfter app startup or deserialization, all state transitions must occur within an update.\n\nAn update is straightforward:\n\n```typescript\n    add (x: number) {\n        this.update(() =\u003e this.count += x) \n    }\n```\nYou pass `update` a void function that performs a state transition.\n\nAt the end of an update, the root component's `view` method is called. Updates are synchronous, but views are refreshed asynchronously.\n\nNested updates are regarded as a single update. The view will at most be called once for an update.\n\nIn more advanced scenarios you can capture updates, as explained later in the child-to-parent-communication section.\n\n# Views\n\nViews are pure functions of state. Solenya uses a virtual DOM (forked from Ultradom) to efficiently update the actual DOM.\n\nYou can add as may optional parameters as you want to your child component `View` methods. This makes it easy for parents to customize their children without their children needing extra state:\n\n```typescript\n    view () {\n        return div (\n            this.child1.view (...),\n            this.child2.view (...)\n        )\n    }\n```\n`view` methods return the type `VElement`. However, your component might be faceless, having no view implementation, or might have several methods returning different `VElement` objects. This is because solenya components are state-centric, not view-centric.\n\nTo write a reusable view, your first approach should be to merely write a function that returns a `VElement`. Only use child components when you need to encapsulate state.\n\n# HTML Helpers\n\nThe HTML helpers take a spread of attribute objects, elements, and primitive values. Solenya has been designed to work well with Typescript, so your IDE can provide statement completion. In conjunction with `typestyle`, as we'll see later, we get a deep, clean static typing experience.\n\nAttribute objects go first. Some examples:\n\n```typescript\ndiv ()                                  // empty\ndiv (\"hello\")                           // primitive value\ndiv ({id: 1})                           // attribute\ndiv ({id: 1, class: \"foo\"})             // multiple attributes\ndiv ({id: 1}, \"hello\")                  // attribute followed by element\ndiv (div ())                            // nested elements\ndiv ({id: 1}, \"hello\", div(\"goodbye\"))  // combination\n```\nMultiple attribute objects are merged. Merging attributes is really useful when writing functions allowing the caller to merge their own attributes in with yours. The following are equivalent:\n\n```typescript\ndiv ({id: 1}, {class:\"foo\"})\ndiv ({id: 1, class:\"foo\"})  \n```\n\nAll the HTML element helper functions such as `div` and `span`, call through to the `h` function. The `h` function merges multiple attribute objects using the `mergeAttrs` method, which you can also directly call yourself.\n\nEvent handlers are specified as simply a name followed by the handler:\n```\nbutton (\n    { onclick: () =\u003e this.add (1) }, \"+\"\n)\n```\nTo aid porting, an online HTML-to-Code converter is available on both [solenya.org](http://www.solenya.org/convert) and [stackblitz](https://convert.stackblitz.io/).\n\n# Style\n\nWhile you can use ordinary css or scss files with solenya, solenya has first class support for [typestyle](https://github.com/typestyle/typestyle), that lets you write css in typescript.\n\nThe key advantages are:\n\n* Typescript is far more powerful than any stylesheet language - it's a better way to organize and abstract your styles.\n* It eliminates the seam between your view functions and styles - easily pass in variables to dynamically create styles.\n* You can colocate your code with your styles, or provide exactly the appropriate level of coupling to maximise maintainability.\n\nHere's what it looks like:\n\n```typescript\ndiv ({style: {color:'green' }}, 'solenya')\n```\nSolenya will call typestyle's `style` function on the object you provide. It's as if you called:\n\n```typescript\ndiv ({class: style ({color:'green'})}, 'solenya')\n```\nIf you need to reuse a style, then don't inline the style: declare it as a variable and refer to it in your class attribute. You can factor it just as you please.\n\nTypestyle will dynamically create a small unique class name, and add css to the top of your page. So the following:\n\n```typescript\ndiv ({style: {color: 'green'} },\n    \"solenya\"\n)\n```\nWhich will generate something like:\n\n```html\n\u003cdiv class=\"fdwf33\"\u003e\n    solenya\n\u003c/div\u003e\n```\nWith the following css:\n```css\nfdwf33 {\n    style: green;\n}\n```\nSolenya automatically merges css values. The following are equivalent:\n\n```typescript\ndiv ({class: \"big\"}, {class: \"happy\"})\ndiv ({class: \"big happy\"})\n```\n\nYou may also use ordinary style strings rather than objects, which bypasses the typestyle library.\n\n## Important Gotcha\n\nSince style objects are actually converted into classes, they may not override other styles in other classes that apply to that element. If this is a issue either add the `!important` modifier to the style, or revert to a string style. You should however discover that with typestyle you have less need to use the `!important` modifier in the first place as you can better abstract your styles.\n\n# Forms\n\nTo make writing forms easier, solenya provides some widget functions for common inputs. You can easily build your own ones by examining the widgets source code.\n\n* `inputText` : text input\n* `inputNumber` : numeric input\n* `inputValue` : restricted input - use to make custom inputs such as `percentInput` or `currencyInput`\n* `inputTextArea` : text area input\n* `inputRange` : numeric range input\n* `selector` : select input\n* `radioGroup` : group of labelled radio buttons\n* `checkbox` : labelled checkbox\n\nThe input functions are css agnostic - you can precisely control an input's attributes and nested element attributes. This enables you to write your own wrapper functions around these functions to target a particular css framework with your particular style. You then only need change the internals of your wrapper functions for all your forms code across your entire application to target completely different css.\n\nIn this example, we write a BMI component with two sliders:\n\n```typescript\nexport class BMI extends Component\n{\n    height = 180\n    weight = 80\n\n    calc () {\n        return this.weight / (this.height * this.height / 10000)\n    }\n\n    view () : VElement {       \n        return div (             \n            div (\n                \"height\",\n                inputRange ({target: this, prop: () =\u003e this.height, attrs: { min: 100, max: 250, step: 1 } }),\n                this.height\n            ),\n            div (\n                \"weight\",\n                inputRange ({target: this, prop: () =\u003e this.weight, attrs: { min: 100, max: 250, step: 1 } })\n                this.weight\n            ),\n            div (\"bmi: \" + this.calc())\n        )\n    }\n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Fbmi.ts)\n\nAll inputs are databound, and all take a single parameter. That parameter always inherits from the base class `InputProps\u003cT\u003e`:\n\n```typescript\nexport interface InputProps\u003cT\u003e { // T is the data type to bind to (e.g. a number)\n    target: Component, // component to bind to\n    prop: PropertyRef\u003cT\u003e, // target property, e.g. () =\u003e this.weight\n    attrs?: HAttributes // the attributes for the input\n}\n```\nThe `target` and `prop` properties reference a component's property, enabling the input to be databound to that property. The `PropertyRef\u003cT\u003e` type can either take a function that refers to a property, such as `() =\u003e this.weight` or a ordinary string, e.g. `weight`. The former (statically typed) should be used when you know the property at compile time, and the latter (dynamically typed) should be used when you only know the property at runtime.\n\nMinimially, all inputs will have an optional `attrs` type. More complex input types have many other properties, including nested attributes, which can be merged together as explained in the merging section.\n\nIn the above example, there's clearly boilerplate. In the next section, you'll notice you can easily write your own `inputUnit` higher-level function that generalizes the concept of an input with a label and validation.\n\n## Validation\n\nThe solenya library comes with a validator.\n\nThe solenya validator builds on the excellent `class-validator` library to validate with javascript decorators. Here's an example of validating some properties on a component:\n\n```typescript\nexport class ValidationSample extends MyForm implements IValidated\n{     \n    @transient validator: Validator = new Validator (this)\n    \n    @Label(\"Your User Name\") @MinLength(3) @MaxLength(10) @IsNotEmpty()  username?: string\n    @Min(0) @Max(10)                                                     rating? number\n    @IsNumber()                                                          bonus? number\n\n    ok() {\n        this.validator.validateThenUpdate()\n    }\n\n    updated (payload: any) {\n        if (this.validator.wasValidated)\n            this.validator.validateThenUpdate (payload)  \n    }\n\n    view () : VElement {           \n        return div (  \n            inputUnit (this, () =\u003e this.username, props =\u003e inputText (props)),\n            inputUnit (this, () =\u003e this.rating, props =\u003e inputNumber (props),\n            inputUnit (this, () =\u003e this.bonus, props =\u003e inputCurrency (props)),\n            div (\n                myButton ({ onclick: () =\u003e this.ok() }, \"ok\")\n            )\n        )       \n    }\n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Fvalidation.ts)\n\nBy decorating class properties, you can express constraints, as well as custom labels to be used for display and validation.\n\nWhen you're ready to validate (in this case because the user clicked 'ok'), you call the `validator`'s `validateThenUpdate` method. This compares the component's properties to the constraints on those properties. When complete, the `validationErrors` property on the `validator` will contain an element for each property with constraint violations.\n\nWe use the `component`'s update method to keep validated after we've first validated, to give the user continuous feedback. We can also manually flip `wasValidated` back to false.\n\nValidation works recursively for child components that also implement `IValidated`.\n\n### Asynchronous Validation\n\nValidation can be asynchronous, since you'll sometimes need to call a service to determine validity.\n\n```typescript\nclass ValidationSample extends Component implements IValidated\n{\n    async customValidationErrors() {\n        ...\n    }\n}\n```\nThe return type is `Promise\u003cValidationError[]\u003e`, where `ValidationError` is a a type from the `class-validator` package.\n\n# 'this' Rules\n\nThe `this` variable's binding is not as straightforward as in object oriented languages like C# and Java. There's a great article about [this here](https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript).\n\nWithin solenya components, follow the pattern you see in this documentation, which has two rules:\n\nAlways wrap a method that's used as a callback in a closure, otherwise `this` might be lost before it's bound.\n\n```typescript\n    // RIGHT\n    methodUsingYourCallback (e =\u003e this.updateProperty (e))\n    \n    // WRONG\n    methodUsingYourCallback (this.updateProperty)\n```\nUse ordinary class methods, not function members when calling update. Otherwise cloning — which solenya relies on for time travel — fails, since the cloned function will refer to the old object's this.\n\n```typescript\n    // RIGHT\n    add () {\n       return this.update (...\n\n    // WRONG\n    add = () =\u003e\n        this.update (...\n```\n\n# Routing\n\nThe solenya library comes with a composable router.\n\nThe samples use routing in two places. First, each sample has it's own route. Second, we use a router so that each tab in the \"tabSample\" has a nested route. So here's the possible routes:\n\n```\n/counter\n/bmi\n/tabSample/apple\n/tabSample/banana\n/tabSample/cantaloupe\n```\n\nLet's start with the outer router first. \n\n```\nexport class Samples extends Component implements IRouted\n{\n    @transient router:Router = new Router (this)\n    @transient routeName = \"\"\n\n    counter = new Counter ()\n    bmi = new BMI ()    \n    tabSample = new TabSample ()\n    ...\n    \n    attached()\n    {\n        for (var k of this.childrenKeys()) {\n            this[k].router = new Router(this[k])\n            this[k].routeName = k\n        }\n        ...\n    }\n}\n```\nA component can be routed by implementing `IRouted`. A routed component defines a `routeName` property that corresponds to a *name* in a *path*.\n\nSo for the path `/tabSample/banana`, there's a component with the `tabSample` `routeName`, which has a child component with the `banana` `routeName`. The root component, `Samples` has an empty string for its routeName.\n\nA *current route* is represented with a component route's `currentChildName` value. So the `currentChildName` of the `Samples` component's router is `tabSamples`, and the `curentChildName` of the `TabSample` component's router is `banana`. Finally, the `currentChildName` of the `banana` component's router is simply ``, since it's a leaf node, i.e. itself has no children.\n\nBy default, the mapping from parent and child name to child component occurs by scanning the parent for children and returning one that has a `routeName` that (case insensitively) matches the name provided. For complete control, you could implement the `childRoute` method to customize that default behaviour.\n\n## Navigation\n\nYou can call a component router's `navigate` method, specifying the child path to go to. All routes are expressed *relatively*, not *absolutely*. In the examples below, we navigate to `banana`:\n\n```\n// when navigating from 'banana':\nthis.router.navigate ('')\n\n// when navigating from 'tabSample':\nthis.router.navigate ('banana')\n\n// when navigating from 'apple', via the parent\nthis.router.parent.navigate ('banana')\n\n// when navigating from 'apple', via the root\nthis.router.root.navigate ('tabSample/banana')\n```\n\n## Initialisation and Browser History\n\nYour first navigation typically occurs in the `attached` method of your root component. For example:\n    \n```\nexport class Samples extends Component implements IRouted {\n    attached() {\n        ...\n        this.router.navigate (location.pathname)\n    }\n    ...\n}\n```\nThe initial call to `navigate` on your root router is special in terms of navigation events:\n\n* History tracking begins\n  * This means from then on, actions such as the back and forward button on the browser will trigger navigation events.\n  * (The router internally uses the `history` api to update the browser history and to respond to browser navigation events.)\n* Navigation callbacks always run\n  * The `beforeNavigate` and `navigated` callbacks on your component, if present, will be called even if the current url is identical to the url navigated to.\n  * This is because before the initial navigation, it shouldn't be assumed that the application state reflects the current url.\n\n## Intercepting Navigation\n\nWe can implement the `navigated` method to detect when a component is routed. We do this in the `Relativity` sample, where we have a continuous animation that we want to trigger when the component is routed:\n\n```typescript\nexport class Relativity extends Component {\n    navigated() {\n        ...\n    }\n}\n```\n\n[play](https://stackblitz.com/edit/solenya-samples?file=app%2Frelativity.ts)\n\n`navigated` will be called for each component in the path.\n\nWe intercept `navigate` by implementing `beforeNavigate`. It's important to be able to intercept navigation for several reasons:\n\n * Prevention: We don't want the user to leave the current form until it's validated, or perhaps we want to redirect the user to another route\n * Preparation: We need to fetch data, perhaps asynchronously, before we can complete the navigation.\n * Redirection: We want to redirect the user by canceling the current navigation and navigating to a new path.\n\nIn the `TabGroup` component, we use `beforeNavigate` for two purposes. First, we want to redirect to the first nested tab if no tab is selected. Second, we want to animate the tab left or right, depending on whether the new tab's index is less than or greater than its previous index.\n\n```typescript\nexport abstract class TabGroup extends Component implements IRouted\n{\n    @transient router: Router = new Router (this)\n    @transient routeName!: string   \n\n    attached() {\n        for (var k of this.childrenKeys()) {\n            this[k].router = new Router (this[k])\n            this[k].routeName = k\n        }\n    } \n\n    async beforeNavigate (childPath: string) {\n        const kids = this.childrenKeys()\n        if (childPath == '') {\n            this.router.navigate (this.router.currentChildComponent ? this.router.currentChildName : kids[0])\n            return false\n        }\n\n        this.slideForward = kids.indexOf (childPath) \u003e kids.indexOf (this.router.currentChildName)\n        return true\n    }\n```\n[play](https://stackblitz.com/edit/solenya-samples?file=app%2FtabSample.ts)\n\nWe return `false` when we want to cancel a navigation, and `true` when we're happy that the navigation goes ahead. The `beforeNavigate` method works very well in tandum with validation, that we discussed earlier. If your current state isn't valid, it's very common to prevent the navigation occuring by returning `false`.\n\nWe can now use `TabGroup` as follows:\n\n```typescript\nexport class TabSample extends TabGroup\n{  \n    apple = new MyTabContent (\"Apples are delicious\")\n    banana = new MyTabContent (\"But bananas are ok\")\n    cantaloupe = new MyTabContent (\"Cantaloupe that's what I'm talking about.\")\n}\n```\n\n## Navigation Links\n\nFor convenience, `router` has a function for generating navigation links. These look like ordinary url links, but they use the `onclick` event to ensure the router's navigation method is called, rather than jumping to a new page:\n\n```typescript\nnavigateLink (path: string, ...content: HValue[])\n```\n\n# Child-To-Parent Communication\n\nUse composition to manage complexity: as your application grows, parent components compose children into larger units of functionality. However, sometimes communication has to go in the reverse direction: from child to parent. This is done in one of three ways:\n\n* Callbacks\n* Parent Interface\n* Update Path\n\nWith all of these approaches, the single-source-of-truth is always maintained. Avoid copying state.\n\n## Callback Communication\n\nWith this approach, the parent passes a callback to the child:\n\n```typescript\nclass ParentComponent extends Component {\n    @Type (() =\u003e ChildComponent) child: ChildComponent\n    view () {\n        return (\n            ...\n            child.view (() =\u003e this.updateSomeValue())\n            ...\n        )\n    }    \n}\n\nclass ChildComponent extends Component {\n    ...\n    view (updateSomeValue?: () =\u003e void) : VElement { \n        ...\n    }\n}\n```\nIt's worth repeating that you should only use a child component if the child component has its *own* state. If not, save yourself some typing and replace your child component with a function that returns a `VElement`.\n\n### todoMVC\n\nIn this sample, we use the callback pattern, both when factoring out a child component (`taskItem`), and factoring out a function that returns a view (`linkListView`):\n\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2Ftodos.ts)\n\nNote that a small design restriction is that the arguments to `view` must be optional to support the parameterless super class `view`.\n\n## Parent Interface Communication\n\nWith this pattern:\n\n1. The parent component implements an interface for exposing only the state your child needs to see\n2. The child component has a method that returns this.parent cast to the parent interface\n\nSo the code structure is as follows:\n\n```typescript\ninterface IParent {\n    someMethod() : SomeType\t\n}\n\nclass ParentComponent extends Component implements IParent {\n    someMethod() { return ... }\n    ...\n}\n\nclass ChildComponent extends Component {\n    iparent() { return \u003cIParent\u003ethis.parent }\n    ...\n}\n```\nThe purpose of the interface is to reduce the surface area of the parent that the child can see, so that you can more easily reason about your code.\n\nYou may also use the `Component`'s `root()`, or `branch()` API (explained in the API section further below) to target a specific ancestor, rather than the immediate parent.\n\nIt's almost always a bad idea to access a sibling component. Instead, the child should access the parent, and the parent should interact with the other child.\n\nWe highly recommend you install the `circular-dependency-plugin` package, and run it as part of your build process. Keep your cyclomatic complexity low!\n\n## Update Communication\n\nAll state changes to `Component` trigger its `updated` method:\n\n```typescript\n   updated (payload: any) {\n      ...\n   }\n```\nThe `updated` method will be subsequently called on each parent through the root. This allows a parent to respond tp updates made by its children, without having to handle specific callbacks.\n\nThe `payload` property contains any data associated with the update. The `source` property will be set to component that `update` was called on, which is occasionally useful.\n\n# Interacting with the DOM\n\n## onRefreshed\n\nYou may call `Component.onRefreshed` to queue a callback to perform DOM side effects after the next refresh. You typically do so in the `view` method:\n\n```typescript\n   view() {\n       this.onRefreshed (() =\u003e { ... }) // called after DOM is updated\n       return div (...)\n   }\n```\nYou may however, need deeper control side-effecting the DOM.\n\n## Lifecycle Events\n\nFor the most part, views are pure functions of state. However, DOM elements can have additional state, such as inputs that have focus and selections. Furthermore, animations, at a low level, need to interact with the DOM bypassing the virtual DOM. This is for both performance reasons (as you don't want to invoke the GC), as well as keeping your application state logic separated from your animation state. For example, if you delete an item from a list, it's a simplifying assumption for your application state to consider that item gone, but you'll want that item to live a little longer in the real DOM to gracefully exit.\n\nTo interact with the DOM directly, you provide lifecycle callbacks on your virtual DOM elements. The lifecycle callbacks should be familiar to anyone familar with a virtual dom:\n\n```\nexport interface VLifecycle\n{\n    onAttached? (el: Element, attrs: VAttributes) : void\n    onBeforeUpdate? (el: Element, attrs: VAttributes) : void\n    onUpdated? (el: Element, attrs: VAttributes) : void    \n    onBeforeRemove? (el: Element) : Promise\u003cvoid\u003e\n    onRemoved? (el: Element) : void\n}\n```\n`onAttached` is called when an element is attached to the DOM.\n\n`onUpdate` is called whenever an element is updated. Use it to perform any final updates to the DOM. The `onBeforeUpdate` lets you take any preliminary actions before the element changes.\n\n`onRemoved` is called when an element is removed from the DOM.`onBeforeRemove` allows you take any preliminary actions - which may be asynchronous - before an element is removed. \n\nHere's how you might plug in some focusing logic when an element is added to the DOM:\n\n```typescript\n div ({\n     onAttached: (el, attrs) =\u003e handleFocus (el...)\n    ...\n```\nWhen the patcher adds an element to the DOM corresponding to your virtual div element, it invokes the `onAttached` callback.\n\nLifecycle callbacks automatically compose. So both `onAttached` functions will be called here, in the order of appearance:\n\n```typescript\n div (\n    {\n        onAttached: (el, attrs) =\u003e handleFocus (el...)\n    },\n    {\n        onAttached: (el, attrs) =\u003e handleSelection (el...)\n    }\n    ...\n }\n```\n## DOM Keys\n\nAfter each update, the virtual DOM is patched. The patcher compares the current virtual DOM tree to the previous one, and modifies the real DOM accordingly. However, the patching algorithm can't know your intent, and so occassionally does the wrong thing. It may try to reuse an element that you definitely want to replace, or it may try to replace a list of child elements that you merely wanted to reorder. To better determine the creation and destruction of DOM nodes, provide *keys* for your virtual DOM nodes. For example:\n\n```typescript\ndiv ({key: wizardPage})\n```\nIf the key changes, the patcher now knows to definitely recreate that DOM element. This means even if your next wizard page happened to have an input that could have been updated, that instead it will be replaced, predictably resetting DOM state like focus and selections, and invoking any animations that should occur on element creation.\n\n## Animating a List\n\nLet's combine the concepts in the previous sections to shuffle an array, where each element gracefully moves to its new position each time the array is updated. We can use lodash's shuffle function to perform the `shuffle`, and our own `transitionChildren` function to perform the animation. We'll need to make sure each item in the array has a unique `key`, so that the patcher knows to reuse each child element.\n\n```typescript\nexport class AnimateListExample extends Component\n{\n    @transient items = range (1, 20)\n\n    view () {        \n        return div(\n            myButton ({ onclick: () =\u003e this.shuffle () }, \"shuffle\"),       \n            ul (transitionChildren(), this.items.map (n =\u003e li ({ key: n }, n)))\n        )\n    }\n\n    shuffle() {\n        this.update (() =\u003e this.items = shuffle (this.items))\n    }\n}\n```\nWe can implement `transitionChildren` using the [FLIP](https://aerotwist.com/blog/flip-your-animations/) technique:\n```typescript\nexport function transitionChildren () : VLifecycle\n{\n    return {                       \n        onBeforeUpdate (el) {                \n            let els = el[\"state_transitionChildren\"] = Array.from(el.childNodes).map(c =\u003e (c as HTMLElement))\n            els.forEach (c =\u003e measure(c))\n        },\n        onUpdated (el) {\n            let els = el[\"state_transitionChildren\"] as HTMLElement[]\n            els.forEach (c =\u003e flip (c))\n        }                    \n    } \n}\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2FanimateList.ts)\n\nBy design, these lifecycle events are not present on solenya components. Solenya components manage application state, only affecting DOM state via its `view` method, and the intentionally ungranular `onRefreshed` method. This lets you separate the very different lifecycles of application state and DOM state, making your code easier to maintain.\n\nWhile there's always pragmatic exceptions, the principles of solenya state are:\n\n * Application state belongs on components.\n * DOM state belongs on DOM elements.\n * No state belongs on virtual DOM elements.\n\n# App\n\nTo start Solenya, pass the constructor of your top level component into your App instance, with a string defining the id of the element where you app will be hosted. For example:\n\n```typescript\nimport { App } from 'solenya'\n\nvar app = new App (Counter, \"app\")\n```\nYou can also construct the application from an explicit instance. This can be useful when you've deserialized the component from somewhere else, such as a server. \n\n```typescript\nvar app = new App (Counter, \"app\", {rootComponent: counterFromTheWeb })\n```\nMake sure you've read the serialization section to ensure your component deserializes correctly.\n\n# Time Travel\n\nMaintaining state history is useful when you want transactions, undo/redo, and time travel debugging.\n\nYou can turn time travel on and off on App as follows:\n\n```typescript\napp.timeTravelOn = true|false\n```\nNow all updates will be recorded.\n\nYou can now navigate as follows:\n\n```typescript\ntime.start()  // goto start state\ntime.end()    // goto end state\ntime.next()   // goto next state\ntime.prev()   // goto prev state\ntime.goto(4)  // goto nth state\n```\nYou can also use a predicate to seek a particular state:\n```typescript\ntime.seek(state =\u003e state.counter.count == 7)\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2FtimeTravel.ts)\n\nWhen time travel is on, solenya serializes the component tree on each update. It's efficient and mostly transparent, but make sure to read the serialization section.\n\n# Serialization\n\nSometimes you want to serialize your application to local storage. \n\nTo save our application with each update, we set the app `autosave` property on the app's `storage` object to `true`:\n\n```typescript\napp.storage.autosave = true\n```\nThis will save your serialized component tree in local storage with the container id you specified (e.g `\"app\"`).\n\nTo turn `autosave` off and clear your application state from local storage:\n```typescript\napp.storage.autosave = false\napp.storage.clear()\n```\n\n## Property Serialization\n\nIt's critical to be aware that **Typescript transpiles away property types** (unlike in C# or Java). The `@Type` decorator of the `class-transformer` serialization package can be used to ensure nested components deserialize with the correct types:\n\n```typescript\nexport class Composition extends Component\n{\n    @Type (() =\u003e Counter) counter1: Counter\n    @Type (() =\u003e Counter) counter2: Counter\n}\n```\nAs a shortcut, you can also call `initDecorators` in your constructor. This will automatically add `@Type` decorators by scanning for non-undefined instance properties (and do so recursively):\n\n```typescript\nexport class Composition extends Component\n{\n    counter1 = new Counter ()\n    counter2 = new Counter ()\n\n    constructor() {\n        super()\n        this.initDecorators()\n    }\n}\n```\nYou must always apply the `@Type` annotation to arrays of components (do **not** include the `[]`):\n\n```typescript\nexport class ItemList extends Component\n{\n   @Type (() =\u003e Item) myList: Item[] = []   \n}\n```\n## Circular references\n\nAvoid circular references unless you absolutely need them. Firstly, the serializer doesn't handle them, and secondly, it increases your cyclomatic complexity which is why some languages like F# deliberately force you to minimize them. However, occassionaly you'll need them. To do so:\n\n* override component's `attached` method to set the circular references there\n* exclude the circular references from being serialized with the `@transient` decorator\n\n## Keep your component state small\n\nAs a general rule, don't gratuitously use component state, and instead try to use pure functions where you can. In particular, avoid storing UI styles in component state - instead pass styles from a parent view down to child views. If you must store a UI style in a component, you'll probably want to decorate it with `@transient` to avoid serialization.\n\nSerialization, deserialization, and local storage are surpisingly fast. However, efficiency is still important. Avoid properties with large immutable objects, and instead indirectly reference them with a key. For example, instead of directly storing a localisation table of French data on your component, you'd merely store the string \"fr\", and return the localisation table based on that key. Minimize the state on your components to that which you need to respond to user actions; keep it as close to a state machine as possible.\n\n# Hot Module Reloading\n\nWhen your application state is serialized, an ordinary page refresh will run your modified code with your previous state. We can automatically trigger a page refresh by listening to server code changes:\n\n```\nmodule.hot.accept('../app/samples', () =\u003e {\n    var latest = require ('../app/samples')\n    window[\"app\"] = new App (latest.Samples.prototype.constructor, \"app\", {isVdomRendered: true})\n})\n```\nThe `isVdomRendered` parameter tells the new app instance that there's already a rendered tree, so do a patch rather than create the DOM from scratch.\n\n# Async\n\nSolenya's update path is synchronous, so you perform aynchronous activites outside of update. Suppose a button invokes your submit event handler that calls a web service. That could be defined as follows: \n\n```typescript\n    async submit () {\n        var result = await fetch(url)\n        if (result.ok) {\n            this.update (() =\u003e ...)\n        }\n    }\n```\n[Play](https://stackblitz.com/edit/solenya-samples?file=app%2FgitSearch.ts)\n\nNotice that the `update` occurs *after* the asynchronous operation has completed.\n\nBoth the `Validator` and `Router`, covered in their own sections, are designed to operate asynchronously.\n\nThe samples demonstrate calling github's search, with debouncing.\n\n# Data Properties\n\n## Data Keys\n\nIt's common for your components to have a mixture of properties representing your domain model, such as `firstName` or `phone`, and properties representing infrastructure, such as `validator` or `router`. You can programmatically get just the domain properties by calling a component's `dataKeys` property, which ignores all properties decorated with `@transient`:\n\n```typescript\nclass Animal extends Component\n{\n    @transient router?: Router = undefined\n    @transient validator?: Validator = undefined\n    species = \"\"\n    caloriesPerDay = NaN\n\n    view() {    \n        return div (this.dataKeys().join (\",\")) // species,caloriesPerDay\n    }\n}\n```\nYou can think of `dataKeys` like `Object.getKeys`, but for data.\n\nThe `Component` class's properties such as `app` and `parent` are decorated as `@transient`.\n\n`@transient` will automatically also decorate your property with `class-transformers`'s `@Exclude()` decorator, so that such properties don't partake in serialization.\n\n## Data Labels\n\nIt's common to want display the name of a data property to the user. The `@Label` decorator can be used exactly for this purpose:\n\n```typescript\nclass Animal extends Component\n{\n    @Label(\"Species\") name = \"Elephant\"\n    caloriesPerDay = 70000\n\n    // prints:\n    // Species: Elephant\n    // Calories Per Day: 70000\n    view() {    \n        return div (\n            this.dataKeys().map (k =\u003e\n                div (printProp (this, k))\n            )            \n    }\n}\n\nconst printProp\u003cT\u003e (obj: any, prop: PropertyRef\u003cT\u003e) =\u003e\n    getFriendlyName (obj, prop) + \": \" + getPropertyValue (obj, prop))\n```\n`getFriendlyName` checks to see if there's a `@Label`, and if not, falls back to calling `humanizeIdentifier` on the property name. This means that by default, your users will see `Calories Per Day` instead of `caloriesPerDay` or `calories-per-day`.\n\nThe validation sample referred to earlier shows how to hook up validation to use `@Label`.\n\n# Merging Attributes\n\nSolenya provides merging functions to make it easier to write reusable and chainable helper functions.\n\nBy default, calling an element function prefixed with multiple attribute objects merges those attributes. For example:\n\n ```typescript\n div ({x:1}, {y:2})  // this\n div ({x:1, y:2})    // becomes this\n ```\nYou can also use the `mergeAttrs` and `mergeNestedAttrs` helper functions to explicitly merge attributes. `mergeNestedAttrs` takes several objects, scans for properties on those objects whose name ends with `attrs`, and then calls through to `mergeAttrs` to merge those attributes and styles.\n\nLet's suppose we create a resuable `myInputText` that customises `inputText` as follows:\n\n```typescript\nexport const myInputText = (props: InputEditorProps\u003cstring|undefined\u003e) =\u003e\n    inputText (mergeNestedAttrs (props, { attrs: {            \n        type: \"text\",\n        class: \"form-control\"\n    }}))\n```\nCalling:\n\n```typescript\nmyInputText ({target: this, prop: () =\u003e this.email, attrs: {\n    type: \"email\",\n    class: \"special\"\n}})\n````\nOutputs:\n```html\n\u003cinput\n  type=\"email\"\n  class=\"special form-control\"\n   ...\n/\u003e\n```\nOccasionally you'll get a merge collision. Solenya favors the attribute that comes first. This is the opposite behaviour of the spread operator, and for good reason: as a callee writing a reusable function you want the caller's attribute object to come first, take precedence, and drive type inference.\n\n# Motivation\n\n## Simplicity\n\n*Simplicity* is a deeply important quality of code, and as such, has become a superlative in programming. Superlatives work on us because we naturally want to believe great things about ourselves: *my* code is simple. But of course, *your* code is complicated, and now simplicity is not objective, but subjective. This subjectivity is exacerbated by the restricted environment of the coding islands we live on. Of course code can be simple when it ignores critical concerns of a larger world.\n\nBut to abandon an objective meaning of 'simple' is to abandon being a good programmer, or to think so provincially that such meanings are never contemplated. Because the side-effects of complexity are costly code bases defended by programmers who's actual talent is politics.\n\nSomething is 'simple' if it's smaller. A system that has 1 thing in it is simpler than a system that has 2 things in it. Simplicity must be coupled with Occam's razor - that out of several *comparably powerful* alternatives, we should pick the simplest one.\n\nSolenya is simple because it:\n\n * Reduces the number or languages to get the job done\n * Removes the need for an extra bolt-on state manager\n\nwithout sacrificing any power.\n\n## Everything expressed in a single, powerful language\n\nSolenya, in part thanks to the excellent `typestyle` library, uses typescript to express everything, including HTML and CSS. This substantially reduces complexity.\n\n![Language Interop Complexity](language-interop-complexity.png \"Language Interop Complexity\")\n\nIn comparison, most front end frameworks, despite putatively being \"unified\" development models, encourage development in 3 different languages: Javascript for application code, HTML for UI, and CSS for styling. This slows development down immeasurably, due to:\n\n* Limited abstractions in the languages\n  * HTML and CSS are in reality used as impoverished unwieldy programming languages\n* Limited interop between languages\n  * You can't for example parameterise some CSS style with an instance of a typescript class\n* Using multiple of anything, when a single thing will do\n  * Especially when that thing is an entire language\n\nRather than addressing the root of the problem, populist solutions appeal to developer tradition, doubling down on the flawed approach with more ad-hoc abstractions. \"New\" slightly-less-impoverished languages are built on top of the impoverished languages. Yet-another-template language reinventing looping constructs, or a layer of top of CSS where we're thrilled we get a feature like... variables.\n\nAnother excuse for using 3 languages rather than 1 is the misnomer that this helps separate concerns. Separation of languages != separation of concerns. In fact, the worst thing you can do if you care about separating concerns is to attempt to do so with a language lacking abstractions that make such separations possible. This is why css code bases (and scss) are littered with repetition, with tooling-invisible two-way dependencies with their accompanying code and HTML.\n\n### Static Typing\n\nSolenya was built from the ground-up to take advantage of static typing. \n\nStatic typing gives you: the best possible testing for free, where the compiler constrains the possible values of your variables. Great tooling. Free documentation. It makes other people's code much easier to understand. And it makes your own code easier to understand as and when you refactor it.\n\nIt's easier to scale a large code base with static typing. It's why Facebook developed their own statically typed version of PHP. It's why Typescript exists. And the tax of static typing in modern languages is really low, thanks to type inference. There's increasingly few excuses for eschewing compile-time types!\n\n### Embracing both OO and Functional Approaches\n\nTypescript, as well as other languages like F#, do a brilliant job of leveraging both the OO and functional paradigms. Solenya, being written in and for Typescript, takes full advantage of this.\n\nUnfortunately, some of the alternatives to the big frameworks are anti-OO frameworks. Their tenets? That state mutation is always wrong. That coupling functions and data is always wrong. Yet their own code demonstrates rampant DRY violations and convoluted higher-order write-only functions. You know, the functions that return functions that return functions that return functions, where oh, yes, well the 2nd innermost function actually did store state. Good luck debugging that.\n\n## Intrinsic State Management\n\nTo manage complexity in a web application, we split it into modular chunks. It's helpful to think of common types of chunks, or \"components\", in terms of their statefulness:\n\n 1. Objects with application state (i.e. a high level chunk of an application).\n 2. Pure functions that return a view (i.e. a stateless virtual dom node)\n 3. Objects with DOM state (i.e. essentially a web component)\n\nMost web frameworks today focus on building components of type '2' and '3', not type '1' components - but this causes serious problems.\n\nThe immediate strategy, which is really a lack of strategy, or upside-down strategy, is to use a type '3' component, an abstraction around an HTML DOM element, and then polute it with application logic. In code samples, and in small applications, we can get away with this dirty ad-hoc approach, but the approach doesn't scale.\n\nFor this reason, many people eventually adopt the strategy of using a separate state management library to get their type '1' components. But now you've got two separate component models to deal with and integrate.\n\nSolenya is designed from the start for building high-level type '1' components. This means you can cleanly organise your application into meaningful, reusable, high-level chunks. So for example, you could have a component for a login, a component for a paged table, or a component for an address. And you can compose components to any scale: in fact your root component will represent your entire application.\n\nEach component's view is a pure function of its state, so within each component we get a high degree of separation of the application logic/state and the view. In fact, for any solenya application, you can strip out its views, and the core structure of the application remains in tact. Writing a component means thinking about its state and state transitions first, and its views second. This approach makes solenya components innately serializable. So another way to think about solenya components is that they represent the serializable parts of your application, that you might want to load and save from and to local storage or the server.\n\n# API Reference\n\nThe `Component` and `App` APIs have been covered in earlier sections, and can also be understood through intellisense and the source code, but are included here to give a reference-oriented overview.\n\nThe HTML, lifecycle, serialization, and time travel APIs have already been covered in their dedicated sections.\n\n## Component Class API\n\n### Component View Members\n\n```typescript\n/** Override to return a virtual DOM element that visually represents the component's state */\nview(): VElement\n\n/** Call to run an action after the view is rendered. Think of it like setTimeout but executed at exactly at the right time in the update cycle. */\nonRefreshed (action: () =\u003e void) \n```\n\n### Component Initialization Members\n\n```typescript\n/** Called after construction, with a flag indicating if deserialization occured */\nattached (deserialized: boolean) \n\n/** Attaches a component to the component tree.\n * Called automatically on refresh but can be explicitly called to eagerly attach.\n */\nattach (app: App, parent?: Component)\n```\n### Component Update Members\n\n```typescript\n/** Call with action that updates the component's state, with optional payload */\nupdate (updater: () =\u003e void, payload: any = {}) \n\n/** Override to listen to an update after its occured\n* @param payload Contains data associated with update - the update method will set the source property to 'this'\n*/\nupdated (payload: any) : void\n```\n\n### Component Tree Members\n\n```typescript\n/** The app associated with the component; undefined if not yet attached - use judiciously - main purpose is internal use by update method */\napp?: App\n\n/** The parent component; undefined if the root component - use judiciously - main purpose is internal use by update method */\nparent?: Component  \n\n/** Returns the root component by recursively walking through each parent */\nroot () : Component \n\n/** Returns the branch, inclusively from this component to the root component */\nbranch () : Component[]\n\n/** Returns the properties that are components, flattening out array properties of components */\nchildren () : Component[]      \n```\n## App Class API\n\n### App Initialization Members\n```typescript\n/**    \n * The entry point for a solenya app\n * @param rootComponentConstructor The parameterless constructor of the root component\n * @param containerId The element id to render the view, and local storage name\n * @param rootComponent Optionally, an existing instance of the root component\n * @param isVdomRendered Optionally, indicate that the vdom is already rendered\n */\nconstructor (\n    rootComponentConstructor : new() =\u003e Component,\n    containerId: string,\n    rootComponent?: Component,\n    isVdomRendered = false\n)\n\n/** Root component of updates, view and serialization */\nrootComponent: Component \n```\n\n### App Serialization Members\n```typescript\n/** Manages serialization of root component to local storage */\nstorage: Storage \n\n/** manages time travel - type is 'any' because component snapshots are converted to plain json objects */\ntime: TimeTravel\u003cany\u003e \n\n/** whether snapshots occur by default after each update */\ntimeTravelOn: boolean \n\n/**\n * Serialize the root component to local storage and/or time travel history\n * @param doSave true = force save, false = do not save, undefined = use value of App.storage.autosave\n * @param doTimeSnapshot true = force snapshot,false = do not snapshot, undefined = use value of App.timeTravelOn\n */\nsnapshot(doSave?: boolean, doTimeSnapshot?: boolean): void\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolenya-group%2Fsolenya","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolenya-group%2Fsolenya","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolenya-group%2Fsolenya/lists"}