arrow-left

Only this pageAll pages
gitbookPowered by GitBook
triangle-exclamation
Couldn't generate the PDF for 142 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

English

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

API

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Guides

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

packages

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

You can create design systems

Today Atomico is used in the development of design systems for various industries such as Banking, Pledge Systems, Insurance, Clinical, Government and more.

Many teams decide to use Atomico for the development of their design systems thanks to its similarity with React, which greatly facilitates the incorporation of human talent into the development of design systems.

hashtag
Why use Atomico to create design systems?

  1. Atomico offers you Storybook 7 Support with superpowers, thanks to you can create stories without the need to declare the argTypes or args since creates them for you

  2. makes it easy for you to build in NPM-friendly ESM format

  3. makes it easy for you to export your code by automatically adding the metadata so that it is optimally consumed as a package, @atomico/exports can even automatically create wrappers for React, Preact and Vue

  4. makes it easy for you to maintain a token system efficiently and sustainably

hashtag
Use cases

hashtag
IBM IX

We thank IBM IX since they have shared their experience in the development of the design system for their client Barmer, you can follow this case through Discord or Github.

@atomico/storybook
@Atomico/storybook
@atomico/vite
@atomico/exports
@atomico/potscss-tokens
https://github.com/atomicojs/atomico/discussions/92arrow-up-right

Getting started with Atomico

This guide will know the essentials to start developing webcomponents with Atomico

Thanks for being here and getting started with Atomico. Let's talk a little about what Atomico offers today:

  1. Development agility, Atomico's functional approach simplifies code at all stages of development.

  2. Lightweight inside and out, Atomico allows you to create a component with less code and with a low dependency impact. Approximately 3kb.

  3. Really fast, Atomico has a in the browser and an agile development experience. Let's understand what a webcomponent created with Atomico looks like:

Let's analyze the code in parts ...

hashtag
1.0 Imports

What have we imported?

  1. c: Function that transforms the functional component into a standard customElement.

  2. css: Function that allows creating the CSSStyleSheet (CSS) for our component as long as it declares the shadowDom.

hashtag
2.0 Creating Our Web Component: Custom Element Definition

hashtag
2.1 Defining Component Render Function

Our function receives all the props (Properties and Attributes) declared in props, the component function declares all the logic and template of the webcomponent. An important rule within Atomico is "📌 every component created with Atomico must always return the tag".

hashtag
2.2 Defining Component Properties(props) and Attributes

Atomico detects the prop (Properties and Attributes) of the component thanks to the association of the props object, this through the use of index and value allows you to define:

  1. index: Name of the property and attribute.

  2. value: type of the prop.

From the example we can infer that Atomico will create in our webcomponent a property and attribute called message and this can only receive values of the String type.

hashtag
2.3 Defining Encapsulated Styles for the Component

Atomico detects the static styles of your component thanks to the association of the styles property:

styles accepts individual or list CSSStyleSheet (CSS) values, the return from the css function is a standard CSSStyleSheet, so it can be shared outside of Atomico.

hashtag
Web Component Registration and Definition

To create our standard customElement we will have to deliver our functional component to the c function of the Atomico module, the c function will generate as a return a customElement that can be defined or extended.

hashtag
Example

good performancearrow-up-right
https://play.atomicojs.dev/arrow-up-right
arrow-up-right
MyComponent.jsx
// Imports
import { c, css } from "atomico";

// Creating Our Web Component: Custom Element Definition
export const MyComponent = c(
  // Defining Component Render Function
  ({ message }) => {
    return <host shadowDom>{message}</host>;
  },
  {
    // Defining Component Properties(props) and Attributes
    props: {
      message: String,
    },
    // Defining Encapsulated Styles for the Component
    styles: css`
      :host {
        font-size: 30px;
      }
    `,
  }
);

// Web Component Registration and Definition
customElements.define("my-component", c(component));
MyComponent.jsx - Line: 1
import { c, css } from "atomico";
MyComponent.jsx - Line: 7 to 9
// Defining Component Render Function
({ message }) => {
    return <host shadowDom>{message}</host>;
}
MyComponent.jsx - Line: 12 to 14
// Defining Component Properties(props) and Attributes
props: {
  message: String,
},
MyComponent.jsx - Line: 16 to 19
// Defining Encapsulated Styles for the Component
styles: css`
  :host {
    font-size: 30px;
  }
`,
MyComponent.jsx - Line: 25
// Web Component Registration and Definition
customElements.define("my-component", c(component));

What can you do with Atomico?

With Atomico you can do this and more

You can create amazing webcomponentschevron-right
You can create design systemschevron-right
You can create web applicationschevron-right
You can create mobile applicationschevron-right
You can create websiteschevron-right

Atomico

A micro library inspired by React Hooks, designed and optimized for the creation of webcomponents.

import { c } from "atomico";
import { c } from "atomico";

Atomico simplifies learning, workflow and maintenance when creating webcomponents and achieves it with:

  1. Scalable and reusable interfaces: with Atomico the code is simpler and you can apply practices that facilitate the reuse of your code.

  2. Open communication: with Atomico you can communicate states by events, properties or methods.

  3. Agnostic: your custom Element will work in any web-compatible library, eg React, Vue, Svelte or Angular.

  4. Performance: Atomico has a comparative performance at Svelte levels, winning the third position in performance according to in a comparison of 55 libraries among which is React, Vue, Stencil and Lit.

hashtag
API

Props(Properties)

The props in Atomico are the way to associate the webcomponent properties and reactive attributes that trigger the logic or interface of the webcomponent.

Props is the Atomico recommended way to declare visible and accessible states at the instance level of your webcomponents, with props you can:

  1. Access state via instance, example: document.querySelector("my-component").myStateProp.

  2. Dispatch events on prop value change, example: document.querySelector("my-component").addEventListener("myPropChange",console.log)

Getting started with Atomico for React users

Hi, I'm Atomico js and I bring you the React syntax for webcomponents, I think you and I get along very well 😊.

First let's say that Atomico is light since it has a size close to 3kB vs React + ReactDOM that have a size close to 60kB, now if your project is already written in React I can integrate Atomico progressively since a component created can be instantiated as a component for React thanks to , example:

Magical 🪄, isn't it?... well now let's speed up your Atomico learning path:

hashtag
How to declare a component?

//
2.5kB
const MyComponent = c(
({name})=><host shadowDom>Hello, {name}</host>,
{
props: { name: String }
}
);
customElements.define("my-component", c(component));
//
2.5kB
const MyComponent = c(
({name})=><host shadowDom>Hello, {name}</host>,
{
props: { name: String }
}
);
customElements.define("my-component", c(component));
webcomponents.devarrow-up-right
dnaProps(Properties)chevron-right
puzzle-pieceVirtualDOMchevron-right
fishing-rodHookschevron-right
microscopeTestingchevron-right
.
  • Reflect attributes as prop, example: <my-component my-prop="...."> to document.querySelector("my-component").myProp.

  • define strict input types for props.

  • hashtag
    Syntax

    Any function that represents the webcomponent will be able to associate the static object props for the declaration of reactive properties and attributes, for example:

    hashtag
    Consider that:

    1. The prop names in Camel Case format will be translated to for use as an attribute to the Kebab Case format, this behavior can be modified through the "attr" property when using a structured declaration.

    2. Structured declarations require the "type" property minimally.

    3. Not all types can use the "reflect" properties.

    4. The declaration of the "value" property can vary depending on the type.

    hashtag
    Simple statements

    Simple statements allow setting just type validations.

    hashtag
    Structured declaration

    Improve the definition by adding utility declarations, allowing for example to reflect the property's value as attributes, automatically emit events or associate default values. Remember these types of declarations minimally require the use of the type property.

    hashtag
    Prop.type

    Type
    Supports reflect

    String

    ✔️

    Number

    ✔️

    Boolean

    ✔️

    hashtag
    Prop.reflect

    If the "reflect" property is set to true, its value is reflected as an attribute of the webcomponent, this is useful for the declaration of CSS states, example:

    hashtag
    Prop.event

    It allows dispatching an automatic event before the prop value change, example:

    Where:

    • event.type: String - optional, name of the event to be emitted when the prop is changed

    • event.bubbles: Boolean - optional, indicates that the event can be listened to by containers.

    • event.detail: Any - optional, allows to attach a custom detail for the event

    • event.cancelable: Boolean - optional, indicates that the event can be canceled by any listener

    • event.composed: Boolean - optional, allows the event to exceed the shadow-root limit

    The special properties of the event are the well-known Event Init, you can know more details in the attached documentationarrow-up-right.

    hashtag
    Prop.value

    Atomico allows the definition of default values of the props.

    The association of callback as value allows generating unique values for each instance of the webcomponent, this is useful with the Object and Array types since it eliminates the references between instances.

    hashtag
    Reactivity in the scope of the webcomponent

    Atomico removes the use of "this" given its functional approach, but adds the hook [useProp] (hooks / useprop.md) which allows to reference a prop for use with a functional syntax, eg:

    hashtag
    Recommended articles

    import { c } from "atomico";
    
    const props = {
        // Simple statement
        value1: String,
        // Structured statement
        value2: {
          type: String,
          reflect: true,
          attr: "advaceprop",
          value: "default string",
          event: {
            type: "UpdateAdvanceProp",
            bubbles: true,
          },
        },
    };
    
    const MyComponent = c((props) => <host>{props.value1}</host>, { props });
    
    customElement.define("web-component", MyComponent);
    const props = {
      propString: String,
      propNumber: Number,
      propObject: Object,
      propArray: Array,
      propBool: Boolean,
      propCallback: Function,
    };
    // valid declaration
    const props = { myName: String };
    // valid declaration
    const props = { myName: { type: String } };
    const props = {
      checked: {
        type: Boolean,
        reflect: true,
      },
    };
    const props = {
      value: {
        type: String,
        event: {
          type: "change",
          bubbles: true,
          composed: true,
          detail: "any value",
          cancelable: true,
        },
      },
    };
    // listener
    nodeComponent.addEventListener("change", handler);
    const props = {
      valueNormal: {
        type: Number,
        value: 100,
      },
      valueObject: {
        type: Object,
        value: () => ({}),
      },
    };
    const props = { message: String };
    
    const MyComponent = c(
      () => {
        const [message, setMessage] = useProp("message");
        return (
          <host>
            Hello, {message}
            <input oninput={({ target }) => setMessage(target.value)} />
          </host>
        );
      },
      { props }
    );
    Is it advisable to declare events using the props API?chevron-right
    Value cycle as propchevron-right
    Atomico, like React, allows a declaration of components using only functions, example:
    import { useState } from "react";
    
    import { c, useProp } from "
    

    From the example we will highlight the following differences:

    1. In Atomico you only use one import.

    2. useProp is like useState, but with the difference that useProp references the state from the webcomponent property defined in counter.props.

    3. const props allows us to create the properties of our webcomponent, these are like React's propTypes, but with a big difference they are associated with the instance and can be read and modified by referencing the node, example document.querySelector("my-counter").count = 10;

    4. ReactDom.render needs a reference to mount the component, in Atomico you only need to create the my-counter tag to create a new instance of the component.

    5. The <host/> tag is similar to <> </> for React, but <host/> represents the webcomponent instance and every component created with Atomico must return the host tag

    6. This is only readability, but in Atomico by convention we do not use capital letters when naming our component, these are only used when creating the customElement as in line 16, since Counter is instantiable.

    Now I want to invite you to learn how to declare a style using Atomico.

    hashtag
    How do you declare styles using Atomico?

    It is common to see the use of libraries such as Emotion or styled-components to encapsulate styles in React, but these add an additional cost, be it for performance or bundle, in Atomico there is no such cost.

    const Button = styled.a`
      /* This renders the buttons above... Edit me! */
      display: inline-block;
      border-radius: 3px;
      padding: 0.5rem 0;
      margin: 0.5rem 1rem;
      width: 11rem;
      background: transparent;
      color: white;
      border: 2px solid white;
    
      /* The GitHub button is a primary button
       * edit this to target it specifically! */
      ${props => props.primary && css`
        background: white;
        color: black;
      `}
    `
    
    render(
      <div>
        <Button
          href="https://github.com/styled-components/styled-components"
          target="_blank"
          rel="noopener"
          primary
        >
          GitHub
        </Button>
    
        <Button as={Link} href="/docs">
          Documentation
        </Button>
      </div>
    )
    import { c, css } from "atomico";
    
    const props = { primary: { type: Boolean, relfect: true } };
    
    const styles = css`
      :host {
        display: inline-block;
        border-radius: 3px;
        padding: 0.5rem 0;
        margin: 0.5rem 1rem;
        width: 11rem;
        background: transparent;
        color: white;
        border: 2px solid white;
      }
    
      :host([primary]) {
        background: white;
        color: black;
      }
    `;
    
    export const Button = c(
      () => (
        <host shadowDom>
          <slot />
        </host>
      ),
      { props, styles }
    );
    
    customElements.define("my-button", Button);

    hashtag
    Instances, children and slots

    It is normal for React to create components that you then instantiate within other components, for example:

    with Atomico there are certain differences:

    hashtag
    1. With Atomico you can instantiate CustomElements using its constructor

    The constructor in Atomic is the product of the c function and is the one you will use to register your webcomponent, example:

    According to the previous example, you can instantiate MyComponent as a JSX Component, example:

    This instance type allows autocompletion at the JSX level and type validation at the Typescript level.

    hashtag
    2. With Atomico you can instantiate components as functions as long as these are only stateless functions

    This will be useful for reusing templates, but always remember stateless.

    import { Button } from "@formas/button/react";
    
    function App(){
       return <>
          <h1>React App!</h1>
          <Button onClick={()=>console.log("Click!")}>
             Submit
          </Button>
       </>
    }
    @atomico/react
    
    function Child({children}){
        return <span>children</span>
    }
    
    function Main(){
        return <>
            <Child>text 1...</Child>
            <Child>text 2...</Child>
        </>
    }
    my-component.tsx
    import { c } from "atomico";
    
    export const MyComponent = c(() => <host>...</host>); // Constructor
    
    customElements.define("my-component", MyComponent);
    import { c } from "atomico";
    import { MyComponent } from "./my-component";
    
    export const MyApp = c(() => (
      <host>
        <MyComponent />
      </host>
    ));
    
    customElements.define("my-app", MyApp);
    function MyIcon({ size }) {
      return (
        <svg height={size} width={size}>
          <circle r="45" cx="50" cy="50" fill="red" />
        </svg>
      );
    }
    
    const MyComponent = c(() => (
      <host>
        Small <MyIcon size={"1rem"} />
        Large <MyIcon size={"2rem"} />
      </host>
    ));
    From React to Atomicochevron-right
    VirtualDOM api differenceschevron-right
    import ReactDOM from 'react-dom'
    function Counter({initialCount}) {
    const [count, setCount] = useState(initialCount);
    return (
    <>
    Count: {count}
    <button onClick={() => setCount(initialCount)}>Reset</button>
    <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
    );
    }
    render(
    <Counter initialCount={1}/>,
    document.querySelector("#counter")
    );
    atomico
    "
    ;
    const props = { count: { type: Number, value: 0 } };
    const Counter = c(
    () => {
    const [count, setCount] = useProp("count");
    return (
    <host>
    Count: {count}
    <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
    <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
    </host>
    );
    },
    { props }
    );
    customElements.define("my-counter", Counter);

    Object

    ✔️

    Array

    ✔️

    Promise

    ❌

    Symbol

    ❌

    Function

    ❌

    All references to existing types in the browser(HTMLElement, Element, Node, Date, File... more than 300 😎)

    ❌

    useEvent

    Dispatch events from the webcomponent without referencing the context(this)

    hashtag
    Syntax

    Where:

    • dispatchEvent: callback, dispatches the event from the webcomponent and allows defining the detail by receiving a first parameter

    myEvent: string, name of the event to dispatch.

  • eventInit: optional object, event configuration.

  • hashtag
    Examples

    import { useEvent } from "atomico";
    
    import { useEvent } from "atomico";
    
    import { Host, useEvent } from "atomico";
    
    type DetailClickButton = {id: number};
    //                             👇 declaration to associate event to JSX/TSX
    function component():Host<{onclickButton: CustomEvent<DetailClickButton>}> {
    //                             👇 type for detail
      const dispatchEvent = useEvent<DetailClickButton >("clickButton", {
        bubbles: true,
        composed: true,
      });
      return (
        <host>
          <button onclick={() => {
            //            👇 Detail
            dispatchEvent({id:100});
          }}>button</button>
        </host>
      );
    }

    hashtag
    Event customization

    The second parameter of useEvent allows customizing the behavior of the even:

    hashtag
    Recommended articles

    const dispatchEvent = useEvent(myEvent, eventInit);
    interface EventInit {
      // Allows the event to be dispatched upstream to the node's containers.
      bubbles?: boolean;
      // Allows the event to traverse the shadowDOM event capture.
      composed?: boolean;
      // Allows the event to be canceled.
      cancelable?: boolean;
      // Allows customizing the event builder, ideal for event instance-based communication.
      base?: Event | CustomEvent;
    }
    How to declare events for your component at the type level for TSX?chevron-right

    VirtualDOM

    Atomico's virtualDOM is designed to enhance the use of webcomponents.

    hashtag
    Syntax

    hashtag
    JSX

    Atomico supports jsx-runtime

    useProp

    Reactivity in the scope of the webcomponent without the use of context(this)

    useProp allows you to work with a prop(property) of the webcomponent in a similar way to useState.

    hashtag
    Syntax

    Where :

    Hooks

    Improves the experience of reusing logic between webcomponents based on Atomico

    hashtag
    Hooks only for webcomponents

    hashtag
    Hooks homogolates of React

    useRef

    Create a persistent object between renders to capture from a node from VirtualDOM

    hashtag
    Syntax

    hashtag
    Example

    Advanced

    hashtag
    Special properties

    Property
    Type
    Effect

    useHost

    Hook that creates a reference that curren is the instance of the webcomponent.

    hashtag
    Syntax

    Returns the instance of the webcomponent in reference format, this reference allows to extend behaviors when creating customHooks.

    hashtag

    function component() {
    const dispatchEvent = useEvent("clickButton", {
    bubbles: true,
    composed: true,
    });
    return (
    <host>
    <button onclick={() => dispatchEvent()}>button</button>
    </host>
    );
    }
    function component() {
    const dispatchEvent = useEvent("clickButton", {
    bubbles: true,
    composed: true,
    });
    return (
    <host>
    <button onclick={() => {
    const detail = "my-component"; // 👈
    dispatchEvent(detail); // 👈
    }}>button</button>
    </host>
    )c
    hashtag
    @atomico/hooks

    Atomico today offers more Hooks external to the core, we invite you to visit @atomico/hooks with more than 50 hooks to enhance the use of webcomponents 😎

    usePropchevron-right
    useEventchevron-right
    useHostchevron-right
    useUpdatechevron-right
    usePromisechevron-right
    useAsync and useSuspensechevron-right
    useStatechevron-right
    useReducerchevron-right
    useRefchevron-right
    useEffect, useLayoutEffect and useInsertionEffectchevron-right
    useMemo and useCallbackchevron-right
    useContextchevron-right
    useIdchevron-right
    @atomico/hookschevron-right
    hashtag
    Observation

    The reference object is useful for referencing nodes between customHooks.

    const ref = useRef(optionalCurrent);
    import { useRef, useEffect, useState } from "atomico";
    
    function component() {
      const ref = useRef();
      const [message, setMessage] = useState();
      useEffect(() => {
        const { current } = ref;
        current.addEventListener("input", () => {
          if (current.validity.typeMismatch) {
            setMessage("Invalid!");
          }
          current.setCustomValidity("");
        });
      }, []);
      return (
        <host>
          <input type="email" ref={ref} />
          {message && <h1>{message}</h1>}
        </host>
      );
    }
    , alternatively you can import the
    h
    function to declare manual of the JSX pragma, eg:

    hashtag
    Return rule

    An important rule of Atomico's virtualDOM is that every webcomponent must return the <host/> tag since it represents the state of the webcomponent's DOM, such as:

    1. Enable the use of the shadowDOM by declaring the shadowDom property.

    2. Association of events, attributes or properties.

    3. Template of the webcomponent.

    hashtag
    Template

    hashtag
    Event Association

    Atomico considers that a property must be associated as an event if it is of the function type and begins with the prefix 'on', eg:

    hashtag
    Simple lists

    hashtag
    Lists with keys

    the key property can receive values of the type of any type that allows generating a reference to the node, eg:

    hashtag
    Node references

    A technique inherited from React, it allows obtaining the reference of the node to which the Ref object is associated through the ref property, example:

    The references must be immutable objects, to create it there is the useRef hook that creates a reference for each instance of the webcomponent.

    hashtag
    shadowDom property

    This property allows you to declare the use of the shadowDom, eg:

    hashtag
    Method association

    You can declare a method by declaring a function in the host tag without using the prefix on in its name, eg:

    If when creating or updating the DOM it does not detect the use of the property, it will be associated as a method of this, thus allowing it to be accessed from the DOM, eg:

    To access the DOM safely wait for the resolution of the updated property created by the render cycle.

    Example

    From the example we can highlight that useListener is a customHook that allows listening to an event from the webcomponent without the need to link said event to the VirtualDOM.

    const refHost = useHost();
    import { useHost, useEffect } from "atomico";
    
    function useListener(type: string, callback: (ev: Event) => void) {
      const ref = useHost();
      useEffect(() => {
        const { current } = ref;
        current.addEventListener(type, callback);
        return () => current.removeEventListener(type, callback);
      }, []);
    }
    import { c } from "atomico";
    
    const MyComponent = c(() => {
      const handlerClick = () => console.log("click!");
      return (
        <host shadowDom onclick={handlerClick}>
          <h1>content</h1>
          <slot></slot>
        </host>
      );
    });
    
    customElements.define("my-component", MyComponent);
    /**@jsx h*/
    import { h } from "atomico";
    import { c } from "atomico";
    
    const MyComponent = c(() => {
      // The webcomponent should always return the host tag
      return (
        <host shadowDom>
          <slot></slot>
        </host>
      );
    });
    
    customElements.define("my-component", MyComponent);
    <host onclick={() => console.log("click!")}></host>;
    <host onMyEvent={() => console.log("MyEvent!")}></host>;
    <input oninput={() => console.log("click!")} />;
    <slot onslotchange={() => console.log("update slot!")} />;
    <host>
      {[1, 2, 3].map((value) => (
        <span>{value}</span>
      ))}
    </host>
    <host>
      {[1, 2, 3].map((value) => (
        <span key={value}>{value}</span>
      ))}
    </host>
    <host>
      {listaInmutable.map((objeto) => (
        <span key={objeto}>{objeto.value}</span>
      ))}
    </host>
    const ref = useRef();
    
    <host ref={ref}></host>; // The reference will be the instance 
                             // of the custom Element
    
    <input ref={ref}/>; // The reference will be the input
    <host shadowDom></host>;
    // The use of shadow Dom is not exclusive to the host tag
    // can be used for any node that supports it
    <div shadowDom></div>;
    // Template
    <host myMethod={() => console.log("method!")}></host>;
    // Use from the DOM
    document.querySelector("my-component").myMethod();
    const myElement = new MyElement();
    
    await myElement.updated;
    
    myElement.myMethod();
    value: Current value of the prop.
  • setValue: Callback to update the value of the prop.

  • myProp: string, defines the name of the prop to be used by the hook.

  • hashtag
    Example

    import { useProp } from "atomico";
    
    import { useProp } from "atomico";
    
    function useCounter(prop) {
      //                                 👇 type for prop
      const [value, setValue] = useProp<number>(prop);
      return {
        value,
        increment: () => setValue((value) => value + 1),
        decrement: () => setValue((value) => value - 1),
      };
    }
    
    function component() {
      const counter = useCounter("value");
      return (
        <host>
          <button onClick={counter.increment}>+</button>
          <strong>{counter.value}</strong>
          <button onClick={counter.decrement}>-</button>
        </host>
      );
    }
    
    component.props = {
      value: { type: Number, value: 0 },
    };

    Where:

    1. useCounter is a customHook and that it can work with any property of the webcomponent of type Number.

    2. useCounter returns 2 methods increment and decrement that modify the value of the prop.

    3. useCounter can be instantiated multiple times for different properties.

    const [value, setValue] = useProp(myProp);

    Boolean

    Enables the use of the shadowDOM on the node.

    staticNode

    Boolean

    Render the node only once, this optimizes the update process as the node is ignored between updates.

    cloneNode

    Boolean

    clone a node of type Element

    $<name>

    any

    the $ prefix allows defining as an attribute in all cases.

    hashtag
    render

    By default, the render is configured to be used within the webcomponent by reading the return of the function, but it can be used outside of Atomico, example:

    import { h, render } from "
    
    import { h, render } from "
    
    import { html, render } from "
    
    circle-exclamation

    Render rule "The first node of the render must always be the host tag".

    hashtag
    Constructor with custom element

    This technique allows you to use any registered custom element without the need to know its tag-name for its use, example:

    Advantage :

    1. Remove leverage from tag-name

    2. Infer the types of the props and autocomplete only if you use JSX and Atomico.

    hashtag
    Constructor with DOM

    Atomico allows the use of the DOM, for this it establishes its created or recovered node as a constructor, example:

    hashtag
    Dynamic constructor

    Atomico associates the variable associated with the instance as a constructor, example:

    hashtag
    staticNode

    allows to declare a node within the scope of the function as static, this will optimize the diff process between render, achieving better performance in cases of high stress of the UI, example:

    the biggest advantage of this is that the node accesses the scope of the webcomponent

    hashtag
    cloneNode

    Allows to clone a node from the virtualDOM, example:

    The objective of this feature is to retrieve slot and use it as a template from the webcomponent.

    hashtag
    SSR hydration

    Atomico allows reusing existing DOM in the document. This is done during the webcomponent instatiation, by setting a special property in the tag to mark it for hydration.

    This can be done for shadowDom too:

    circle-info

    These code samples are not part of the standard yet, so polyfills must be used to ensure that it works in all browsers. Read more about Google Chrome's proposal here https://web.dev/declarative-shadow-dom/arrow-up-right.

    shadowDom

    // 1️⃣ We create the custom element
    const Component = c(()=><host/>);
    
    // 2️⃣ We register the custom element
    customElements.define("my-component", Component);
    
    function App(){
        return <host>
            <Component/>
        </host>
    }
    const MyComponent = c(()=>{
        const Div = useMemo(()=>document.createElement("div"));
        return <host>
            <Div style="color: black">content...</Div>
        </host>
    });
    const MyComponent = c(()=>{
        const TagName = `my-${subComponent}`;
        return <host>
            <TagName/>
        </host>
    });
    const MyComponent = c(() => (
      <host>
        <h1 staticNode onclick={console.log}>
          i am static node!
        </h1>
      </host>
    ));
    const Div = document.createElement("div");
    
    Div.innerHTML = `<h1>Div!</h1>`;
    
    const MyComponent = c(() => (
      <host>
        <Div cloneNode onclick={console.log} />
        <Div cloneNode onclick={console.log} />
        <Div cloneNode onclick={console.log} />
        <Div cloneNode onclick={console.log} />
        <Div cloneNode onclick={console.log} />
      </host>
    ));
    <my-webcomponent data-hydrate>
        <h1>I will be the title of the component</h1>
    </my-webcomponent>
    <my-webcomponent data-hydrate>
        <template shadowroot="open">
            <h2>Shadow Content</h2>
            <slot></slot>
            <style>shadow styles</style>
        </template>
        <h2>Light content</h2>
    </my-webcomponent>
    function useCounter(prop) {
    const [value, setValue] = useProp(prop);
    return {
    value,
    increment: () => setValue((value) => value + 1),
    decrement: () => setValue((value) => value - 1),
    };
    }
    function component() {
    const counter = useCounter("value");
    return (
    <host>
    <button onClick={counter.increment}>+</button>
    <strong>{counter.value}</strong>
    <button onClick={counter.decrement}>-</button>
    </host>
    );
    }
    component.props = {
    value: { type: Number, value: 0 },
    };
    atomico
    "
    ;
    render(
    h("host",{ style: {background:"red"} }
    h("h1",null,"Text content...")
    ),
    document.querySelector("#app")
    );
    atomico
    "
    ;
    render(
    <host style={{background:"red"}}>
    <h1>Text content...</h1>
    </host>,
    document.querySelector("#app")
    );
    atomico
    "
    ;
    render(
    html`<host style=${background:"red"}>
    <h1>Text content...</h1>
    </host>`,
    document.querySelector("#app")
    );

    useState

    hashtag
    Syntax

    const [state, setState] = useState(optionalInitialState);

    Where:

    1. const [state,setState] : Return of useState, the arguments allow reading and updating of the state associated with the hook instance.

      • state : Current state.

      • setState: Current status updater.

    2. useState( optionalInitialState ): Function that associates the state to the webcomponent:

      • optionalInitialState: Optional parameter that defines the initial state associated to the hook instance, If optionalInitialState is a function it will be executed in order to obtain the initial state only at the moment of the hook instance for the first time.

    hashtag
    Example

    function MyComponent() {
      const [count, setCount] = useState(0);
      return <host onclick={() => setCount(count + 1)}> {count} </host>;
    }

    useReducer

    useMemo and useCallback

    hashtag
    Syntax

    const memoValue = useMemo(callback, optionalArgumentList);

    Where :

    1. memoValue : Return memorized by useMemo.

    2. callback: Function that is executed one or more times according to optionalArgumentList.

    3. optionalArgumentList: Array of arguments that controls the execution of callback, if an argument of optionalArgumentList changes it will trigger that callback is executed again.

    hashtag
    useCallback

    Hook that allows you to memorize a callback so that it keeps its scope

    Where:

    1. memoCallback : Return memorized by useCallback.

    const memoCallback = useCallack(callback, optionalArgumentList);

    useEffect, useLayoutEffect and useInsertionEffect

    Allows to run side effects after rendering

    hashtag
    Syntax

    useEffect(effectCallback, optionalArgumentList);

    Where :

    1. effectCallback : Function that is executed one or more times according to optionalArgumentList,effectCallback can return a function that will be executed only if effectCallback is executed again or the webcomponent is unmounted.

    2. optionalArgumentList: Array of arguments that controls the execution of effectCallback, if an argument ofoptionalArgumentList changes it will trigger that effectCallback is executed again without first cleaning the effects subscribed by the previous execution.

    hashtag
    Example

    hashtag
    useLayoutEffect

    useLayoutEffect replicates the logic of useEffect but with synchronous execution after rendering.

    hashtag
    useInsertionEffect

    useLayoutEffect replicates the logic of useEffect but with synchronous execution before rendering.

    useUpdate

    Force an update, ideal for working with references

    It is normal that the state of your component depends on the references of a slot, this hook facilitates the process of observing these references without the need to associate the changes to a state.

    hashtag
    Syntax

    Where:

    update: Callback, force component update.
    const update = useUpdate();
    const listenerClickWindow = () => {
      const handlerClick = () => {
        console.log("Click window!");
      };
    
      window.addEventListener("click", handlerClick);
    
      const unlistenerClickWindow = () =>
        window.removeEventListener("click", handlerClick);
    
      return unlistenerClickWindow;
    };
    
    useEffect(listenerClickWindow, []);

    useId

    useId is a Atomico Hook for generating unique IDs that can be passed to accessibility attributes.

    hashtag
    Syntax

    import { useId } from "atomico";
    
    const stringId = useId();

    hashtag
    Example

    Use ID generate a unique ID for each component, this id can be useful for referencing CSS selectors or creating names for inputs that only the component knows about.

    function component() {
      const id = useId();
      return <host id={id}>
        <style>{`#${id}{ display: block; color: white; }`}</style>
        <h1>useId</h1>
      </host>;
    }

    useProvider

    Allows the host that instantiates this useProvider to become the context.

    This hook enables you to take control of the context from the component that instantiates useProvider, thus avoiding the need to instantiate the context node in the DOM.

    hashtag
    Example of the useProvider hook:

    import { createContext, useProvider, c } from "atomico";
    
    export const Theme = createContext({
        color: "white",
        background: "black"
    });
    
    export const App = c(()=>{
        useProvider(Theme,{
            color: "red",
            background: "yellow"
        });
        
        return <host shadowDom><slot/></host>
    });
    
    

    hashtag
    Objective

    Avoid creating an instantiable context node in the DOM.

    useContext

    Since version Atomico@1.62.0 has introduced a context api as part of the core.

    With the new contexts API you will be able to easily communicate components without the need to handle events, by default the communication is top down, but through it you can share anything as long as it is defined as an object.

    Atomico's api is similar to React's Context api, let's explore the behavior of Atomico's context api:

    hashtag
    createContext

    create a custom Element as a context, this will serve to synchronize all the components nested within it, you must always remember to declare the tagname of this customElement, example

    import { createContext } from "atomico";
    
    export const Theme = createContext({
        color: "white",
        background: "black"
    });
    
    customElements.define( "my-theme", Theme  );

    hashtag
    useContext

    It allows to consume the return of createContext, let's go back to the previous example and suppose that we want to consume the customElement Theme created by createContext, the code for this would be the following:

    In this way useContext captures the value of the parent component or reuses the value given by default to createContext.

    hashtag
    Custom context values

    By setting the value prop on the context, you can pass custom values down the sub-tree:

    circle-info

    It is highly recommended to always use custom properties to expose the appearance configuration of your component at the static CSS level, since useContext is designed to share information between components in a unidirectional way.

    hashtag
    When to use the Context API?

    It is ideal to always prioritize a conversation between parent and child through events or props api, but sometimes the depth of the DOM makes this process difficult, for this the context api has been introduced. To remove DOM depth limitations and ensure synchronization based on a unique identifier, some ideal cases to solve with the context api are:

    1. Synchronize states or private methods between components.

    2. Share a value or states inherited from the parent regardless of DOM depth.

    import { useContext } from "atomico";
    import { Theme } from "./my-theme";
    
    function card(){
        const {color, background} = useContext(Theme);
        return <host>
            color: { color }
            background: { background }
        </host>
    }
    function child() {
        const {color, background} = useContext(Theme);
        return <host>{color === "red" && background === "blue"}</host>
    }
    
    
    export const MyChild = c(child);
    customElements.define("my-child", MyChild);
    
    function parent() {
        return (
            <host>
                <Theme value={{ color: "red", background: "blue" }}>
                    <my-child></my-child>
                </Theme>
            </host>
        )
    }

    useAbortController

    Atomico now introduces a new hook called useAbortController, which allows aborting the execution of asynchronous calls, example:

    The idea is to create an instance of AbortController every time the hook's parameters change. Each parameter change will abort the previously generated controller, thus cancelling any subscribed promises.

    hashtag
    Example

    import { useAsync, useAbortController } from 'atomico';
    
    async function getUser(id: number, signal: AbortSignal) {
      return fetch(`/id/${id}`, { signal });
    }
    
    function myComponent({ userId }: Props<typeof myComponent>) {
      const { signal } = useAbortController([userId]);
    
      const user = useAsync(getUser, [userId, signal]);
    
      return <host>{user.name}</host>;
    }
    
    myComponent.props = { userId: { type: Number, value: 0 } };

    The significant advantage of using useAbortController is the automatic cancellation of asynchronous processes that depend on it when modifying the arguments provided to useAbortController (similar to useEffect).

    useAsync and useSuspense

    suspend the execution of a render until the resolution of an asynchronous process

    hashtag
    useAsync

    with a similar approach to React's use hook, but with scope approach.

    scope approach? yes, this hook seeks to resolve a promise from a callback return, this allows you to regenerate the promise according to the scope, example:

    import { useAsync } from "atomico";
    

    where:

    • getUser: async callback.

    • [ userId ]: arguments that if changed regenerate the promise.

    • user : promise return

    Like useEffect, the promise will be executed every time the arguments to the second parameter of useAsync change.

    circle-exclamation

    Rendering will be suspended until the promise is resolved or rejected, the resolution of the promises can be observed with useSuspense

    hashtag
    useSuspense

    allows to listen to all useAsync executions nested in the component, example:

    const getUser = (userId) => fetch(`users/${userId}`).then((res) => res.json());
    function component({ userId }) {
    const user = useAsync(getUser, [userId]);
    return (
    <host>
    <h1>name: {user.name}</h1>
    </host>
    );
    }
    component.props = { userId: Number }
    function component() {
      const status = useSuspense();
      return (
        <host shadowDom>
          {status.pending ? "Loading..." : status.fulfilled ? "Done!" : "Error!"}~
          <slot />
        </host>
      );
    }
    import { Props, useAsync } from "atomico";
    
    const getUser = (userId: number): Promise<{ name: string }>
      fetch(`users/${userId}`).then((res) => res.json
    
    function component({ userId }: Props<typeof component>) {
      const user = useAsync(getUser, [userId]);
      return (
        <host>
          <h1>name: {user.name}</h1>
        </host>
      );
    }
    
    component.props = { userId: Number };

    usePromise

    Easily observe asynchronous processes

    The purpose of this hook is to facilitate the consumption of promises.

    hashtag
    Syntax

    import { usePromise } from "atomico";
    
    const callback = (id: number) => Promise.resolve(id);
    const args: Parameters<typeof callback> = [1];
    const autorun = true;
    
    const promise = usePromise( callback, args, autorun );

    where:

    • callback : asynchronous function.

    • args: arguments that callback requires.

    • autorun: by default true, it automatically executes the promise, if it is false the execution of the promise is suspended.

    • promise : return object, at the type level it defines the following properties:

      • pending: boolean, defines if the promise is executed but pending resolution.

      • fulfilled

    circle-info

    Note: When using Typescript Do not force the types for the second argument of usePromise, as usePromise will infer them from your callback.

    It is preferable to use ternary or if to condition the reading of states, since this will help the definition of result

    hashtag
    Example

    circle-info

    Note: At the type level, autocompletion is conditional on the state you are looking to observe.

    =>
    ())
    ;
    : boolean, defines if the promise has been fulfilled
  • result: result of the promise.

  • rejected: boolean, defines if the promise has been rejected.

  • import { usePromise } from "atomico";
    
    const getUsers = (id: number) => Promise.resolve({ id, name: "Atomico" });
    
    function component() {
      const promise = usePromise(getUsers, [1]);
    
      return (
        <host>
          {promise.fulfilled ? (
            <h1>Done {promise.result.name}!</h1>
          ) : promise.pending ? (
            <h1>Loading...</h1>
          ) : (
            <h1>Error!</h1>
          )}
        </host>
      );
    }

    atomico/test-hooks

    Build test to custom hooks created with Atomico with an isolated instance and predictable

    The "atomico/test-hooks" submodule is a direct part of Atomico's core and will allow you to run the customHook in a controlled way without the need for webcomponents.

    import { createHooks } from "atomico/test-hooks";

    hashtag
    createHooks

    function that creates an isolated context that is shared globally with Atomico hooks when executing the load method

    hashtag
    Instance

    Where:

    • optionalRender: Callback that allows restarting the hook's life cycle, this callback will be executed every time a hook like or requests the scope update, Atomico uses it to render the webcomponent again.

    • opcionalHost: allows to share an object for the hook , Atomico uses it to share the instance of the webcomponent.

    hashtag
    Return of the instance

    Where:

    • load: Function that allows to load the hooks to the temporary global context of Atomico.

    • clearEffect: Function that activates the collector of useLayoutEffect, when executing clear Effect a callback will be returned that allows ending the collector for useEffect, closing the cycle of secondary effects.

      If clear Effect receives true as parameter, it communicates the unmounted.

    hashtag
    Example:

    ./use-counter.js: customHook to test.

    ./use-counter.test.js: The example is based on the test environment from .

    useState
    useReducer
    useHost
    @web/test-runnerarrow-up-right
    const hooks = createHooks(opcionalRender, opcionalHost);
    interface Hooks {
      load<T>(callback: () => T): T;
      clearEffect(unmounted?: boolean): () => ()=> void;
    }
    import { useState } from "atomico";
    
    export function useCounter(initialValue) {
      const [value, setValue] = useState<number>(initialValue);
      return {
        value,
        increment: () => setValue((value) => value + 1),
        decrement: () => setValue((value) => value - 1),
      };
    }
    import { expect } from "@esm-bundle/chai";
    import { createHooks } from "atomico/test-hooks";
    import { useCounter } from "./use-counter.js";
    
    it("Check return", () => {
      const hooks = createHooks();
    
      const counter = hooks.load(() => useCounter(10));
    
      expect(counter.value).to.equal(10);
      expect(counter.increment).to.be.an.instanceOf(Function);
      expect(counter.decrement).to.be.an.instanceOf(Function);
    });

    Testing

    We recommend the @web/test-runnerarrow-up-right tool, if your project was created with the command npm init @atomico the test environment will be preconfigured, you can see this pre-configuration at https://github.com/atomicojs/base/tree/2-startedarrow-up-right.

    Render cyclechevron-rightatomico/test-domchevron-rightatomico/test-hookschevron-right

    hashtag
    Advanced (Documentation in progress)

    To scale the test environment you can complement with tools such as:

    1. : allows you to automate interactions and evaluate interactions of your interface.

    2. : Allows you to evaluate the appearance changes of your component or application between versions.

    Cypressarrow-up-right
    Percyarrow-up-right

    Frequent questions

    How to declare events for your component at the type level for TSX?

    Atomico makes it easy to declare events at the type level using the Host type.

    hashtag
    Example

    import { Host, c, useEvent } from "atomico";
    
    type CustomDetail = { timestamp: number };
    
    const MyComponent = c(
      (): Host<{ onMyCustomEvent: CustomEvent<CustomDetail> }> => {
        const dispatch = useEvent<CustomDetail>("MyCustomEvent");
        return (
          <host>
            <button
              onclick={() => {
                dispatch({ timestamp: Date.now() });
              }}
            >Click</button>
          </host>
        );
      }
    );
    
    
    

    In the previous code, we have declared the onMyCustomEvent event at the type level. According to the template logic, this event will be dispatched every time the button is clicked, and the detail attached to this event will be of type CustomDetail.

    Atomico with Typescript

    Atomico with Typescript will improve the scalability of your project thanks to a really productive type system when creating, distributing and maintaining webcomponents

    With Atomico and Typescript you will be able to:

    1. Construct the props parameter of your functional component.

    2. Check the correct declaration of your component.

    📌 If you are a Typescript user I recommend using TSX vs the template-string, as you will benefit from:

    1. JSX runtime, Typescript will automatically import Atomico upon detecting the use of TSX.

    2. Autocompletion and validation of attributes, properties and events of tags and webcomponents.

    3. Webcomponent instance validation using constructors.

    hashtag
    Construct the props parameter of your functional component.

    Your component as a function does not infer the props, to help infer the props you should use the Props type, example:

    From an object: Construct props directly from the declaring object.

    From the component: Build the props from the component, internally Atomico captures the property component.props.

    From another customElement component: It's unusal, but when you extend another component you can infer the props from its constructor.

    hashtag
    Check the correct declaration of your component.

    Typescript checks the structure of your component by using the c function on it, thus validating which props and styles have been attached.

    The return of c is a CustomElement with the types of the associated props, example:

    It will report an error to Typescript since this component's props define that value is of type number, this validation also applies when instantiating your CustromElement in JSX or TSX.

    Both TSX or JSX will help you to build better applications since it verifies the types according to the definition of its component, this verification is Free, it does not need plugins, only a tsconfig.json file and Typescript to automate the revision.

    Check the correct use of hooks.
    Declare meta-types to the component.
    import { Props } from "atomico";
    
    const props = {
      value: { type: Number, value: 0 },
    };
    
    function myComponent({ value }: Props<typeof props>) {
      return <host>{value * 10}</host>;
    }
    
    myComponent.props = props;
    import { Props } from "atomico";
    
    function myComponent({ value }: Props<typeof myComponent>) {
      return <host>{value * 10}</host>;
    }
    
    myComponent.props = {
      value: { type: Number, value: 0 },
    };
    import { Props, c } from "atomico";
    import { MyOtherComponent } from "./my-other-component";
    
    function myComponent({ value }: Props<typeof MyOtherComponent>) {
      return <host>{value * 10}</host>;
    }
    
    export const MyComponent = c(myComponent, MyOtherComponent);
    import { Props, c, css } from "atomico";
    
    function component(props: Props<typeof component>) {
      return <host>{props.value}</host>;
    }
    
    component.props = {
      value: Number,
    };
    
    component.styles = css`
      :host {
        color: tomato;
      }
    `;
    
    const MyComponent = c(component);
    
    customElements.define("my-component", MyComponent);
    const myComponent = new MyComponent();
    
    myComponent.value = "abc";
    <MyComponent value="abc"></MyComponent>

    Atomico and React

    hashtag
    React and webcomponents

    React has begun to support the use of webcomponents in an experimental wayarrow-up-right, this will allow to use custom tag with association of events and more, example:

    import React from "react";
    import "@formilk/components";
    
    export default function App() {
      return (
        <fm-button>Click!<fm-button>
      );
    }

    Advantages: The react team will take care of the coverage of this characteristic.

    Disadvantages: (Optional) The maintainer of the component must declare the types of the custom tag.

    Although the use of the custom tag is a way of instantiating the component, many times it does not define the import path at the time of its use, complicating the resolution of the component's origin, to avoid this we recommend the use of the Atomico wrapper for React.

    hashtag
    Atomico wrapper for React

    Atomico the package allows:

    1. Create a Wrapper component for the custom Element

    2. Avoid react conflicts with webcomponents, such as association of events, attributes, properties and children.

    3. Reflect the types declared in Atomic to React, valid for JSX or TSX

    Coverage is automatic if you decide to share your package using under the following

    hashtag
    Atomico inside Next.js

    All webcomponents work in Next if it escapes from SSR

    hashtag
    Example of custom tag usage

    hashtag
    Example of use of the wrapper.

    Remember if you use the auto module, it should always be imported first than the customElement to use, otherwise auto will generate an id as a custom tag to instantiate the component within react

    @atomic/react
    @atomico/exports
    export conditions.
    import  "@formilk/components/button";
    
    export default function Home() {
      return (
        <div>
          <fm-button name="Matias">
            <h1>Hello Next.js</h1>
          </fm-button>
        </div>
      );
    }
    import dynamic from "next/dynamic";
    
    const Button = dynamic(() => import("./wrapper-button.js"), {
      ssr: false,
    });
    
    export default function Home() {
      return (
        <div>
          <Button  name="Matias">
            <h1>Hello Next.js</h1>
          </Button>
        </div>
      );
    }
    import { auto } from "@atomico/react/auto";
    import { Button } from "@formilk/components/button";
    
    export default auto(Button);

    Atomico and Asynchrony

    With Atomico, asynchrony is really easy thanks to the fact that they will allow you to know the status of the process or suspend the rendering of the component.

    Atomico has 3 great hooks to solve asynchronous tasks:

    1. usePromise: Processes asynchronous tasks and shows the status and resolution of these

    2. useAsync: Allows you to pause rendering until a promise is resolved

    3. : Allows to know the paused states product of the use of useAsync nested in the component

    useSuspense

    Webcomponents with hybrid rendering

    Improve the interaction of your inputs with forms using hybrid rendering (LightDOM and ShadowDOM)

    Normally we create webcomponents that only work with lightDOM or shadowDOM, example:

    function componentWithLightDom() {
      return (
        <host>
          <h1>I am in the lightDOM</h1>
        </host>
      );
    }
    
    function componentWithShadowDom() {
      return (
        <host shadowDom>
          <h1>I am inside the shadowDOM</h1>
        </host>
      );
    }

    With atomico you can go further thanks to the hook @atomico/hooks/use-render which allows you to execute renders independent of the main one, example:

    import { useRender } from "@atomico/hooks/use-render";
    
    function componentWithLightDomAndShadowDom() {
      useRender(() => <h1>I am in the lightDOM</h1>);
      return (
        <host shadowDom>
          <h1>I am inside the shadowDOM</h1>
        </host>
      );
    }

    hashtag
    What are the benefits of using useRender?

    1. Encapsulate DOM fragments within custom Hooks and then render to any webcomponent.

    2. patch the limitations of webcomponents that use shadowDOM, to improve communication with forms.

    hashtag
    Patch the limitations of web components that use shadow DOM, to improve communication with forms

    With the useRender(...) fragment we are managing to render an input in the lightDOM, thanks to this we will be able to control said input from inside the webcomponent.

    hashtag
    Conclucion

    Thanks to useRender you will be able to work together with LightDOM and shadowDOM from the scope of the webcomponent created with Atomico.

    import { css, useProp } from "atomico";
    import { useRender } from "@atomico/hooks/use-render";
    
    function componentWithLightDomAndShadowDom(props) {
      const [value, setValue] = useProp("value");
    
      useRender(() => (
        <input {...props} oninput={({ target }) => setValue(target.value)} />
      ));
    
      return (
        <host shadowDom>
          <slot />
        </host>
      );
    }
    
    componentWithLightDomAndShadowDom.props = {
      type: {
        type: String,
        reflect: true,
        value: "text",
      },
      value: String,
    };
    
    componentWithLightDomAndShadowDom.styles = css`
      ::slotted([type="text"]) {
        border: 1px solid black;
      }
      ::slotted([type="checked"]) {
        width: 30px;
        height: 30px;
        border: 1px solid black;
      }
    `;

    Slot as templates

    hashtag
    Introduction

    Slots are a powerful feature when working with webcomponents that use the shadowDOM. And thanks to the hook @atomico/hooks/use-slot you will be able to observe the state of the slot and know the childNodes of this to manage the logic of the template. For example:

    import { useRef } from "atomico";
    import { useSlot } from "@atomico/hooks/use-slot";
    
    function component() {
      const refSlotIcon = useRef();
      const slotIcon = useSlot(refSlotIcon);
      return (
        <host shadowDom>
          <slot
            name="icon"
            ref={refSlotIcon}
            style={slotIcon.length ? null : "display: none"}
          />
          <slot />
        </host>
      );
    }

    Thus managing to easily condition parts of our template to define the slots.

    Another trick that useSlot allows us is the interaction at the DOM level, example:

    import { useRef, useEffect } from "atomico";
    import { useSlot } from "@atomico/hooks/use-slot";
    
    function component() {
      const refSlotSlides = useRef();
      const slotSlides = useSlot(refSlotSlides);
    
      slotSlides.forEach((slide) => {
        slide.style.width = "100%";
        slide.style.height = "300px";
      });
    
      return (
        <host shadowDom>
          <slot name="slides" />
        </host>
      );
    }

    We can even apply filters per instance for a better identification of the node, example:

    ⚠️ In case of high ui computation use prefer to use slot modifiers inside a useEffect.

    hashtag
    Slot pattern as a template

    Slots are not only limited to a static definition, you can use them to identify a node and use it to create new nodes according to the logic of the webcomponent, example:

    Thanks to useSlot(refSlotTemplateUser) we get the first node of the Template slot to be used in the iteration of the results obtained by fetch, with this we have eliminated the leverage of the webcomponent in charge of rendering the user at the level of our webcomponent userFetch

    hashtag
    Example

    ⚠️ the cloneNode property is available from version atomico@1.37.0

    useSlot(refSlotSlides).filter(
      (childNode) => childNode instanceof HTMLDivElement
    );
    import { useRef, usePromise } from "atomico";
    import { useSlot } from "@atomico/hooks/use-slot";
    
    function userFetch() {
      const refSlotTemplateUser = useRef();
      const [Template] = useSlot(refSlotTemplateUser);
      const promise = usePromise(
        () =>
          fetch("https://jsonplaceholder.typicode.com/users").then((res) =>
            res.json()
          ),
        [],
        true
      );
    
      return (
        <host shadowDom>
          <slot name="template" ref={refSlotTemplateUser} />
          <div class="list">
            {promise.fulfilled
              ? !!Template &&
                promise.result.map((props) => <Template {...props} cloneNode />)
              : "Pending..."}
          </div>
        </host>
      );
    }

    Frequent questions

    hashtag
    How to render a webcomponent in Storybook with React

    The following error is the most common when using Atomico in Storybook with React.

    localhost/:1 Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': this constructor has already been used with this registry

    We recommend using @atomico/react, this package creates a friendly wrapper for React.

    circle-info

    Using requires the following script to be imported into the .storybook/preview.js file

    import "@atomico/react/proxy";
    @atomico/react

    Component

    Working on this documentation...

    Slot

    The use of slots improves the composition allowing to reflect the content exposed in the lightDOM of the component in the shadowDOM of this, example:

    circle-info

    The use of slot is thanks to the use of the ShadowDOM api, to make use of the shadowDom you must declare it as a property of the host tag, example:

    <host shadowDom>I exist inside the shadowDOM</host>

    hashtag
    ::slotted(<selector>)

    The slotted selector allows the manipulation of the content exposed as a lightDOM slot from the shadowDOM, example:

    hashtag
    Auto slot

    With Atomico you can define a default slot for your components, this is useful if you want to maintain some compositional consistency without the need to declare the use of slot, example:

    To define a default slot, you only have to declare slot in the props, example:

    Consider this practical only if the composition is leveraged to the container, this does not prevent you from modifying the slot property from the component instance.

    hashtag
    Conditional slot

    Atomico has the hook that appends the slotchange event to a reference, this will allow you to hide slots if they do not declare content, example:

    <my-component>
        <my-component-header></my-component-header>
        <my-component-footer></my-component-footer>
    </my-component>
    myComponentHeader.props = {
        slot: { type: String, value: "header" }
    }
    
    myComponentFooter.props = {
        slot: { type: String, value: "footer" }
    }
    import { useRef } from "atomico";
    import { useSlot } from "@atomico/hooks/use-slot";
    
    function component() {
        const ref = useRef();
        const childNodes = useSlot(ref);
        return <host shadowDom>
            <header style={{
                display: childNodes.length? "block": "none"
            }}>
                <slot name="header" ref={ref} />
            </header>
        </host>
    }
    @atomico/hooks/use-slot

    Archives

    SSR / SSG

    Implement SSR and SST without friction

    hashtag
    Node

    Atomico and automatic SSR support, you will not need additional packages, example:

    // If your version of node accepts top-level await just point to 'atomico/ssr'
    import 'atomico/ssr/load'; 
    

    For this case we are using a JS-only component to avoid compilation in the example. you can achieve the same with JSX or TSX.

    You will not need any additional package, atomic internally understands that the component must be hydrated

    Unlike other libraries, Atomico automatically hydrates when mounting the component in the DOM, so no additional client configuration is required.

    circle-info

    To use SSR in Node without tools like Next.js, Fresh or Astro you should import the path atomico/ssr/load from the server

    hashtag
    SSR/SSG via @atomico/react

    The @atomico/react package allows you to take advantage of support for SSR or SSG designed for React/Preact, example:

    1. Next.js:

      • Example:

      • Repo:

    hashtag
    Astro build

    allows to integrate SSR and SSG support to Astro build, We have used this integration to create the site.

    Forms and shadowDOM

    Improve the interaction with the forms and accessibility of your components.

    It is normal that we use the technique of dispatching events from the component to communicate states and more, but when using forms this changes since the events inside the shadowDOM do not interact with the form outside of it, example:

    <form>
        <my-input>
            #shador-root
                <input/>
        </my-input>
    </form>

    To solve this we will have to nest an input tag in the lightDOM of our component, in order to reflect the logic of this to the form. Atomico facilitates this hybrid interaction between lightDOM and shadowDOM with the @atomico/hooks/use-render hook that allows executing a second render that works from the lightDOM, example:

    import { useRender } from "@atomico/hooks/use-render";
    
    function myInput(){
        // render LightDOM
        useRender(()=><input/>);
        
        // render ShadowDOM
        return <host shadowDom>
            <slot/>
        </host>
    }

    With this you have gained full control over the existing input tag in the lightDOM, allowing you to apply styles using the ::slotted selector, example:

    myInput.styles = css`
        :slotted(input){
            border: 1px solid black;
            height: 40px;
        }
    `;

    We have created an input tag that allows you to interact directly with the form, this technique is applicable with all the tags that interact with the form, such as button, input, textarea and others.

    Fresh:

    • Example: https://atomico-webcomponents-tgd58wd8ntcg.deno.dev/arrow-up-right

    • Repo: https://github.com/atomicojs/example-fresh-denoarrow-up-right

    import { html } from 'atomico';
    import { Component } from './static/component.js';
    import express from 'express';
    const app = express();
    const port = 3010;
    app.use(express.static('static'));
    app.get('/', (req, res) => {
    res.send(`
    <script type="importmap">
    {
    "imports": {
    "atomico": "https://unpkg.com/atomico"
    }
    }
    </script>
    <script src="./component.js" type="module"></script>
    ${html`<${Component} value=${100}>
    <h1>Message from server!</h1>
    </${Component}>`.render()}
    `);
    });
    app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`);
    });
    import { c, html, css, useProp } from 'atomico';
    
    function component() {
      const [value, setValue] = useProp('value');
      return html`<host shadowDom>
        <h1>Atomico webcomponent</h1>    
        <button onclick=${() => setValue(value + 1)}>${value} Increment</button>
        <slot/>
      </host>`;
    }
    
    component.props = {
      value: { type: Number, value: 0 },
    };
    
    component.styles = css`
      :host{
        font-size: 32px;
        font-family: arial;
      }
    `;
    
    export const Component = c(component);
    
    customElements.define('my-element', Component);
    
    {
      "name": "node-starter",
      "type": "module",
      "version": "0.0.0",
      "private": true,
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "@atomico/site": "^0.1.0",
        "atomico": "^1.61.1",
        "express": "^4.17.1"
      }
    }
    
    https://example-next-js-mocha.vercel.app/arrow-up-right
    https://github.com/atomicojs/example-next-jsarrow-up-right
    @atomico/astroarrow-up-right
    atomicojs.devarrow-up-right

    Atomico design patterns

    Atomico has been designed to be simple even in complex situations, in this guide you will know some patterns that Atomico offers to create webcomponents at an advanced level.

    Atomico and Storybook

    hashtag
    Status

    hashtag
    @Atomico/storybook

    hashtag
    decorador

    Easily render webcomponents created with Atomico inside storytbook stories.

    .storybook/preview.js

    hashtag
    define

    It facilitates the creation of stories, analyzing the components created with Atomico to automatically define the argTypes and args.

    define also improves the declaration of stories by improving the autocompletion of these.

    hashtag
    @atomico/vite

    @atomico/vite has exclusive storybook utilities, with these it seeks to facilitate the use of Atomico's JSX /TSX and live reload.

    hashtag
    Config storybook 7

    configuration on Githubarrow-up-right
    @atomico/storybookchevron-right
    Frequent questionschevron-right
    import { decorator } from "@atomico/storybook";
    
    export const decorators = [decorator()];
    import { define } from "@atomico/storybook";
    import { ColorPicker } from "./color-picker";
    
    export default {
      title: "components/brand",
      ...define(ColorPicker)
    };
    
    export const Default = (props: any) => <ColorPicker {...props}></ColorPicker>;
    import { mergeConfig } from "vite";
    
    export default {
        stories: [
            // 📌 remember to define the path according to your configuration
            "../../components/**/*.stories.mdx",
            "../../components/**/*.stories.@(js|jsx|ts|tsx)",
            "../../components/*.stories.mdx",
            "../../components/*.stories.@(js|jsx|ts|tsx)",
        ],
        addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
        framework: {
            name: "@storybook/web-components-vite",
            options: {},
        },
        async viteFinal(config, { configType }) {
            // 📌 return the customized config
            return mergeConfig(config, {
                plugins: [
                    ...(await import("@atomico/vite")).default({
                        // 📌 needed to define files that use JSX/TSX
                        storybook: ["components/**/*"],
                    }),
                ],
            });
        },
    };
    v
    {
      "type": "module",
      "scripts": {
        "storybook": "storybook dev -p 6006",
        "build-storybook": "storybook build"
      },
      "dependencies": {
        "atomico": "^1.68.0"
      },
      "devDependencies": {
        "@atomico/storybook": "^1.5.0",
        "@atomico/tsconfig": "^1.1.2",
        "@atomico/vite": "^2.3.2",
        "@storybook/addon-actions": "^7.0.0-alpha.47",
        "@storybook/addon-essentials": "^7.0.0-alpha.47",
        "@storybook/addon-links": "^7.0.0-alpha.47",
        "@storybook/web-components": "^7.0.0-alpha.47",
        "@storybook/web-components-vite": "^7.0.0-alpha.47",
        "lit-html": "^2.4.0",
        "storybook": "^7.0.0-alpha.47",
        "vite": "^3.2.2"
      }
    }

    Design systems

    I will show you a series of useful techniques to start programming your design systems with Atomico, analyzing the recommended structure and its files.

    hashtag
    Recommended structure

    src/
    	# Import, export and declare all our components
    	components.js 
      # Group all the tokens in our system
    	tokens.js
    	# Structure example for our component
    	/button
    		button.{js,jsx,ts,tsx}
    		button.md
    		button.test.js

    We will analyze the above.

    hashtag
    src/components.js

    File that imports, exports and declares all the components of our design system, example:

    the utilities of this are to centralize everything in components.js are:

    1. Clarity of the definition of customElements in a single file for our entire design system

    2. Export of all customElements to be extended or redefined.

    hashtag
    src/tokens.js

    File that centralizes the custom-properties of our design system, example:

    From the example above I highlight the custom property declaration pattern

    --background will be a token that can be modified at the instance level and this inherits a global token from our system called --my-ds-input--background, I want you to notice that the global name of our custom property has a pattern, example:

    Where:

    1. prefix: Prefix of our global design system

    2. namespace: group independent of our design system

    3. property: property associated with the system, such as color, size, or other value.

    Why use the recommended pattern? To individualize the configuration at the group level and separate the property definition from it thanks to the use of double hyphens (--), internally everything is simplified since the tokens only capture the global configuration global to reflect it to a simpler variable accessible only from the component instance level.

    hashtag
    Instance level

    It is the instance of the component either in HTML or JS, example:

    hashtag
    src/button.js

    From the previous code I highlight:

    1. import of "../tokens" and the destructuring of the module that declares the use of tokensColor and tokensInput.

    2. button.styles: Atomico allows to associate multiple styles through the use of an array.

    Button export.
    import { Button } from "./button/button";
    export { Button } from "./button/button";
    
    customElements.define("my-button", Button);
    import { css } from "atomico";
    
    export const tokensInput = css`
      :host {
        --background: var(--my-ds-input--background, #fff);
        --border-width: var(--my-ds-input--border-width, 1px);
        --border-color: var(--my-ds-input--border-color, black);
        --radius: var(--my-ds-input--radius, 0.5rem);
        --min-height: var(--my-ds-input--min-height, 40px);
      }
    `;
    
    export const tokenColors = css`
      :host {
        --primary: var(--my-ds--primary);
        --secondary: var(--my-ds--secondary);
        --success: var(--my-ds--warning);
        --warning: var(--my-ds--warning);
        --danger: var(--my-ds--warning);
        --info: var(--my-ds--warning);
      }
    `;
    :host {
        --background: var(--my-ds-input--background, #fff);
    }
    ---my-ds-input--background
    ---<prefix>-<namespace>--<property>
    <my-component style="--background: red;"></my-component>;
    const component = document.createElement("my-component");
    
    component.style = "--background: red";
    import { c, html, css } from "atomico";
    import { tokensColor, tokensInput } from "../tokens";
    
    function button(props) {
    
      return html`<host shadowDom>
        <button ...${props} class="input-box">
          <slot name="icon"></slot>
          <slot></slot>
        </button>
      </host>`;
    }
    
    button.props = {
      name: String,
      value: String,
      disabled: Boolean,
    };
    
    button.styles = [
      tokensColor,
      tokensInput,
      css`
        .input-box {
          display: flex;
          gap: 1rem;
        }
      `,
    ];
    import { c, css } from "atomico";
    import { tokensColor, tokensInput } from "../tokens";
    
    function button(props) {
      return (
        <host shadowDom>
          <button {...props} class="input-box input-box--use-border">
            <slot name="icon"></slot>
            <slot></slot>
          </button>
        </host>
      );
    }
    
    button.props = {
      name: String,
      value: String,
      disabled: Boolean,
    };
    
    button.styles = [
      tokensColor,
      tokensInput,
      css`
        .input-box {
          display: flex;
          gap: 1rem;
        }
      `,
    ];
    
    export const Button = c(button);

    File structure

    Atomico style guide

    First thanks for using Atomico 😉, in this guide you will find some useful tips when developing with Atomico, all with the aim that your webcomponents are sustainable and scalable over time.

    @atomico/hooks

    series of useful hooks for creating webcomponents

    Hooks
    v3
    v4

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ✅

    ⚠️

    use-current-value

    ✅

    use-slot
    use-render
    use-child-nodes
    use-click-coordinates
    use-copy
    use-css-light-dom
    use-ref-values

    useCurrentValue

    The useCurrentValue hook allows associating a value with current based on its parameter, equivalent to:

    const ref = useRef();
    
    ref.current = value;

    With the useCurrentValue hook

    useCurrentValue(value)

    Design systems

    hashtag
    Distribution and consumption

    When creating design systems or component systems, we recommend that you maintain a centralized distribution structure in a single export package with multiple import paths, example:

    import { Button, Input } from "
    import { Button } from "my-ds/button";
    

    hashtag
    What are the benefits of this type of distribution?

    Aesthetic coherence, the entire design system leverages itself and thanks to this we gain aesthetic coherence.

    While a monorepo might seem like an ideal path, it actually increases the complexity of maintenance, whether it be component versioning and dependency maintenance.

    We move faster and reduce implementation errors if you don't rely on individual versioning at the component level and leave this only defined at the design system level.

    This has its pros and cons:

    Pros:

    1. It speeds up the creation of components, since it avoids the individual publication process and centralizes all this at the design system level.

    Cons

    1. you will not be able to update a component individually, since it is leveraged at the design system level.

    hashtag
    Recommended file structure for webcomponents

    you always prefer to keep each component isolated in a directory with names associative to the same component, example:

    why? The NPM-oriented packaging tool allows automatic export from the recommended structure,@atomico/exports will generate a modern package.json to current standards, automatically generating all (main, types, exports and more) what is necessary for your package to be distributed correctly.

    my-ds
    "
    ;
    import { Input } from "my-ds/input";
    @atomico/exports
    ├── my-button
    │   ├── my-button.{js,jsx,ts,tsx}
    │   ├── my-button.test.{js,jsx,ts,tsx}
    │   ├── my-button.stories.js
    │   └── my-button.md
    └── my-input
        ├── my-input.{js,jsx,ts,tsx}
        ├── my-input.test.{js,jsx,ts,tsx}
        ├── my-input.stories.js
        └── my-input.md

    use-intersection-observer

    Create an IntersectionObserver instance to observe the intersection of the webcomponent or a reference.

    Remember that to detect any intercept, a minimum height and width of the reference to be observed is required.

    hashtag
    useIntersectionObserver

    import { useIntersectionObserver } from "@atomico/hooks/use-intersection-observer";
    
    function component(){
        useIntersectionObserver(([entry])=>{
            console.log("",{isIntersecting })
        },{
              threshold: 0.1,
        })
        return <host shadowDom/>
    }
    
    component.styles = css`
        :host{
            display: block;
            width: 100%;
            min-height: 1px;
        }
    `

    hashtag
    useRefIntersectionObserver

    import { useHost } from "atomico";
    import { useRefIntersectionObserver } from "@atomico/hooks/use-intersection-observer";
    
    function component(){
        const host = useHost();
        useRefIntersectionObserver(
            ref,
            ([entry])=>{
                console.log("",{isIntersecting })
            },{
              threshold: 0.1,
            }
        );
        return <host shadowDom/>
    }
    
    component.styles = css`
        :host{
            display: block;
            width: 100%;
            min-height: 1px;
        }
    `

    use-ref-values

    Consume the values of a reference without major inconvenience

    This hook has a behavior similar to useEffect but focused on resolving the consumption of one or more references.

    hashtag
    Example

    import { useRef } from "atomico";
    import { useRefValues } from "@atomico/hooks/use-ref-values";
    
    function component(){
        const ref = useRef();
        
        useRefValues(
            // 👇 current will be the value assigned to ref.current
            ([current])=>{
                // 1️⃣ here the effect dependent on the reference
                // 🔴 The following line is optional
                return ()=>{
                // (Optional) here the cleanup of the reference dependent effect
                }
            },
            [ref]
        );
        
        return <host>
            <input ref={ref}/>
        </host>
    }

    use-script

    load global scripts when mounting the component

    hashtag
    Syntax

    const src = "https://code.jquery.com/jquery-3.6.0.min.js";
    const done = ()=> console.log( $ );
    const status = useScript(src, done);

    where:

    1. src: javascript type resource to inject into document.head

    2. done: optional , callback when called at the end of the script load

    3. status: script load status

    use-attributes

    capture all attributes that are defined in the webcomponent but are not props

    hashtag
    Syntax

    const attrs = useAttributes();

    Where:

    1. attrs: all the attributes defined on the webcomponent but that are not props, to facilitate reading this hook transforms the index to camelCase

    hashtag
    Example

    use-prop-proxy

    Share values from the scope via setter and getters

    allows to create a setter and getter for a property of the webcomponent, this is useful for:

    1. Reflect elements internal to the scope as part of the instance, example references.

    2. Observe from the scope the mutations external to Atomico.

    hashtag
    Example

    import { Props, useRef } from "atomico";
    import { useRender } from "@atomico/hooks/use-render";
    import { usePropProxy } from "@atomico/hooks/use-prop-proxy";
    
    function component(props: Props<typeof component>) {
      const refInput = useRef();
    
      useRender(() => (
        <input
          slot="input"
          class="reset"
          ref={refInput}
          type={props.type}
          value={props.value}
        />
      ));
    
      usePropProxy("value", {
        get() {
          return refInput.current?.value;
        },
      });
    
      return (
        <host shadowDom>
          <slot name="input"></slot>
        </host>
      );
    }
    
    component.props = {
      type: String,
      value: String,
    };
    

    Monorepo

    The following format is friendly when sharing a webcomponent to NPM using @atomico/exports

    ├── src
    │   ├── define.{js,ts,jsx,tsx}
    │   ├── elements.{js,ts,jsx,tsx}
    │   ├── define.test.{js,ts,jsx,tsx}
    │   └── slots
    │       ├── my-sub-element-1.{js,jsx,ts,tsx}
    │       └── my-sub-element-2.{js,jsx,ts,tsx}
    ├── README.md
    ├── index.html
    ├── .npmignore
    ├── package.json
    └── tsconfig.json
        

    From the previous structure we highlight:

    1. src/define: import the components from src/elements and declare them as customElements

    2. src/elements: groups and export components as CustomElements

    hashtag
    Why separate the export of the element from the declaration?

    since it improves the customElements definition experience, example:

    Thanks to @atomico/exports this is really easy, example:

    You can see a use case of this structure in @atomico/components

    import "my-component"; // internalmente define el customTag
    import { MyElement } from "my-component/elements"; // no define el customTag
    exports src/{define,elements}.{ts,tsx} --exports --types

    use-keyboar

    capture key combinations easily

    hashtag
    Module

    import { useKeyboard } from "@atomico/hooks/use-keyboard";

    hashtag
    Syntax

    useKeyboard(
        ref: Ref<Element>,
        keysCode: string[],
        callback: (event: KeyboardEvent)=>void
    );

    where:

    1. ref, the reference to associate the event keydown and keyup.

    2. keysCode: key combination to capture

    3. callback: receives the last event of the key combination

    hashtag
    Example

    use-reflect-event

    the useReflectEvent hook reflects the event from the reference origin to the reference destination

    the useReflectEvent hook reflects the event from the reference origin to the reference destination, the captured event will be canceled

    One of the possibilities of this hook is to reflect the events inside shadowDOM to lightDOM, example when using forms

    hashtag
    module

    import { 
        useReflectEvent, 
        reflectEvent  
    } from "@atomico/hooks/use-reflect-event";

    hashtag
    Syntax useReflectEvent

    useRefectEvent will listen for the eventType of refFrom, to reflect it on refTo

    hashtag
    Syntax reflectEvent

    Reflects on an element the given event

    useReflectEvent(
        refFrom: Ref<Element>,
        refTo: Ref<Element>,
        eventType: string
    );
    reflectEvent( target: Element, event: Event );

    use-click-press

    The useClickPress hook will allow you to execute a callback with acceleration according to the click time, for example in the input type number we have 2 buttons by default, an up arrow and a down arrow, these allow us to modify the input value, either:

    1. Increase the value before a click in a unit.

    2. Increase the value by more than one unit according to the click pressure time.

    hashtag
    Example

    hashtag
    Live example

    import { useClickPress } from "@atomico/hooks/use-click-press";
    
    function counter() {
      const refButton = useRef();
    
      const [value, setValue] = useProp("value");
    
      const increment = () => setValue((value) => value + 1);
    
      useClickPress(refButton, increment);
    
      return (
        <host>
          <h1>value: {value}</h1>
          <button ref={refButton}>Increment</button>
        </host>
      );
    }
    
    counter.props = { value: { type: Number, value: 0 } };j

    use-dollars

    Create reactive templates that interact with the state of your webcomponent

    The purpose of this hook is to allow communication between the webcomponent and the lightDOM without knowing the DOM, achieving with this hook:

    1. expose the webcomponent's api to the lightDOM and react to it.

    2. data binding between the state of the webcomponent and the lightDOM.

    hashtag
    module

    hashtag
    Syntax

    hashtag
    Example

    import { useDollars } from "@atomico/hooks/use-dollars";
    useDollars(ref: Ref<HTMLSlotElement>);

    use-listener

    Associate a listener with a reference

    hashtag
    Module

    import { useListener } from "@atomico/hooks/use-listener";

    hashtag
    Syntax

    useListener(
        ref: Ref<Element>, 
        name: string, 
        handler: (event)=>void, 
        option?: boolean | AddEventListenerOptions 
    );

    hashtag
    Example

    use-form

    Communicate the component with external forms.

    hashtag
    Module

    import { 
        useForm, 
        useFormListener, 
        useFormInputHidden, 
        useFormInputRadio 
    } from "@atomico/hooks/use-form";

    hashtag
    useForm syntax

    If the webcomponent is nested within a form tag, this hook will return that form tag as a reference.

    hashtag
    useFormListener syntax

    If the webcomponent is nested within a form tag, you will be able to listen to events from that tag through useFormListener.

    hashtag
    useFormInputHidden syntax

    el hook renderiza un input hidden en el lightDOM cuyo name y value del input seran los parametros que el hook resiva

    hashtag
    useFormInputRadio

    One of the difficult inputs to standardize when working with shadowDOM is the radio input, thanks to this hook you will be able to create and observe a radio input synchronized with the form and its webcomponent.

    This hook requires the definition of the properties in its webcomponent:

    You can work with the input from the component logic, example:

    Your component will automatically be reactive to the change of the states of the radio input

    hashtag
    Example

    const ref = useForm()
    useFormListener("reset",handler);
    useFormInputHidden("my-field","my-value");
    myComponent.props = { name: String, checked: Boolean }
    const refInput = useFormInputRadio(<input slot="input" value="my-value"/>);
    
    return <host>
        <slot name="input"/>
    </host>;

    use-click-coordinates

    The usesClickCoordinates hook is for capturing click coordinates, this is useful when positioning a tooptip or creating visual effects

    hashtag
    Module

    import { useClickCoordinates } from "@atomico/hooks/use-click-coordinates";

    hashtag
    Syntax

    useClickCoordinates(ref, handlerClick);

    where:

    1. ref: node reference to observe the click event.

    2. handlerClick: Callback that receives the coordinates of the click event.

    hashtag
    Coordinates

    Where :

    • x: MouseEvent.clientX

    • y: MouseEvent.clientY

    • offset.x : MouseEvent.offsetX

    hashtag
    Example

    use-copy

    Copies the content in text format of a reference.

    hashtag
    Module

    hashtag
    Syntax

    Where:

    offset.Y : MouseEvent.offsetY
    interface Coordinates {
      x: number;
      y: number;
      offset: {
        x: number;
        y: number;
      };
    }
  • ref : Node reference to copy

  • copy: Callback, executes the content copy event.

  • hashtag
    Example

    import { useCopy } from "@atomico/hooks/use-copy";
    const copy:()=>void = useCopy(ref);

    use-disabled

    Synchronize the state of the disabled prop with the fieldset tag

    Inherit the disabled status of a parent tag type fieldset, under certain rules:

    1. The label must be on the lightDOM.

    2. The component that uses this hook must declare the prop {disabled: Boolean}.

    hashtag
    Import

    hashtag
    Sintaxis

    Where:

    1. matches: Optional string, allows to change the search of the parent tag fieldset for another tag or selector compatible with .

    hashtag
    Example

    import { useDisabled } from "@atomico/hooks/use-disabled";
    const disabled:boolean = useDisabled(matches?: string = "fieldset");
    Element.matchesarrow-up-right

    use-css

    Inject CSS into the shadowRoot

    Inject tag style into shadowRoot with content given as parameter to use CSS.

    hashtag
    Import

    import { useCss } from "@atomico/hooks/use-css";

    hashtag
    Sintaxis

    hashtag
    Example

    hashtag
    Note

    This hook was not created as a replacement for component.styles, it is rather a utility that seeks to facilitate the integration of css from a customHook, either by defining a state or another action.

    useCss(cssText: string);

    use-responsive-state

    Declare a state based on a responsive expression similar to using the tag img[srcset].

    hashtag
    Module

    import { useResponsiveState } from "@atomico/hooks/use-responsive-state";

    hashtag
    Syntax

    const expression = "phone, tablet 720px, destop 1080px";
    const

    Where:

    • state: String, Current state according to the comparison between experiment and matchMedia.

    • expression: String, An expression that declares the serialized states.

    hashtag
    Expressions

    Where:

    • defaultState: Default state this cannot contain the use of commas ", ".

    • caseState: Status to show if matchMedia match.

    hashtag
    Example

    The following example shows the use of useResponsiveState, to display a message based on the mediaquery.

    size: size expression to observe, example:
    • "1080px": (min-width: 1080px)

    • "1080x720px": (min-width: 1080px) and (min-height: 720px)

    • "50rem": (min-width: 50rem)

    • "50em": (min-width: 50em)

    state
    =
    useResponsiveState
    (
    expression
    )
    ;
    "<defaultState>, <caseState> <size>"
    const state = useResponsiveState`
        phone, tablet 720px, destop 1080px
    `;

    use-promise

    The usePromise hook consumes an asynchronous function is ideal for using fetch or other asynchronous tasks.

    hashtag
    Module

    import { usePromise } from "@atomico/hooks/use-promise";

    hashtag
    Syntax

    const [result, status] = usePromise(
      asyncFunction,
      runFunction,
      optionalArguments
    );

    Where :

    • result: Retorno de la promesa

    • status: Estado de la promesa:

      • ""

    hashtag
    Example

    : Without executing.
  • "pending": In action.

  • "fulfilled": Successfully executed.

  • "rejected": Executed with error.

  • asyncFunction: asynchronous function.

  • runFunction: Booleano, if true it will execute the promise and define the status.

  • optionalArguments: Optional any[], allows to regenerate the promise through arguments.

  • use-parent

    Retrieve a node higher than the current webcomponent.

    hashtag
    Module

    import { useParent, useParentPath } from "@atomico/hooks/use-parent";

    hashtag
    Syntax useParent

    const selector = "form";
    const parent = useParent(selector);

    Where:

    • selector: String, Selector to be used by when searching for the parent.

    • parent: Element, ascending search result according to selector.

    hashtag
    Syntax useParentPath

    Where:

    • parents: parent nodes of the webcomponent

    • composed: bypasses shadow DOM in parent capture.

    hashtag
    Example

    const parents = useParentPath(composed?: boolean);
    Element.matchesarrow-up-right

    You can create mobile applications

    working on this documentation...

    You can create web applications

    working on this documentation...

    You can create websites

    Creating sites with Atomico is really easy and SEO friendly because:

    1. With Atomico you can perform SSR and SSG thanks to tools like Astro build, with Astro + Atomico you can send previously rendered components to the client, thus giving a result at the HTML level that is really friendly with search engines.

    2. Atomico being really small (3kB) your sites will load fast, especially if you only apply SSG with Atomico.

    3. Atomico not only supports SSR through Astro, you can SSR today with Atomico in Next.js, Express or any environment that supports ESM modules.

    4. (Coming soon) Yes, with Atomico soon you will be able to create blocks for Gutenberg easily

    hashtag
    We recommend for SSR or SSG based sites

    hashtag
    we recommend you for new projects

    We recommend the use of build with the @atomico/astro plugin, with this you can create sites like

    hashtag
    we recommend you for projects based on React

    Preferably we recommend + React + Atomico, but in case your project inherits the use of Next.js you can do SSR with Atomico in Next.js using .

    You can create amazing webcomponents

    Atomico makes it easy to build components with less code, better readability, and better reusability.

    We invite you to discover part of the development experience you will get with Atomico:

    hashtag
    Create really fast webcomponents

    Quick components to write since with Atomico you will require fewer lines of code to declare your webcomponents which will help you to be more productive

    Fast in performance, since Atomico sends less code to the client, making your interface load quickly

    Astroarrow-up-right
    atomicojs.devarrow-up-right
    Astroarrow-up-right
    @atomico/react

    hashtag
    Create web components with less code

    This is thanks to a functional orientation inherited from React hooks plus some internal optimization from Atomic that ease the process of shaking the tree at compile time, achieving in this way sending the client a highly optimized JS that only has what you really use

    hashtag
    Create webcomponents with a functional orientation

    This is thanks to Atomico's reliance on React hooks syntax plus the ability to completely eliminate the need for this when using webcomponents.

    hashtag
    Create friendly webcomponents for React, Vue and other libraries

    Atomic offers additional coverage for native behavior for React and Vue, allowing your component to be more embed-friendly, example React:

    react-app.tsx
    import { Button } from "@formas/button/react";
    
    function App(){
       return <>
          <h1>React App!</h1>
          <Button onClick={()=>console.log("Click!")}>
             Submit
          </Button>
       </>
    }

    use-debounce-state

    creates a bottleneck to the definition of a state, limits concurrency.

    hashtag
    Module

    import { useDebounceState } from "@atomico/hooks/use-debounce-state";

    hashtag
    Syntax

    this hook is similar to useState, but the purpose of this hook is to bottleneck the state at update time

    mode differences

    1. fps:

      1. if delay is set to 1, the update is executed for each cycle of requestAnimationFrame(60fps),

      2. if delay is defined as 2, the update is executed for every 2 cycle of requestAnimationFrame(30fps)

    hashtag
    Example

    Value cycle as prop

    Atomico has a really efficient and simple type validation method, the type validation works in the following way:

    hashtag
    Cycle as attribute:

    the given value is transformed to the corresponding type, be it String, Number, Boolean, Array or Object, once transformed it is sent to the cycle as property.

    hashtag
    Cycle as property:

    evaluates if the value is of the declared type:

    • If it corresponds to the type:

      1. It is saved in props.

      2. An event is emitted (if this has been configured in the prop).

    timeout: the delay will be the milliseconds for setTimeout

  • idle : the delay will be the milliseconds for requestIdleCallback

  • const [state, setState] = useDebounceState(
        delay:number, 
        initialState: any,
        mode?: "fps" | "timeout" | "idle"
    );
    It is reflected as an attribute (if this has been configured in the prop).
  • It is sent to the update queue and subsequent rendering.

  • It does not correspond to the type: an error is created by console with the following data:

    • target: Instance of the webcomponent.

    • value: Input value.

    • type: expected type.

  • use-resize-observer

    Observe the size change of a reference.

    hashtag
    Module

    import {
      useResizeObserver,
      useResizeObserverState,
    } from "@atomico/hooks/use-resize-observer";

    hashtag
    Syntax

    hashtag
    useResizeObserver

    Where:

    • ref: Ref, reference to observe the resizing.

    • rect: Object, the return of DOMRectReadOnly.toJSON(), documentation of

    hashtag
    useResizeObserverState

    Where:

    • ref: Ref, reference to observe the resizing.

    • rect: Object, the return of DOMRectReadOnly.toJSON(), documentation of

    hashtag
    Example

    width

  • height

  • x

  • y

  • top

  • right

  • bottom

  • left

  • width

  • height

  • x

  • y

  • top

  • right

  • bottom

  • left

  • useResizeObserver(
      ref,
      (rect) => void
    );
    const rect = useResizeObserverState(ref);
    DOMRectarrow-up-right
    DOMRectarrow-up-right

    use-channel

    create connection between components to share internal states

    Now, Atomico includes a context API as part of its core. We recommend implementing it as an alternative to using useChannel.

    useContextchevron-right

    An alternative to React's context but solely based on hooks.

    hashtag
    Modulo

    import { useChannel } from "@atomico/hooks/use-channel";

    hashtag
    Syntax

    Where :

    1. channel: String, defines the name of the event to be used to generate the channel.

    2. parentValue: Value inherited by the parent component.

    hashtag
    Example

    This hook is used by

    setChildValue
    :
    Callback
    , defines a value for nested components.
    const channel = "MyChannel";
    const [parentValue, setChildValue] = useChannel(channel);
    @atomico/components/router

    atomico/test-dom

    hashtag
    fixture

    function that allows to keep the render on a single node for each execution of it, the container will be unmounted at the end of each execution of it.

    import { html } from "atomico";
    import { expect } from "@esm-bundle/chai";
    import { fixture } from "atomico/test-dom";
    import { Component } from "./component.js";
    
    describe("my test", () => {
      it("my-component", async () => {
        const component = fixture(html`<${Component}>
            <span>content...</span>
        </${Component}>`);
    
        await component.updated;
    
        /** Test logic */
      });
    });

    This allows each fixture execution inside it to be related to the container used for the test, allowing you to fily test external DOM changes, example:

    import { html } from "atomico";
    import { expect } from "@esm-bundle/chai";
    import { fixture } from "atomico/test-dom";
    import { Component } from "./component.js";
    
    
    describe("my test", () => {
      it("my-component", async () => {
        // first instance of the render, it will return the component
        const component = fixture(html`<${Component}>
            <span>content...</span>
        </${Component}>`);
    
        await component.updated;
        
        // updates the content of the span tag of the first instance of the render
        fixture(html`<${Component}>
            <span>new content...</span>
        </${Component}>`);
        
        await component.updated;
    
        expect(
              component.querySelector("span").textContent
        ).to.equal("new content...");
      });
    });

    Render cycle

    Atomico optimizes the execution of your script by minimizing resources through the rendering control.

    Atomico's render cycle works like this:

    First, when the component is instantiated, 3 promises will be created pending resolution:

    1. componentInstance.mounted: resolved once the connectedCallback has been executed by the customElement.

    2. componentInstance.updated: the render cycle for first mount or update is encapsulated in componentInstance.updated.

    3. componentInstance.unmounted: resolved once the disconnectedCallback has been executed by the customElement.

    Render or rerender cases are:

    1. First render.

    2. Updating a property or attribute.

    3. Update dispatched by a hook

    Remember all updates are stacked into a single big loop of componentInstance.updated.

    This improves the testing experience since to observe the changes you only have to wait for the resolution of componentInstance.updated , example:

    hashtag
    Webcomponent as function

    The first rendering as the update of a prop will call to execute again the function that defines our component, Atomico internally stores the references to support the Hooks api and will render the virtualDOM returned by said function, this is encapsulated within componentInstance.updated

    hashtag
    SSR

    Atomico allows modifying its life cycle in cases of SSR, improving the rendering of the CSS in case of SSR and avoiding the effects of useEffect and useLayoutEffect

    hashtag
    Hydration

    Technique to reuse the DOM generated by the SSR, Atomico only in the first render of the component that declares date-hydrate will retrieve the existing DOM in the DOM not created by Atomico to relate it to the virtualDOM, avoiding creating the node again.

    hashtag
    Optimization

    Atomico by default has a good performance, but it can be further optimized if certain techniques are applied.

    hashtag
    Render optimization with static nodes

    A static node is equivalent to using node.cloneNode(true)

    hashtag
    render optimization with renderOnce

    The renderOnce property renders the node only once, one of the advantages of this technique is that the virtualDOM can access the scope of the function.

    import { html } from "atomico";
    import { expect } from "@esm-bundle/chai";
    import { fixture } from "atomico/test-dom";
    import { Component } from "./component.js";
    
    describe("my test", () => {
      it("my-component", async () => {
        const componentInstance = fixture(html`<${Component}>
            <span>content...</span>
        </${Component}>`);
    
        await componentInstance.updated; // fist render
    
        componentInstance.myProp1 = 10;
        componentInstance.myProp2 = 20;
        componentInstance.myProp3 = 20;
    
        await componentInstance.updated; // now we can observe the effects on the DOM from the previous updates
        
        await componentInstance.unmounted; // the component has been unmounted
      });
    });
    function component(){
        useEffect(()=>{
            console.log("Component mounted");
            ()=> console.log("Component unmounted");
        }, []);
        return <host/>
    }
    import {options} from "atomico";
    
    // replace the internal use of CSS StyleSheet by the style tag
    options.sheet = false; 
    
    // will avoid hook side effects
    options.ssr = true; 
    const staticDom = (
      <host shadowDom>
        <slot />
      </host>
    );
    function component() {
      return staticDom;
    }
    function component() {
      return html`<host shadowDom>
        <slot />
      </host>`;
    }
    function component() {
      return <host shadowDom renderOnce>
        <slot />
      </host>
    }

    Can I use Atomico in browsers without ESM support?

    Yes, but Atomico doesn't only depend on ESM, it also depends on the following api's that you will have to cover with Polyfill or some bundle tool.

    1. ESM modules: documentation arrow-up-right- caniusearrow-up-right

    2. CustomElements: documentationarrow-up-right - caniusearrow-up-right

    3. ShadowRoot: -

    4. Map: -

    5. Symbol and Symbol.for: -

    6. append and prepend: -

    7. Declarative Shadow DOM: Only for using SSR with webcomponents that use shadowDOM.

    Let's understand that today Atomico covers 94% of existing browsers without the need for polyfills or packers, the 6% not covered are usually browsers like ie11 or others.

    If you depend on the uncovered segment of browsers you can use the following tools to support the apis necessary for Atomico to work.

    to associate Map, Symbol, append and prepend.

    to associate esm.

    to associate customElements apis.

    documentationarrow-up-right
    caniusearrow-up-right
    documentationarrow-up-right
    caniusearrow-up-right
    documentationarrow-up-right
    caniusearrow-up-right
    documentation arrow-up-right
    caniusearrow-up-right
    documentationarrow-up-right
    polyfill-fastly.ioarrow-up-right
    https://github.com/Rich-Harris/shimportarrow-up-right
    https://github.com/ungap/custom-elementsarrow-up-right

    Is it advisable to declare events using the props API?

    It is not recommended to use the props API to create an event, as this callback associated as props will have the following limitations:

    1. Its name cannot have the on prefix since, if it does, Atomico will recognize it as a property expecting to listen to the event.

    2. It can only have one listener, limiting others from observing the event.

    hashtag
    We recommend:

    1. Using the hook to dispatch component-level events or any custom hook.

    2. Using the API to dispatch events when the observed prop changes.

    Always prefer the two previously mentioned methods, as they allow you to:

    1. Define if the event bubbles.

    2. Define if the event is cancelable.

    3. Define if the bubbling event can penetrate the shadow DOM.

    4. Define a custom constructor for the event.

    hashtag
    When is it recommended to use a callback as a prop?

    Cuando se busca comunicar parametros o leer el retorno de este, ejemplo:

    Having multiple listeners for the event

    useEvent
    Prop.event
    const MyForm = c(
      ({ getPosts }) => {
        const posts = useAsync(getPosts, []);
    
        return <host></host>;
      },
      {
        props: {
          getPosts: {
            type: Function,
            value: async (): Promise<{ id: number; title: string }[]> => [],
          },
        },
      }
    );

    When and why to use shadowDom?

    hashtag
    When to use the shadow Dom?

    By creating a UI solution that coexists in the web ecosystem such as HTML, libraries and frameworks, since the shadowDOM allows you to:

    1. Protect your UI from libraries like React, Vue and others

    2. Associate styles natively, efficiently and safely

    3. Reference lightDOM content in the shadowDOM

    Webcomponents with Shadow DOM are ideal for creating cross UI solutions as they coexist without conflict with the entire web ecosystem.

    hashtag
    Why use shadowDOM?

    To protect our component at the DOM manipulation level and take advantage of some of the benefits of the shadowDOM such as:

    1. Slot management

    2. CSS appearance of web component.

    3. The shadowDOM is ideal for creating components that interact with libraries like React, Vue or others. The ShadowDOM protects the DOM it contains from manipulations generated by other libraries.

    hashtag
    Slot management

    Slots allow us to reflect existing nodes in the lightDOM into the shadowDOM, example:

    This is really useful, since we can group slots according to their name in a specific place of the webcomponent.

    We invite you to learn more about slots through the following guide:

    hashtag
    CSS appearance of web component.

    The shadowDOM allows you to do something fantastic, protect your CSS from global styles, this has 2 great advantages:

    1. The appearance of our component will not be affected by global CSS declarations.

    2. Customization protected by CSS custom Properties

    We invite you to learn more about handling styles in atomic through the following guide:

    🔀Slotchevron-right
    CSS Styles with Shadow DOMchevron-right

    use-slot

    Retrieves the nodes assigned to a slot.

    CSS Styles with Shadow DOM

    Working on this documentation...

    Naming

    Simple but useful

    If you are a React user, it is common for you to make component declarations like these:

    export function MyComponent(){
        return <>My component in react</>
    }

    Thanks to the previous code in React we can:

    1. Instantiate your component through its function, example <MyComponent>

    2. Friendly export component as module

    In Atomico there are some small differences

    hashtag
    we recommend in Atomico

    Export the return of the c function, since it is instantiable at the JSX level or when using the new operator, example:

    Instance with operator new: The new operator allows the instance to associate at the TS level the types declared using Atomico

    Instance with document.createElement

    Gracias a lo anterior podemos:

    1. Instantiate your component with TS-level type validation either by using JSX or the new operator with, but remember that to get this type of instances you must first have registered your customElement in the same file or another.

    2. Export the useful component to instantiate.

    circle-exclamation

    Prefer this export format because tools like @atomico/exports take advantage of this format to automatically create wrappers for React, Preact, and Vue.

    <my-component></my-component>
    👇arrow-up-right
    export const MyComponent = c(() => <host>My component in Atomico</host>);
    
    customElements.define("my-component", MyComponent);
    import { MyComponent } from "./my-component";
    
    function app(){
        return <host>    
            <MyComponent/>
        </host>
    }
    import { MyComponent } from "./my-component";
    
    const component = new MyComponen; 
    import "./my-component";
    
    const component = document.createElement("my-component");

    Meta-types

    Meta-types allow you to define properties not covered by Atomico's Automatic support, such as:

    Method declaration
    Type declaration
    Event declaration

    Component

    Type to structure a component from its creation

    import { Component } from "atomico";
    
    // 📌 Parameters for the function and set 
    //    the structure rules for myComponent.props
    interface Props{
        checked: boolean;
        value: string
    }
    
    // 📌 Optional, improves the typing experience 
    //    in JSX (Atomico, Preact and React)
    interface MetaProps {
        myMethod:(value: number)=>void;
        onMyEvent: Event;
    }
    
    const myComponent: Component<Props, MetaProps> = (props) => {
        const myMethod = (value: number)=>{};
        return <host myMethod={myMethod }></host>
    }
    
    myComponent.props = {
        checked: Boolean,
        value: { type: String, event: {type: "MyEvent"} },
    }

    The declaration const myComponent: Component<Props, MetaProps> defines at the Typescript level the types of props and other options with which the component should be structured.

    This process is strict but makes autocompletion and error detection easier in the component declaration.

    Some warnings that this type can create are:

    1. The declaration of the prop in myComponents.props does not match the type declared in Props.

    2. A props declaration is missing.

    3. Props has invalid metadata, example reflect has been defined for a Promise type.

    Type

    Although the props today offer strict rules, Type allows them to be complemented with a direct definition of the types to accept, example:

    import { Props } from "atomico";
    
    function component(){
        return <host/>
    }
    
    component.props = {
        value: String as Type<"A"|"B"|"C">
    }

    The above is equivalent to using value as a function, example:

    import { Props } from "atomico";
    
    function component(){
        return <host/>
    }
    
    component.props = {
        value: {
            type: String,
            value: ():"A"|"B"|"C"=>"A"
        }
    }

    This is also valid for the null type that in Atomic translates as Any, example:

    component.props = {
        src: null as Type<string | Promise<any>>
    }

    Method declaration

    Atomico allows the definition of methods from the host tag, this in order to share the scope with the methods (Functions) of the component, but this escapes the definition of types, to patch this we must use the Host type, this will allow us to validate the method from the JSX, TSX and the component instance.

    hashtag
    Host to method declaration

    import {Host} from "atomico";
    
    type MyMethod = (value: number)=>void;
    
    function component():Host<{myMethod: MyMethod }>{
        const myMethod: MyMethod = ()=>{}
        return <host myMethod={myMethod }/>
    }

    Integrating Atomico in React

    Atomico integrates very easily into React, either using @atomico/exports or @atomico/react.

    hashtag
    @atomico/exports

    CLI that decorates the package.json according to the code to be exported in order to share your code optimally. By using the --wrappers flag the CLI will detect the export of webcomponents and automatically create a wrapper for React, Preact and Vue.

    You can learn more about @atomico/exports in the following guide:

    @atomico/exportschevron-right

    hashtag
    @atomico/react

    With this package you will be able to create wrappers for your Webcomponents in React, even managing to make SSR of your webcomponents in environments like Next.js with these wrappers.

    You can learn more about in the following guide:

    @atomico/react
    @atomico/reactchevron-right

    Check the correct use of hooks

    By default most hooks infer types automatically, however here are some typing tips:

    hashtag
    useState

    useState infers the type according to its initial state, if you don't define that state you can define the type manually, example:

    const [message, setMessage] = useProp<string>();

    The above statement will define that:

    1. message is of type string.

    2. setMessage only accepts values of type string or functions that return a string.

    hashtag
    useProp

    useProp will not infer the type from the prop, you must define it, example:

    Let's remember that useProp has a return api similar to useState, so the previous declaration will define that:

    1. message is of type string.

    2. setMessage only accepts values of type string or functions that return a string.

    hashtag
    useMemo and useCallback

    both useMemo vs useCallback infer its type based on the callback that builds the memo state, but for certain conditions you may need to force the type return, example:

    Although useMemo infers that its type is string, you can change the return rules via the first type parameter.

    The above statement will define that:

    1. message is of type string.

    This applies equally to useCallback.

    hashtag
    useEvent

    useEvent allows defining the detail structure through the type parameter, example:

    The above statement will define that:

    1. The dispatch callback expects an object of type {id: string} as a parameter.

    hashtag
    useRef

    useRef allows to define through the type parameter the expected value of current in the reference.

    The above statement defines:

    1. refForm?.current is of the type HTMLFormElement.

    hashtag
    useHost

    useHost defines that current always exists, so the optional selector is not necessary, the type parameter of useHost allows to define the source Element, example:

    All hooks in Atomico have Type declarations, explore them when importing each hook as a documentation source.

    const [message, setMessage] = useProp<string>("message");
    const message = useMemo<string>(() => "i'am atomico!");
    const dispatch = useEvent<{ id: string }>("MyCustomEvent", { bubbles: true });
    
    dispatch({ id: string });
    const refForm = useRef<HTMLFormElement>();
    const host = useHost<HTMLElement>();

    From React to Atomico

    Atomico inherits part of the React syntax and applies it to webcomponents, with a closer to standard approach.

    hashtag
    If you develop with React, you will know 80% Atomico ... now why use Atomico?:

    1. Atomico will not limit your React learning curve, what you learned in Atomico is applicable in React, for example hooks and virtualDOM.

    2. Atomico is 3kB in size which is 7% of React + React-dom.

    3. Better component abstraction, for example the use of the ShadowDOM will avoid the need to use css-in-js like styles-components or emotion, reducing dependencies.

    4. Agnostic Components, what you create with React only works within React, what you create with Atomico works on the web, so you can use your components within React, Vue, Svelte or Html.

    5. Exclusive component for React, the CLI @ atomico / exports automatically generates a wrapper component of your webcomponent for React, improving backward compatibility with React.

    The following examples show some differences between React and Atomico.

    hashtag
    Counter example

    From the example we will highlight the following differences:

    1. In Atomico you only use one import.

    2. useProp is like useState, but with the difference that useProp references the state from the webcomponent property defined in counter.props.

    hashtag
    CustomHooks example

    From the example we will highlight the following differences:

    1. The hook api is the same.

    2. the component in Atomico returns the `<host/> tag.

    hashtag
    Supported hooks

    In Atomico you will have the most useful React hooks such as:

    1. useRef

    2. useState

    3. useReducer

    hashtag
    CSS-in-JS

    It is common to see the use of libraries such as Emotion or styled-components to encapsulate styles in React, but these add an additional cost, be it for performance or bundle, in Atomico there is no such cost.

    counter.props allows us to create the properties of our webcomponent, these are like React's propTypes, but with a big difference they are associated with the instance and can be read and modified by referencing the node, example document.querySelector("my-counter").count = 10;
  • ReactDom.render needs a reference to mount the component, in Atomico you only need to create the my-counter tag to create a new instance of the component.

  • The <host/> tag is similar to <> </> for React, but <host/> represents the webcomponent instance and every component created with Atomico must return the host tag

  • This is only readability, but in Atomico by convention we do not use capital letters when naming our component, these are only used when creating the customElement as in line 16, since Counter is instantiable.

  • useLayoutEffect
  • useEffect

  • useMemo

  • useCallback

  • useContext : Not supported, event api is better practice than context when using webcomponents, example useChannel****

  • import { useEffect, useState } from "react";
    
    function useJsonPlaceholder(path) {
      const [state, setState] = useState();
      useEffect(() => {
        let cancel;
        setState(null);
        fetch(`https://jsonplaceholder.typicode.com/${path}`)
          .then(() => res.json())
          .then((data) => !cancel && setState( data ));
        return () => (cancel = true);
      }, [path]);
    }
    
    function Component() {
      const posts = useJsonPlaceholder("posts");
      return (
        <>
          {posts 
            ? posts.map(({ title }) => <h1>{title}</h1>)
            : "Loading..."}
        </>
      );
    }
    import { useEffect, useState } from "atomico";
    
    function useJsonPlaceholder(path) {
      const [state, setState] = useState();
      useEffect(() => {
        let cancel;
        setState(null);
        fetch(`https://jsonplaceholder.typicode.com/${path}`)
          .then(() => res.json())
          .then((data) => !cancel && setState( data ));
        return () => (cancel = true);
      }, [path]);
    }
    
    function component() {
      const posts = useJsonPlaceholder("posts");
      return (
        <host>
          {posts 
            ? posts.map(({ title }) => <h1>{title}</h1>)
            : "Loading..."}
        </host>
      );
    }
    const Button = styled.a`
      /* This renders the buttons above... Edit me! */
      display: inline-block;
      border-radius: 3px;
      padding: 0.5rem 0;
      margin: 0.5rem 1rem;
      width: 11rem;
      background: transparent;
      color: white;
      border: 2px solid white;
    
      /* The GitHub button is a primary button
       * edit this to target it specifically! */
      ${props => props.primary && css`
        background: white;
        color: black;
      `}
    `
    
    render(
      <div>
        <Button
          href="https://github.com/styled-components/styled-components"
          target="_blank"
          rel="noopener"
          primary
        >
          GitHub
        </Button>
    
        <Button as={Link} href="/docs">
          Documentation
        </Button>
      </div>
    )
    import { c, css } from "atomico";
    
    function button() {
      return <host shadowDom><slot/></host>;
    }
    
    button.props = { primary: { type: Boolean, relfect: true } };
    
    button.styles = css`
      :host {
        display: inline-block;
        border-radius: 3px;
        padding: 0.5rem 0;
        margin: 0.5rem 1rem;
        width: 11rem;
        background: transparent;
        color: white;
        border: 2px solid white;
      }
    
      :host([primary]) {
        background: white;
        color: black;
      }
    `;
    
    export const Button = c(button);
    import { useState } from "react";
    import ReactDOM from 'react-dom'
    
    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => setCount(prevCount => prevCount - 1
          <button onClick={() => setCount(prevCount => prevCount + 1
        </>
      );
    }
    
    
    render(
      <Counter initialCount={1}/>, 
      document.querySelector("#counter")
    );
    import { c, useProp } from "atomico";
    
    function counter() {
      const [count, setCount] = useProp("count");
      return (
        <host>
          Count: {count}
          <button onClick={() => setCount(prevCount => prevCount - 1
          <button onClick={() => setCount(prevCount => prevCount + 1
        </host>
      );
    }
    
    counter.props = { count: { type: Number, value: 0
    
    const Counter = c(counter);
    
    customElements.define(
      "my-counter",
      Counter  
    );
    )
    }
    >
    -
    </
    button
    >
    )
    }
    >
    +
    </
    button
    >
    )
    }
    >
    -
    </
    button
    >
    )
    }
    >
    +
    </
    button
    >
    }
    }

    Event declaration

    Atomico supports through the use of the Host type, the declaration of events and methods, this is useful for associating meta-types to the customElement instance when using JSX or TSX.

    hashtag
    Host to declare events

    Host will be useful for you to declare your event using JSX or TSX regardless of its origin, example:

    import { Host, c, useEvent } from "atomico";
    
    function myComponent(): Host<{
      onMyCustomEvent: Event
    }> {
      const dispatch = useEvent("MyCustomEvent");
      return <host>
          <button onclick={dispatch}>click</button>
      </host>;
    }
    
    export const MyComponent = c(myComponent);

    The use of Host allows that when using JSX or TSX your event is validated through Typescript, this also applies when using @atomico/react, example:

    import { MyComponent } from "my-componnet";
    
    <MyComponent
      onMyCustomEvent={(event) => {
        event.currentTarget; //  < MyComponent
      }}
    ></MyComponent>;

    Note event.currentTarget accesses the CustomElement, example properties and more.

    That has another benefit, capturing the event as a type to be used in an external handler, example:

    function handlerMyCustomEvent(
      event: DOMEvent<"MyCustomEvent", typeof MyComponent>
    ) {
      event.currentTarget; //  < MyComponent
    }

    Class inheritance

    hashtag
    Classes external to Atomico

    import { c, html } from "atomico";
    
    function component() {
      return html`<host shadowDom> ...my content </host>`;
    }
    
    class VanillaElement extends HTMLElement {
      constructor() {
        super();
        console.log("create");
      }
      connectedCallback() {
        console.log("mount");
      }
      disconnectedCallback() {
        console.log("mount");
      }
      attributeChangedCallback() {
        console.log("my-attr update");
      }
      static get observedAttributes() {
        return ["my-attr"];
      }
      // ⚠️ not native but valid within Atomico.
      // this is just an example the ideal is to 
      // have a shared reference of the CSSStyleSheet
      static get styles(){
        const sheet= new CSSStyleSheet();
        sheet.replace('a { color: blue; }');
        return sheet;
      }
    }
    
    
    const Component = c( component,  VanillaElement );

    component: function that declares the webcomponent for Atomico.

    VanillaElement: class that will be extended by Atomico to create Component, Atomico will not break the life cycle of the component, allowing them to interact freely.

    hashtag
    Classes internal to Atomico

    classes produced by the Atomico function c, these can be extended between components, example:

    Consider the following effects when using this inheritance model:

    1. The render will be rewritten.

    2. The props are inherited, Atomico will reuse the previously declared props.

    3. Styles

    hashtag
    Inheritance outside of Atomico

    The c function creates an optimized standard custom element, which can be extended to modify its behavior, be:

    1. Adding methods.

    2. Creating or replacing style sheets.

    3. Creando nuevas propiedades.

    Suppose we have a MyButton product of the function c, we can extend this component to modify its appearance without the need to completely rewrite it, example:

    The benefit of this inheritance is to simplify the modification of the appearance of a component created with Atomico, since it avoids its rewriting.

    are inherited. Atomico will merge the stylesheets.
    import { c, html, css } from "atomico";
    
    function component1({ prop1 }) {
      return html`<host shadowDom> ...my content, ${prop1} </host>`;
    }
    
    component1.props = {
      prop1: String,
    };
    
    component1.styles = css`
      :host {
        font-size: 100px;
      }
    `;
    
    function component2({ prop1, prop2 }) {
      return html`<host shadowDom> ...my content, ${prop1} and ${prop2} </host>`;
    }
    
    component2.props = {
      prop2: String,
    };
    
    const Component1 = c(component1);
    
    const Component2 = c(component2, Component1);
    
    customElements.define("component-2", Component2);
    import { css } from "atomico";
    import { MyButton } from "./src/my-button/my-button.js";
    
    class MyNewButton extends MyButton {
      static styles = [
        /**
         * super.styles allows to load the previous styles
         * this static property is created internally by atomico
         */
        super.styles,
        /**
         * In the following way we are associated with a new
         * styleSheet to our customElement
         */
        css`
          :host {
            --button-background: teal;
          }
        `,
      ];
    }

    Tips

    Here are some tips you can take into account when creating webcomponents with Atomico

    hashtag
    Component name as function

    Write the functional component using the first lowercase character, since the functional declaration is not instantiable as a constructor in JSX.

    function component() {
      return <host />;
    }

    This prevents confusion when identifying the constructor of the component instance. arrow-up-rightAtomico if it supports instances as Constructors, see cases.​arrow-up-right

    hashtag
    useProp

    preferably use useProp in the following cases:

    • By modifying the prop from inside the component.

    • By isolating the logic of the prop in a customHook.

    In most cases downloading the prop from the first argument of the function is simpler and more declarative, example:

    hashtag
    Prefers the use of static styles

    This does not rule out the use within the style tag, since it is sometimes the solution to the definition of conditional styles or variables to the logic and outside the scope of the custom properties.

    Atomico has type support in both JSDOC and Typescript by inferring the types of the props.​arrow-up-right
    function useCounter(prop) {
      const [value, setValue] = useProp(prop);
      return {
        value,
        increment() {
          setValue(value + 1);
        },
      };
    }
    function useCounter(prop) {
      const [value, setValue] = useProp(prop);
      return {
        value,
        increment() {
          setValue(value + 1);
        },
      };
    }
    function component({ value }) {
      return <host>{value}</host>;
    }
    
    component.props = {
      value: Number,
    };
    function component() {
      return <host shadowDom />;
    }
    
    component.styles = css`
      :host {
        display: block;
      }
    `;

    Rendering Differences

    Atomico escapes React DOM immutability logic and moves closer to the native DOM API, this means that Atomico at the scope level is only responsible for receiving the props and rendering the DOM, but it does not observe the internal mutations of the DOM automatically, this is because the native level mutations can come from anywhere, for example other libraries. Now for ensure state synchronization it is convenient that every webcomponent that wants to be observed has the obligation to dispatch a change event

    You can easily achieve this by using useEvent or Prop.event, applying that you can:

    capture change as event

    const [ value, setValue ] = useState("init"); // you can also use useProp
    
    <host>
        <my-component 
            value={value} 
            onchange={({target})=>setValue(target.value)}
        ></my-component>
    </host>

    Force a fixed value

    const update = useUpdate();
    
    <host>
        <my-component 
            value={value} 
            onchange={update}></my-component>
    </host>

    the update function forces an update on the component, in order to redefine value according to the scope

    VirtualDOM api differences

    Guide that defines some differences that exist between Atomico and React when working with the DOM.

    Atomico's virtualDOM is:

    1. Close to standard DOM .

    2. Additional coverage to webcomponents.

    hashtag
    Components as constructors

    Atomico does not support the use of functions to instantiate the component as we traditionally do in React, so that the component can be instantiated as a constructor it must be a webcomponent or a real Node.

    hashtag
    Event Association

    Like React in Atomico you need to use the prefix on to announce an event, but there is a difference Atomico does not manipulate the name of the event so onClick is different from onclick, the purpose of this difference is to support custom events.

    hashtag
    Keyes

    the key property in Atomicoo can be of type String, Number, Symbol or other immutable reference.

    hashtag
    dangerouslySetInnerHTML

    function Component(){
        const [ state, setState ] = useState();
        return <host></host>
    }
    
    function App(){
        return <host>
            <Component/>
        </host>
    }
    // ⚠️ stateless component
    function Component(){ 
        return <host></host>
    }
    
    function App(){
        return <host>
            <Component/>
        </host>
    }
    function component(){
        return <host></host>
    }
    
    const Component = c(component);
    customElements.define("my-component",Component)
    
    function app(){
        return <host>
            <Component/>
        </host>
    }
    function app(){
        const Div = document.createElement("div");
        return <host>
            <Image/>
            <Div/>
        </host>
    }
    <div>
        <button onclick={console.log}>click</button>
        <button onMyCustomEvent={console.log}>click</button>
        <button onmy-event={console.log}>click</button>
        <button onmouseover={console.log}>click</button>
    </div>
    <div>
        <div key={1}>1</div>
        <div key={symbol}>1</div>
        <div key={immutable}>1</div>
    </div>
    <div innerHTML={`<h1>html!</h1>`}/>

    useSlot

    hashtag
    Module

    import { useSlot } from "@atomico/hooks/use-slot";

    hashtag
    Syntax

    const optionalFilter = (element)=> element instanceof MyCustomElement;
    const childNodes = useSlot(ref, optionalFilter);

    Where:

    1. ref: Reference of the slot to observe.

    2. childNodes: List of nodes assigned to the observed slot.

    3. optionalFilter: allows to filter nodes assign to childNodes

    hashtag
    Live example

    Props

    The Props type allows you to infer the types of props already declared in the props object, example:

    import { Props } from "atomico";
    
    function myComponent(props: Props<typeof myComponent>) { // {checked: boolean}
        return <host>Hello {props.checked?"Yes":"No"}</host>
    }
    
    myComponent.props = {
        checked: Boolean,
    }

    The main difference with the Component type is that Props does not generate stricture rules for the component, it only infers the props object to be used as an argument.

    At the Typescript Props level, it does evaluate the structure of the component, in order to correctly infer the props, so if the component has a writing error, a warning about this type will be displayed.

    WebComponents.devWebComponents.devchevron-right
    Logo
    GitHub - atomicojs/atomicojs.devGitHubchevron-right
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    components/src/components/table at master · atomicojs/componentsGitHubchevron-right
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    Logo
    Logo

    useProxySlot

    useProxySlot allows you to observe the nodes assigned to a slot and reassign them to another slot dynamically, example:

    Input: Suppose we have a component that observe the slot[name="slide"] node

    <my-component>
        <img slot="slide" src="slide-1"/>
        <img slot="slide" src="slide-1"/>
        <img slot="slide" src="slide-1"/>
    </my-component>

    output: thanks to useProxySlot you will be able to modify the assignment of the list nodes without losing the nodes in the process as normally happens with useSlot, example:

    <my-component>
        <img slot="slide-1" src="slide-1"/>
        <img slot="slide-2" src="slide-1"/>
        <img slot="slide-3" src="slide-1"/>
    </my-component>

    hashtag
    Syntax and example

    import { useRef } from "atomico";
    import { useProxySlot } from "@atomico/hooks/use-slot";
    
    function component() {
      const ref = useRef();
      const children = useProxySlot(ref);
    
      return (
        <host shadowDom>
          <slot name="slide" ref={ref} />
          {children.map((child, index) => (
            <slot name={(child.slot = "slide-" + index)} />
          ))}
        </host>
      );
    }
    circle-info

    All redirected hooks must exist under a slot

    hashtag
    Live example

    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Logo
    WebComponents.devWebComponents.devchevron-right
    Ejemplo en vivo
    Logo