![PyPI Now Supports iOS and Android Wheels for Mobile Python Development](https://cdn.sanity.io/images/cgdhsj6q/production/96416c872705517a6a65ad9646ce3e7caef623a0-1024x1024.webp?w=400&fit=max&auto=format)
Security News
PyPI Now Supports iOS and Android Wheels for Mobile Python Development
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
UI library for creating Web Components with simple and functional API
class
and this
syntax, the library uses plain objects with property descriptors and pure functions for defining custom elements.will
and did
- properties are independent and only have connect
and disconnect
callbacks in the definition.hybrids
gives you all power to create views with JavaScript included.The recommended way is to use the npm
registry:
npm install hybrids
# or
yarn add hybrids
You can also use the built version of the library (with hybrids
global namespace):
<script src="https://unpkg.com/hybrids/dist/hybrids.js"></script>
For the built version all name exports of the
hybrids
are available on thehybrids
global namespace.
The library requires Promise
and Reflect.construct
APIs and Shadow DOM, Custom Elements, and Template specifications. You can use hybrids
in all evergreen browsers and IE11 including a list of required polyfills and shims. The easiest way is to add the following code on top of your project:
// Promise and Reflect.construct polyfills loaded if IE11 is detected
// Web Components shims and polyfills (external packages)
import 'hybrids/shim';
...
Web components shims have some limitations. Especially, webcomponents/shadycss
approximates CSS scoping and CSS custom properties inheritance. Read more on the known issues and custom properties shim limitations pages.
The library calls shims if they are needed, so direct use is not required.
import { define } from 'hybrids';
const MyElement = { ... };
define('my-element', MyElement);
define
takes tag name and plain object with a map of hybrid property descriptors, creates Wrapper
constructor, applies properties on the Wrapper.prototype
and defines custom element using customElements.define()
method.
define(tagName: string, descriptors: Object): Wrapper
tagName
- custom element tag name,descriptors
- object with a map of hybrid property descriptorsWrapper
- custom element constructor (extends HTMLElement
)The following code shows a complete structure of the hybrid property descriptor:
const MyElement = {
propertyName: {
get: (host, lastValue) => { ... },
set: (host, value, lastValue) => { ... },
connect: (host, key, invalidate) => {
...
return () => { ... }; // disconnect
},
},
};
get
orset
method is required,connect
method can be omitted
For the callbacks definition, pure functions without side effects are in favor over this
syntax. Moreover, where it applies the first argument is a host
- custom element instance. This gives full access to the defined hybrid properties and DOM element API as well as an ability to use argument destructuring.
get: (host: Element, lastValue: any) => { ... }
host
- element instancelastValue
- last cached value of the propertyvalue
- value of the current state of the propertyValue of the hybrid property is cached by default. get
method is called only if other hybrid properties used in the body of the getter function have changed. For example, in the following code, name
property getter is only called if firstName
or lastName
has changed:
const MyElement = {
firstName: 'John',
lastName: 'Smith',
name: ({ firstName, lastName }) => `${firstName} ${lastName}`,
};
The example uses property translation feature,
name
property is translated toget
method of property descriptor
set: (host: Element, value: any, lastValue: any) => {...}
host
- an element instancevalue
- a value passed to assertion (ex., el.myProperty = 'new value'
)lastValue
- a last cached value of the propertynextValue
- a value of the property, which replaces cached valueset
method is called within every assertion of the property. The cached value is invalidated if returned nextValue
is not equal to lastValue
(nextValue !== lastValue
). However, get
method is called in the next get call of the property (it is not recalculated after invalidation). Nonprimitive values should be treated as immutable - property is invalidated when value reference changes.
connect: (host: Element, key: string, invalidate: Function) => { ... }
host
- an element instancekey
- a property key nameinvalidate
- a callback function, which invalidates cached valuedisconnect
- a function (without arguments)connect
method is called synchronously in the connectedCallback
of the custom element. Similarly, returned disconnect
function is called in the disconnectedCallback
.
invalidate
callback function forces property value recalculation. It can be used to connect to async web APIs or external libraries.
Invalidate (explicit or by the assertion) triggers
@invalidate
custom event on an element (composed and bubbling). It allows observing elements changes.
Using explicit hybrid property descriptor structure for defining properties is usually not required because define
method translates values to built-in factory functions or to property descriptors. Translation is done in the following order:
const MyElement = {
// If a key is "render"
render: () => {},
// it equals to `render` factory function:
render: render(() => {}),
// If value is a function
computedValue: ({ firstName, lastName }) => `${firstName} ${lastName}`,
// it equals to object with `get` key:
computedValue: {
get: ({ firstName, lastName }) => `${firstName} ${lastName}`,
},
// If value is primitive value
primitive: 'text', // String, Number, Boolean, ...
// it equals to `property` factory function:
primitive: property('text'),
// If value is an object without `get` and `set` properties
emptyArray: [],
// it equals to `property` factory function:
emptyArray: property([]),
};
Object descriptor passed to the define
method is not changed and it stays as it was defined. It means, that custom element definition can be just a simple structure of default values and methods without external dependencies.
The factory is a function, which produces hybrid property descriptor. Rather than explicitly describe the property, factories hide implementation details and minimize redundant code. hybrids
includes four factories, which cover the most important features for creating custom elements.
property(defaultValue: any, [connect: Function]): Object
defaultValue
- any valueconnect
- a connect callback function of the property descriptorproperty
creates property binding with fallback to corresponding element's attribute. property
uses a transform function, which ensures the strict type of the value set by an attribute or a property.
Type of the passed defaultValue
is used to detect transform function. For example, when defaultValue
is set to "text"
, String
function is used. defaultValue
can be a transform function itself, which is called when a property value is set.
string
-> String(value)
number
-> Number(value)
boolean
-> Boolean(value)
function
-> defaultValue(value)
object
-> Object.freeze(value)
undefined
-> value
Object values are frozen to prevent mutation of the own properties, which does not invalidate cached value. Moreover, defaultValue
is shared between custom element instances, so it should not be changed by any of them.
To omit transform, defaultValue
has to be set to undefined
.
All types except object
and undefined
create a fallback connection to element attribute (dashed name of the property key). An attribute value is used only once when an element is connected. The library follows HTML specification and properly transforms attribute to boolean
and string
values.
Rather than using custom element tag name, access to parent or children elements is set by the reference to an object containing hybrid property descriptors. This feature allows avoiding name collision between custom elements because it is irrelevant on what name related custom element is defined.
Binding can be created only between custom elements defined by the library. Built-in elements or other custom elements are not supported.
parent(hybrids: Object): Object
hybrids
- reference to an object containing hybrid property descriptorsnull
or Element
instanceparent
creates a binding with a custom element (defined with hybrids
) in upper DOM tree up to document.body
level (crossing Shadow DOM boundary). The binding is set and updated when custom element is connected and disconnected.
Resolved parent custom element can be safely used in other hybrid properties. If parent hybrid property invalidates, the value of a related property is invalidated as well.
In the following example, label
relates to count
property of the AppStore
. The value of label
is invalidated and recalculated when count
changes:
import { parent } from 'hybrids';
const AppStore = {
count: 0,
};
const MyElement = {
store: parent(AppStore),
label: ({ store: { count } }) => `store count: ${count}`,
}
Possible usage in html (tag names can be different):
<app-store count="42">
<my-element></my-element>
</app-store>
children(hybrids: Object, [options: Object]): Object
hybrids
- reference to an object containing hybrid property descriptorsoptions
- object with available keys:
deep
- boolean, defaults to false
nested
- boolean, defaults to false
array
of Element
instanceschildren
creates a binding with children elements (only in light DOM). Without options, only direct children of the element are on the list. deep
option allows traversing
deeper children. nested
option allows adding element and children of that element if the condition is met (nested
option works only with turn on deep
option).
In the same way as parent
factory works, children
binding invalidates properties when a hybrid property of one of the resolved custom elements is used.
import { children } from 'hybrids';
const TabItem = {
name: '',
active: false,
...
};
const TabGroup = {
tabs: children(TabItem),
active: ({ tabs }) => tabs.find((tab) => tab.active),
...
};
Possible usage in html (tag names can be different):
<tab-group>
<tab-item name="one"></tab-item>
<tab-item name="two" active></tab-item>
</tab-group>
render(fn: Function): Object
fn(host: Element): Function
- callback function with host
argument; returned function have host
and shadowRoot
argumentsrender
creates views in the custom element Shadow DOM. It is template agnostic - passed fn
should return function for updating DOM. The preferred way is to use template engine from the library, but it can be used with any external UI library, that renders DOM.
import { render } from 'hybrids';
const MyElement = {
customRender: render((host) => {
return (host, shadowRoot) => {
// Update DOM here
};
}),
};
Updates are scheduled with requestAnimationFrame()
API triggered by @invalidate
event listener on document level. For example, the view is updated when one of the hybrid property used in fn
changes. If execution of the update function passes ~16ms threshold (it counts from the beginning of the schedule), next element in the queue is updated with next requestAnimationFrame()
.
render
factory ensures update after invalidation of hybrid property, but it is possible to trigger an update by calling property manually on the element instance.
Property defined with render
factory uses the same cache mechanism like other hybrid properties. It means that fn
is only called if hybrid properties invalidate.
The main concept is inspired by the lit-html
, but the implementation is different and follows own conventions. The library provides html
and svg
functions for creating templates (both have the same interface, but svg
uses SVG namespace). They use tagged template literals syntax to create DOM and update dynamic parts leaving static content untouched.
For the best development experience, check if your code editor supports highlighting HTML in tagged template literals
html`<div propertyName="${value}"></div>`;
Attribute expression set a case-sensitive property of element instance (if it has that property in prototype
) with fallback to attribute. There are two exceptions, where it works differently.
class
attribute expression adds or removes a class from element's classList
. An expression can be a string, an array of strings or a map of keys with boolean values.
const name = 'one two';
const array = ['one', 'two'];
const map = { one: true, two: false };
html`<div class="${name || array || map}"></div>`;
style
attribute expression set style properties by the CSSStyleDeclaration
API. An expression has to be an object with dashed or camel-case keys with values.
const styles = {
backgroundColor: 'red',
'font-face': 'Arial',
};
html`<div style="${styles}"></div>`;
However, the preferred way to style elements is using <style>
element inside of the template body:
const MyElement = {
render: () => html`
<style>
div { background-color: red }
</style>
<div>...</div>
`,
};
Attribute expression with other text resolves to string
attribute value:
html`<div class="button ${buttonType} ${buttonColor}"></div>`
on*
attribute expression resolves to event listener set by the addEventListener
API. The part of the attribute after on
prefix is used as an event type. The function returned by the expression is called in an event listener callback.
function send(host, event) {
// do something with value
}
const MyElement = {
value: 42,
render: () => html`
<button onclick="${send}">Send</button>
`,
};
host
references to custom element instance (target element is available at event.target
). The scope of the render function is not required, so a callback can be defined as a pure function.
string
, number
and object
value resolves to textContent
(HTML can be set by the innerHTML
property).
html`<div>Name: ${name}, Count: ${count}</div>`;
// Use it with caution, it might open XSS attack
html`<div innerHTML="${htmlCode}"></div>`;
Falsy expression removes previous truthy value from DOM and renders nothing (the exception is number 0
).
html`<div>${isValid && ...}</div>`;
An expression can return a function, which takes two arguments: host
and target
(text node position marker). Update function returned by the html
is compatible with this API and it can create nested templates.
const submit = (fn) => html`
<button onclick=${fn}>Submit</button>
`;
function myCallback(host, event) {...}
html`
<form>
...
${submit(myCallback)}
</form>
`;
In above example submit
factory function returns an update function created by the html
. The context is propagated, so fn
callback will get the same host
argument as the main template.
For iteration, an expression should return array
with a list of content expressions. Items can be primitive values, nested templates as well as nested arrays.
html`
<todo-list>
${names.map((name) => `Name: ${name}`)}
${items.map(({ id, name }) =>
html`<todo-item>${name}</todo-item>`.key(id)
)}
</todo-list>
`;
Array index
identifies rendered expressions. For efficient re-order use html
function and set iteration key by key
method on returned update function (it sets key and returns update function).
html`...`.key(id)
Promises as a value of the expression are not supported, but the library support them by the html.resolve
method.
html.resolve(promise, placeholder, delay = 200)
promise
- promise, which should resolve/reject update functionplaceholder
- update function for render content until promise is resolved or rejecteddelay
- delay in milliseconds, after which placeholder is renderedconst promise = asyncApi().then(...);
html`
<div>
${html.resolve(
promise
.then((value) => html`<div>${value}</div>`)
.catch(() => html`<div>Error!</div>`),
html`Loading...`,
)}
</div>
`
For templates, which use other custom elements, update function provides helper method for resolving dependencies dynamically. It takes an object with key/value pairs of hybrids definitions or custom element's constructors and defines them.
This method helps to avoid defining unused elements and allows creating a tree-like dependency structure. A complex structure may require only one explicit definition at the root level. As the library factories decouple tag name from the definition, elements can be set with custom names.
In the future, when scoped custom element registers will be available,
define
helper will benefit from that feature and register elements in thehost
element scope.
html`...`.define(map: Object)
map
- object with pascal case or camel case keys and hybrids definitions or custom element's constructors as valuesimport UiHeader from './UiHeader';
const UiCard = {
...,
render: ({ withHeader }) => html`
<div>
${withHeader && html`
<ui-header>...</ui-header>
`.define({ UiHeader })}
...
</div>
`,
};
In above example, the customer of the UiCard
element does not have to explicitly define UiHeader
. It will be defined and processed inside of the rendering process (and only if withHeader
is rendered).
In the browser, which does not support Shadow DOM, ShadyCSS is used to create scoped CSS. This process requires moving out <style>
element from the template and put it into the head of the document. It is done once and before expressions are calculated, so expressions inside style element are not processed correctly.
Expressions inside of the <style>
element are only supported in native implementation of Shadow DOM. However, creating dynamic styles in the environment, which supports Shadow DOM can be inefficient (styles are not shared between elements instances).
html`
<style>
div { color: ${user ? 'blue' : 'black'}; }
</style>
<div>Color text</div>
`;
html`
<style>
div { color: black; }
div.user { color: blue; }
</style>
<div class="${{ user }}">Color text</div>
`
<table>
, <tr>
, <thead>
, <tbody>
, <tfoot>
and <colgroup>
elements with expressions should not have additional text other than whitespace:
html`<tr>${cellOne} ${cellTwo} some text</tr>`;
html`<tr>${cellOne} ${cellTwo}</tr>`;
Expressions inside of the <template>
element are not supported:
html`
<custom-element>
<template>
<div class="${myClass}"></div>
</template>
<div>${content}</div>
</custom-element>
`;
html`
<custom-element>
<template>
<div class="my-static-class"></div>
</template>
<div>${content}</div>
</custom-element>
`;
dispatch(host: Element, eventType: string, options)
host
- element instanceeventType
- type of the event to be dispatchedoptions
- object following dispatchEvent
DOM API specificationfalse
if event is cancelable and at least one of the event handlers which handled this event called preventDefault()
, otherwise it returns true
dispatch
is a helper function, which simplifies event dispatch on element instance. It creates CustomEvent
with set options
and dispatches it on given host
element.
import { html, dispatch } from 'hybrids';
function change(host) {
host.value += 1;
// Trigger not bubbling `change` custom event
dispatch(host, 'change');
}
const MyElement = {
value: 0,
render: ({ value }) => html`
<button onclick="${change}">You clicked me ${value} times!</button>
`,
};
hybrids
is released under the MIT License.
FAQs
A JavaScript framework for creating fully-featured web applications, components libraries, and single web components with unique declarative and functional architecture
The npm package hybrids receives a total of 1,923 weekly downloads. As such, hybrids popularity was classified as popular.
We found that hybrids demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.