lightview.dev v1.8.1b (BETA)

Introduction to Lightview

Small, simple, powerful web UI and micro front end creation ...

Great ideas from Svelte, React, Vue and Riot plus more combined into one small tool: 7.5K (minified/gzipped)

A ToDo demo does not get much simpler than this: lightview-todo

What You Get

  1. Single file and template components.

  2. Sandboxed remote components and micro front ends.

  3. Unit testable components and a debug mode for using standard JavaScript debuggers.

  4. No pre-deployment transpilation/compilation required.

  5. Svelte like variable usage, i.e. write your state modifying code like normal code.

  6. Extended variable type declarations including min, max and step on number or limits on string and array lengths.

  7. TypeScript like runtime type checking of variables in components.

  8. Automatic server retrieval and update of variables declared as remote.

  9. Automatic import, export, cross-component sync, or reactive response to attributes/props/variables. See superVariable.

  10. Automatic input field variable creation and binding.

  11. Attribute directives like l-if, and a single powerful l-for that handles array and object keys, values, and entries.

  12. Reactive string template literals for content and attribute value replacement.

  13. No virtual DOM. The Lightview dependency tracker laser targets just those nodes that need updates.

  14. SPA, and MPA friendly ... somewhat SEO friendly and short steps away from fully SEO friendly.

  15. A component library including charts and gauges that work in Markdown files.

  16. Lots of live editable examples.

    If you like what you get on the front-end with Lightview, check out our back-end reactive framework Watchlight at https://watchlight.dev.

Usage

Install it from NPMJS. Or, visit the repository on GitHub. Note, we will actively iterate on CodePen and the NPM or GitHub versions may not be the most recent. If you want the bleeding edge download it from CodePen and include it in your build pipeline.

It should be evident from the todo example above, Lightview components are just HTML files similar to Riot or Vue's SFCs. And, for the most part, the code you write looks like regular JavaScript ... we modeled this on Svelte.

The script blocks in these files are set to type lightview/module and inside them the variable self is bound to the component.

The HTML in the files is rendered in a shadow DOM node and the style blocks and scripts are isolated to that shadow DOM.

You use the components in other files by inserting the Lightview script in the head of the file where you want ot use the component and load the components using link tags, e.g.

<head>
<link href="../components/gantt/gantt.html" rel="module">
<script src="./lightview.js"></script>
</head>
<body>
<l-gantt id="myChart" style="height:500px;" title="Research Project" hidden l-unhide>
    {
    options: { },
    rows: [
    ['Research', 'Find sources',"2015-01-01", "2015-01-05", null,  100,  null],
    ['Write', 'Write paper',null,"2015-01-09", "3d", 25, 'Research,Outline'],
    ['Cite', 'Create bibliography',null, "2015-01-07","1d" , 20, 'Research'],
    ['Complete', 'Hand in paper', null, "2015-01-10", "1d" , 0, 'Cite,Write'],
    ['Outline', 'Outline paper', null, "2015-01-06", "1d" , 100, 'Research']
    ]
    }
</l-gantt>
</body>

You can do the same in a Markdown file, just leave out the head and body tags. You can see the source of a Markdown file, or see it rendered.

API

Variables

Much of the power of Lightview comes from its expressive variable declarations.

You can use the normal declarations var, let, and const for data that only needs to be present within a script block. For other data, you need to declare variables using self.variables(options).

Object self.variables(
    {[name:string]:dataType:string, [ ...]},
    [{[functionalType]:boolean|string|object, ...}]
);

The available dataTypes are "any", "array", "boolean", "number", "object", "string". You can also provide any object constructor like, Array.

FunctionalTypes apply behaviors to variables. The available functionalTypes are shared, reactive, imported, exported, observed, remote, set, constant. These are reserved words and, with the exception of set and constant, are themselves functions that configure variable behavior.

If a varibable is required, set or constant DO NOT need to be present. The required is enforced when attempts are made to set the variable.

set can take any value that is consistent with the dataType of the variable(s) it is used with.

constant can take any value that is consistent with the dataType of the variable(s) it is used with.

set and constant can't be used together.

See More On Remote Variables for details on the string and object value configuration options for remote.

As a result of the use of the variable definition approach, Lightview variable declarations look very much like TypeScript.

You would probably never make a variable this powerful, but the below illustrates the use of most of the types of variables.

self.variables({superVariable:"number"}, { set:1, imported, exported, shared, reactive, observed, remote:remote("./superVariable")});

observe(() => {
  console.log("superVariable changed to:",superVariable);
});
</script>

Note: lightview/module code is sensitive to the use/lack of of semi-colons. When in doubt, terminate your statements with semi-colons. Also, since since Lightview compiles your code into an asynchronous function, top level await is supported.

Runtime type checking will be applied to ensure superVariable is always a number or can be coerced to a number. There is an example for type checking.

It will initially be set to 1 and then an attempt to import a value from the attributes of the component will be made. If no attribute by the name of the variable exists, the value will remain 1. Any time it changes, its value will be exported back up to the attributes. The value will also be shared with all instances of the same component and reactive HTML will be re-rendered. Also see the example Sharing State.

Reactive HTML is any HTML, including attribute values (with the exception of the name attribute on input elements), that contains a reference or references to a variable inside a template literal.

Because superVariable is observed you can also call addEventListener(callback) to register a listener that will be called any time the attribute superVariable changes. Attributes that are observed are automatically imported. If your needs are simple, you can just rely on reactive rather than define a callback using addEventListener.

Because you declared superVariable as reactive and wrapped a function referencing it in observe, the function will be called every time superVariable changes.

And, because you declared superVariable as remote at the relative URL ./superVariable, its value will be retrieved from the server. Also, since you made it reactive, changes will be sent to the server as PATCH requests.

Extended Type Definitions

There are extended type definitions that use the symbolic names of all the basic types, e.g. string vs "string". These extended types can be used like their string named counterparts except they do not automatically coerce, or they can be used as functions for more sophisticated type checking, e.g. the basic types always attempt coercion, with extended types this is turned off by default, although you can turn in on. The declaration of superVariable above, might look like this:

const {number} = await import("./types.js");
self.variables(
    {superVariable:number}, // note, number is not quoted
    {imported:true, exported:true, shared:true, reactive:true, observed:true, remote:"./superVariable"}
  );
await superVariable;

Or, if you want to coerce and constrain the value to a range, like this:

const {number} = await import("./types.js");
self.variables(
    {superVariable:number({coerce:true,min:0,max:1000})}, // note, how number is used as a function
    {imported:true, exported:true, shared:true, reactive:true, observed:true, remote:remote("./superVariable")}
  );
await superVariable;

An example showing errors should help:

const {string,number} = await import("./types.js");
self.variables({name:string({minlength:2,maxlength:20,required:true,default:"anonymous"}),age:number});
console.log(name); // will log "anonymous"
try {
  name = "J"; // will throw an error since the value is too short
} catch(e) {

}
try {
  name = null; // will throw an error since a value is required
} catch(e) {

}
try {
  age = "10"; // will throw an error since the value is not a number
} catch(e) {

}

name = "joe"; // will succeed
age = 10; // will succeed

The extended types include:

any({required?:boolean,whenInvalid?:function,default?:any})

array({coerce?:boolean,required?:boolean,whenInvalid?:function,minlength?:number,maxlength?:number,default?:Array})

boolean({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:boolean})

number({coerce?:boolean,required?:boolean,whenInvalid?:function,min?:number,max?:number,step?:number,allowNaN?:boolean,default?:number})

object({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:object})

remote(string|object)

Remote must be imported from types.js and SHOULD be called with a configuration. See More On Remote Variables.

string({coerce?:boolean,required?:boolean,whenInvalid?:function,minlength?:number,maxlength?:number,pattern?:RegExp,default?:string})

symbol({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:symbol})

All extended types have a default whenInvalid parameter which throws an error when an attempt to set the varibale to an invalid value is made. A custom function can be passed in that swallows the error and returns the existing value for the variable, or undefined, or some other value, for example:

const whenInvalid = (variable) => {
    return variable.value;
}

you could even go ahead and make the assignment but log a warning:

const whenInvalid = (variable,invalidValue) => {
    console.warn(`Assigning ${variable.name}:${variable.type.name||variable.type} invalid value ${invalidValue});
    return newValue;
}
More On Remote Variables

Below is an example of how simple it can be to set up remote sensor monitoring using remote variables. This example is talking to a simulator running on a Cloudflare Worker. The guages themselves are available in the Lightview component library.

The easiest way to configure remote variables is to provide the absolute or relative unique URL to access the variable value, e.g.

const {remote} = await import("./types.js");
self.variables(
    {sensor1:object}, {remote:remote("./sensors/sensor1")}
);
await sensor1;

Note: You MAY need to await the remote variable after it is declared. Future use, e.g. in template literals, will NEVER need to be awaited.

If you do not call remote during your variable declaration, the assumed path to the variable will be the current file path plus the variable name, e.g.

const {remote} = await import("./types.js");
self.variables({sensor1:object}, {remote});

is the same as

const {remote} = await import("./types.js");
self.variables({sensor1:object}, {remote:remote("./sensor1")});

If you call remote with a path that is terminates by a slash, then the variable name is appended to the path.

const {remote} = await import("./types.js");
self.variables({sensor1:object}, {remote:remote("https://mysite.com/sensors/")});

is the same as:

const {remote} = await import("./types.js");
self.variables({sensor1:object}, {remote:remote("https://mysite.com/sensors/sensor1")});

In some cases you may have an existing application that does not provide an easily addressable unique URL for each variable, in this case you can provide a configuration object providing a get method (as well as patch and put if the variable is reactive and sending updates to the server), along with an optional path and ttl.

The get method should have the signature get(path,variable). You can use the path, the variable definition contained in variable, and any variables within the closure of your method to create a URL and do your own fetch. Your patch method must parse the fetch response and return a Promise for JSON.

The patch method should have the signature patch({target,property,value,oldValue},path,variable). (Currently, remotely patched variables must be objects, in the future {value,oldValue} will also be legal for primitive variables).

You can use data from the target object along with the path, the variable definition contained in variable, and any variables within the closure of your method to create a URL and do your own fetch. Your patch method must parse the fetch response and return a Promise for JSON.

The put method should have the signature put(target,path,variable). You can use data from the target object along with the path, the variable definition contained in variable, and any variables within the closure of your method to create a URL and do your own fetch. Your put method must parse the fetch response and return a Promise for JSON which is the current state of the variable on the server.

The ttl is the number of milliseconds between server polls to refresh data. If you do not wish to poll the server, you could also implement get so that it establishes a websocket connection and update your variables in realtime.

Here is an example of a custom remote variable configuration for polling sensor data:

const {remote} = await import("./types.js");
self.variables(
    { sensor1:object, sensor2:object },
    { remote:
      remote({
        path: "./sensors/",
        ttl: 10000, // get new data every 10 seconds
        get(path,variable) {
          // create a normalized full path to the sensor data
          const href = new URL(path + object.id,window.location.href).href;
          return fetch(href)
            .then((response) => {
              if(response.status===200) return response.json();
            })
        }
      })
    }
);
await sensor1;
await sensor2;

Here is partial example of a custom remote variable configuration for streaming data over a websocket:

const {remote} = await import("./types.js");
// use these in the UI so that it automatically updates
self.variables({sensor1:object,sensor2:object},{reactive});
// use a variable to hold the websocket
self.variables(
    { ws: object },
    { remote:
      remote({
        path: "./sensors",
        ttl: 10000, // get new data every 10 seconds
        async get(path,variable) {
          // only create one socket
          if(!ws) {
            // create a normalized full path to the sensor data
            const href = new URL(path,window.location.href).href.replace("https://","wss://");
            ws = new WebSocket(href);
            // do websocketty stuff, ideally in a more robust way than this!
            ws.onmessage = (event) => {
              const {sensorName,value} = event.data;
              // assumes sensor1 and sensor2 are the names
              self.setVariableValue(sensorName,value);
            }
            // end websockety stuff
            return Promise.resolve(ws); // you must return a Promise for the socket
        }
      })
    }
);
await ws;

Since using remote variables requires running a custom server, it is not possible to demonstrate on this CodePen hosted site. Below is the source code for a very basic custom NodeJS server that will respond appropriately to remote variable requests and updates for data stored in JSON files. The full demo can be found in the GitHub repository.

Functions

We already introduced observe above. There are several more functions available with Lightview and components created using Lightview.

Lightview

Class Lightview.createComponent(name:string, node:HTMLElement [, {framed:boolean, observer:MutationObsever}])

HTMLCustomElement Lightview.bodyAsComponent([{as = "x-body", unhide:boolean, framed:boolean}])

void Lightview.whenFramed(callback:function [, {isolated:boolean}])

Lifecycle Event Handlers

self.addEventListener(eventName:string,callback:function)

addEventListener(eventName:string,callback:function)

Component Variable Access

You should be careful not to overload and shadow these functions by redefining them on your component.

Array self.getVariable()

Array self.getVariableNames()

any self.getVariableValue(variableName:string)

boolean self.setVariableValue(variableName:string, value:any[, {coerceTo:string|function}])

object self.variables({[variableName]:variableType,...}[,...]})

self.variables({v1:"string"},{imported,shared});
/* returns
  {
      v1: {name: "v1", type: "string", imported:true, shared:true}
  }
*/
self.variables({v2:"number"},{exported,reactive});
/* returns
  {
      v2: {name: "v2", value:2, type: "number", exported:true, reactive:true}
  }
*/
Other Component Methods

Components are DOM nodes with a shadowRoot and have the standard DOM node capability, e.g. getQuerySelector. They also implement getElementById (which is normally only on a document).

Examples of Use

All of the code examples on this site with a Reset button and LIVE label can be edited directly on the site. Or, you can open them in CodePen using the link above the example toward the right margin.

Some examples are not implemented as Pens and can't be edited.

Reactive Variables and Encapsultated Style

A basic example of Lightview to show how simple reactive UI development can be.

If you declare variables as reactive, with the exception of the name attribute for input elements, any HTML referencing the variables will be automatically re-rendered. If you inspect the content of the rendered view, you will also see that this particular page has turned the body into a self rendering component called 'x-body'.

The name attribute is excluded because making dynamic substitutions for name can result in VERY obscure code.

lightview-counter

See the Pen hcx-counter by Simon Y. Blackwell (@anywhichway) on CodePen.

Auto Binding Inputs And Forms

Lightview can automatically create variables based on form input field names and types.

Any time you use a string literal in the value attribute of form inputs, Lightview checks to see if the entirety of the literal is a declared or undeclared variable. If it is undeclared, a reactive variable is automatically created. The variables are then bound to the form input. For radio buttons, it just checks to see if the name attribute matches a variable. (This is one of the reasons the name attribute on inputs can't also be a dynamically bound variable.)

Note how the only variable declared in the Pen below is color, because it is used outside the context of an input value in the style attribute.

lightview-form

See the Pen hcx-form by Simon Y. Blackwell (@anywhichway) on CodePen.

You can bind or autobind an object to the value attribute of a form and then bind the inputs to properties on the object using dot notation paths. Also note in this example the use of observe in the demo instrumentation rather than a change event listener.

See the Pen lightview-form by Simon Y. Blackwell (@anywhichway) on CodePen.

Attribute Directives

Lightview supports l-if, l-for, and : (to handle custom boolean attributes).

lightview-directives

See the Pen lightview--directives by Simon Y. Blackwell (@anywhichway) on CodePen.

Targeted Anchor/HREF Imports

Anchor elements that include an element id as a target can be used to import components and place them at the target. For security, the current implementation requires the components be hosted on the same server as the requesting file or you must set the CORS attribute crossorigin on your <a> element. (This is not a typical location for the crossorigin attribute).

You MUST ABSOLUTELY TRUST the server from which you are loading anchor imported components. Components can navigate up out of the shadowDom and modify other areas of a page. For more on components that are loaded across origins, see Link imports and Nested Components and Sandboxed Components.

Same Origin Import

Since importing through a Pen requires cross origin activity, there is no Pen, just this included example.

Cross Origin Import

Pens are hosted on a separate server, so the crossorigin attribute is used below.

lightview-anchor-crossdomain

See the Pen lightview-anchors by Simon Y. Blackwell (@anywhichway) on CodePen.

Local Templates

Template tags can be used to define components in the same file where they are used. You can actually use any DOM node, but that's for another time...

lightview-template

See the Pen lightview-template by Simon Y. Blackwell (@anywhichway) on CodePen.

You can import components using the <link> tag in the head of your components. These will be recursively loaded by components that use the component with <link> imports.

If you are operating cross domain (which Pens frequently do, you must include the crossorigin CORS attribute). You MUST ABSOLUTELY TRUST the server from which you are loading linked components. Components can navigate up out of the shadowDom and modify other areas of a page. For secure components in iframes see Sandboxed Components, they can also be nested.

lightview-nested

See the Pen lightview-nested by Simon Y. Blackwell (@anywhichway) on CodePen.

The above Pen uses the following two remote files:

In order to reduce network calls, a "compiler" that turns local links into templates in the files containing the links is under development.

Type Checking

lightview-types

See the Pen hcx-types by Simon Y. Blackwell (@anywhichway) on CodePen.

Sharing State Across Components Of Same Type

Lightview can ensure that state is the same across all instances of the same component. This example also shows how variables declared as imported can be used to map attributes to content.

lightview-types

See the Pen lightview-shared by Simon Y. Blackwell (@anywhichway) on CodePen.

Sharing Exclusive Or State

If you had components that implemented audio streams, you would only want to play one at a time. This shows how to implement an XOR of state across components using checkboxes in place of radio buttons.

lightview-xor

See the Pen lightview-xor by Simon Y. Blackwell (@anywhichway) on CodePen.

Sandboxed Remote Components and Micro Front Ends

Since components can contain JavaScript, loading them from another domain brings with it security risks. Lightview can sandbox remote components in iframes. Communication can then occur by getting and setting attributes. Lightview manages this for you.

We can't show you a Pen here because Pens do not work well with iframe communication.

As shown below, you only need to add two lines of code to a remote component to enable it for iframe messaging.

    <script>
        Lightview.whenFramed(({as,unhide,isolated,enableFrames,framed}) => {
        Lightview.bodyAsComponent({as,unhide,isolated,enableFrames,framed});
    })
    </script>

The spread of the argument object above is done for clarity, you could just do this:

    <script>
        Lightview.whenFramed((options) => Lightview.bodyAsComponent(options));
    </script>

Note, because the iframe demo above is doubly nested, the demo iframe does not automatically resize. However, the nested iframe does resize based on its inner component. You can view the demo on a separate page to get a cleaner picture.

Below is the code for the remote form, the code is the same as the form demo shown earlier with just two lines of code and some styling added to iframe enable it. Plus, the addition of a Place Order button.

The automatically exported variable message is created in the component. Just assign values to it like the placeOrder function below.

The parent document requires a little more work because it must handle messages from the child.

You must this metatag to the parent document, <meta name="l-enableFrames"> and some event handlers.

Component Library

Lightview comes with a component library documented separately. The initial release is Goolge Chart based.

Debugging

Your lightview/module script are re-compiled on the fly, so you can't set break points in them directly in your source. Instead, insert a debugger command at the location you wish to start debugging. When the debugger stops at that point, you can set breakpoints in the re-compiled source.

The source is likely to be shown as just one line by your browser debugger. If you are using Chrome, Edge, and Firefox you can click {} in the bottom left of the source window to pretty-print the code.

If you set the variable lightviewDebug to true in a normal script at the top of your component file, then some additional assistance is provided, i.e. DOM nodes with template literals are left in place when executing attribute directives.

  <script>var lightviewDebug=true</script>

Unit Testing

You can use Puppeteer to unit test components. Below is a unit test for the counter button component when instantiated as a standalone file. As described earlier, if you provide the query string ?as=x-body to the Lightview script in the head of a componenty definition file it can be instantiated directly. This script is ignored when loading the component from another file.

Security

In addition to the cross-origin security issues discussed above, there are some security issues related to the use of template literals to substitute values into HTML. This is particularly true if you allow un-sanitized user input as values for variables. We strongly recommend against this for anything other than demos and prototypes. You should use something like DOMPurify or the new browser API Sanitize when it becomes available in order to santize your input prior to assigning values to Lightview variables.

This being said, Lightview has a small blunt mechanism for providing some level of protection:

  1. It "sanitizes" templates before attempting to resolve them by making suspicious code unparseable. The result is that the template will simply not be replaced in the component.
  2. If the target node is an HTMLElement or Attr, it takes the result of the template interpolation and escapes all HTML characters before inserting the value into the DOM. If the target node is a TextNode, no escaping is conducted because it is not needed. The DOM will not try to treat the content of a text node like it is HTML, even if it looks just like HTML. Surprisingly, most of the time, the target will be a TextNode.

Here is the code:

  const templateSanitizer = (string) => {
        return string.replace(/function\s+/g,"")
            .replace(/function\(/g,"")
            .replace(/=\s*>/g,"")
            .replace(/(while|do|for|alert)\s*\(/g,"")
            .replace(/console\.[a-zA-Z$]+\s*\(/g,"");
    }
    Lightview.sanitizeTemplate = templateSanitizer;

    const escaper = document.createElement('textarea');
    function escapeHTML(html) {
        escaper.textContent = html;
        return escaper.innerHTML;
    }

Any errors thrown by the santizier will be logged as warnings to the console and re-thrown.

If you need dynamic arrow function closures in your templates, you can replace Lightview.sanitizeTemplate with your own code at the top of your component file:

  <script src="./lightview.js"></script>
  <script>
   Lightview.sanitizeTemplate =  (string) => {
        return string.replace(/function\s+/g,"")
            .replace(/function\(/g,"")
            .replace(/(while|do|for|alert)\s*\(/g,"")
            .replace(/console\.[a-zA-Z$]+\s*\(/g,"");
    }
  </script>

Here is an example of Lightview sanitizing in action.

lightview-invalid-template-literals

See the Pen lightview-invalid-template-literals by Simon Y. Blackwell (@anywhichway) on CodePen.

Finally, Lightview may be flagged by software supply chain analysis programs for the use of eval. Ligthview actually uses Function, which is slightly less risky, but still an issue. Hence, it deserves explanation.

Lightview makes use of the much maligned but very powerful with statement at four locations in its codebase. The with statement is not typically considered a security risk, but its uninformed use can make for slow and obscure code.

Three of the locations where with is used are related to template interpolation. At the expense of making the code base substantially larger or adding 3rd party dependencies, the use of with could be avoided for interpolation.

The fourth use of with is related to lightview/module script execution. The means by which Ligthview is able to support what appears to be direct coding against variables is through the use of with and a Proxy. The with statement is not valid in strict mode Javascript like modules. The dynamic Function by-passes this restriction for this very limited place in which with is used. It would probably not be possible to implement Ligthview as without a pre-processor if this approach were not taken, in which case Svelte would be the best option.

License

MIT License

Copyright (c) 2022 AnyWhichWay,LLC - Lightview Small, simple, powerful UI creation ...

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Change History

Reverse Chronological Order

If the day is ??, then the version is currently under test on this site, but not yet commited to GitHub or updated on NPM.

2022-05-19 v1.8.1b Addressed issue with textarea inputs not binding properly. Link elements in the head of components are now imported when a module link is imported. Eliminated need for script recompiles. Now, just set a mount function for the component. Much faster. Smaller codebase. Works better with syntax highlighters.

2022-05-11 v1.7.3b Standardized chart components so they all have a very similar API. Added additional charts OrgChart and Timeline.

2022-05-10 v1.7.2b Adjusted relative pathing for components.

2022-05-09 v1.7.1b Added support for object bound forms. Exposed observe for use in lightview/module scripts. Addressed issue related to single quotes causing lightview/module scripts to fail parsing. Improved l-for so that it is not destructive of HTML. Added examples/todo.html. Made l-on more restrictive. Value MUST now be in a template literal. Added examples/markdown.md.

2022-05-04 v1.6.6b Added unit tests. Simplified code for functional types.

2022-05-03 v1.6.5b Added Medium remote sensor article example.

2022-05-02 v1.6.4b Added default whenInvalid to all extened types. Fixed issue with extended boolean always return a valid state when not coercing. Added many unit tests.

2022-05-01 v1.6.3b GitHub repository README updates.

2022-05-01 v1.6.2b Added unit testing example and documentation. Added self.getVariable.

2022-04-30 v1.6.1b Renamed variableType to dataType and variableKind to functionalType. Moved functional type remote to the types.js file since it adds almost 1K of size to the core and may not be used by many developers. Fixed dates in the Change History (they were all set to 2021!).

2022-04-29 v1.5.1b Added extended type definitions, added chart and gauge components, modified lifecycle events to use event listeners.

2022-04-26 v1.4.10b Eliminated excess export of auto defined variables. Fixed unit test flaw for radio elements.

2022-04-25 v1.4.9b Added support for remote variables.

2022-04-8 v1.4.8b Addressed issue where inputs had multiple of the same event handler registered. Addressed script tags getting processed for interpolation when they shouldn't. Added reset button to Pens. Fixed invisible cursor issue in Pens. Fixed default selection for forms based on variables.

2022-04-7 v1.4.7b Documentation changes, addition of any and object constants, aditional sanitizing.