🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@seahax/elemental

Package Overview
Dependencies
Maintainers
2
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@seahax/elemental

Functional, reactive, web component base library.

latest
npmnpm
Version
0.8.7
Version published
Maintainers
2
Created
Source

@seahax/elemental

Functional, reactive, web component base library.

Contains everything you need to build anything from a single component up to a full reactive application, with minimal overhead.

  • Create fully portable web components
  • Direct and safe access to the DOM (no virtual DOM)
  • React-style code reuse using composable hooks
  • Global & local state reactivity
  • Client-side routing
  • No Build Tooling
  • No Dependencies
  • Tiny Bundle Size

NPM BundleJS (GZIP) builds.sr.ht status

Define A Web Component

import {
  defineComponent,
  h,
  useRef,
  useStore,
  useAttributes,
  useParent,
  useDocument,
  useRoute,
  useAsync,
  useEffect,
  useStyleEffect,
  useChildEffect,
  useDisconnectEffect,
  useElementInternals,
  useHost,
  useShadow,
} from '@seahax/elemental';

export const MyComponent = defineComponent((shadow) => {
  // This function is called every time the component is connected to the
  // document.
  // 
  // For the most part, a component's lifecycle should be handled as if it
  // starts on connect and ends on disconnect, even though the component
  // can be reconnected to the document. Restore internal state on
  // connection from host attributes and properties. Effect hooks are
  // designed to facilitate this pattern.

  // Create HTML elements and save references to them.
  const myInput = h('input');

  // Render content to the shadow DOM.
  h(shadow, [
    h('p', { class: 'hello' }, ['Hello, World!']),
    h('div', { class: 'inputs' }, [
      myInput,
    ]),
  ]);

  // Use a reference (reactive state) value.
  const localStateRef = useRef('initial value', (newValue) => {
    // Handle
  });

  // Use a reference (reactive state) bound to a (shared) store.
  const globalStateRef = useStore(myStore, select, mutate);

  // Use references (reactive state) bound to the component's attributes.
  const [dataValueRef, ...] = useAttributes('data-value', ...);

  // Use a reference (reactive state) bound to the component's parent node.
  const parentNode = useParent();

  // Use a reference (reactive state) bound to the component's owner document.
  const ownerDocument = useDocument();

  // Use references (reactive state) bound to route properties.
  const [pathnameRef, searchRef, hashRef] = useRoute('pathname', 'search', 'hash');

  // Use a reference (reactive state) bound to an async loader function.
  const asyncRef = useAsync([
    // dependency references
  ], async (signal, ...dependencyValues) => {
    // Reactive async code runs when the component is connected to the
    // document, and when any of the dependencies change. The signal is
    // aborted if the dependencies change before the promise returned by
    // this function is resolved.
  });

  // React to reference changes.
  useEffect([
    // dependency references
    localStateRef,
    globalStateRef,
    dataValueRef,
    pathnameRef,
    searchRef,
    hashRef,
    asyncRef,
  ], (...dependencyValues) => {
    // Reactive code runs when the component is connected to the document,
    // and when any of the dependencies change.

    return () => {
      // Cleanup after dependency refs are changed (before the next effect
      // callback) and after the component is disconnected from the
      // document.
    };
  });

  // React to reference changes and apply CSS styles to the shadow root.
  useStyleEffect([
    // dependency references
    localStateRef
  ], (localState) => {
    // Reactive code runs when the component is connected to the document,
    // and when any of the dependencies change.

    // Return a CSS string to be adopted by the shadow root.
    return /* css */ `
      .hello {
        color: ${localState};
      }
    `;
  });

  // React to child list changes.
  useChildEffect(() => {
    // Reactive code runs when the component is connected to the document,
    // and when the children of the component change. Access the current
    // children using the `shadow.host.children` property.

    return () => {
      // Cleanup before the next effect callback and after the component
      // is disconnected from the document.
    };
  });

  // Register a document disconnection callback.
  useDisconnectEffect(() => {
    // Called when the component is disconnected from the document.
  });

  // Use the element's internals.
  const elementInternals = useElementInternals();

  // Use the component host element.
  const host = useHost();

  // Use the component shadow root.
  const shadow = useShadow();
});

Add Styles

const MyComponent = defineComponent(
  (shadow) => {
    ...
  },
  {
    // Styles to be adopted by the shadow root. String values are parsed
    // as CSS to create new `CSSStyleSheet` instances.
    styles: [cssText, cssStyleSheet],
  }
)

Customize The Shadow Root

const MyComponent = defineComponent(
  (shadow) => {
    ...
  },
  {
    // Use custom shadow root initialization options.
    // (default: { mode: 'open' }).
    shadow: {
      mode: 'closed',
      ...
    },
  }
);

Enable Form Association

import {
  useElementInternals,
  useForm,
  useFormDisabled,
  useFormResetCallback,
  useFormRestoreCallback,
} from '@seahax/elemental';

const MyComponent = defineComponent(
  (shadow) => {
    // Use the element's internals.
    const elementInternals = useElementInternals();

    // Use a reference (reactive state) bound to the associated form.
    const formRef = useForm();

    // Use a reference (reactive state) bound to the form disabled state.
    const formDisabledRef = useFormDisabled();

    // Register a form reset callback.
    useFormResetCallback(() => {
      // Called when the associated form is reset. Only called on connect
      // if the form was reset while the component was disconnected.
    });

    // Register a form restore callback.
    useFormRestoreCallback((state, reason) => {
      // Called when the associated form is restored. Only called on connect
      // if the form was restored while the component was disconnected.
    });
  },
  {
    // Enable form association.
    formAssociated: true,
  }
);

Add Web Component Properties

interface Props {
  checked: boolean;
}

const MyComponent = defineComponent<Props>(
  (shadow, propRefs) => {
    // Get properties (the ref value is initially undefined).
    const isChecked = propRefs.checked.value ?? shadow.host.hasAttribute('checked');

    // Set properties.
    propRefs.checked.value = true;

    // Alternatively, access the property on the host element.
    const isChecked = shadow.host.checked;
    shadow.host.checked = true;

    // React to property changes.
    useEffect([propRefs.checked], (checked) => {
      ...
    });
  },
  {
    props: {
      // Return a property descriptor that uses a pre-defined ref and the
      // host element. The property descriptor must have a `get` function,
      // `value` is not allowed, and all other properties are optional.
      // The ref value is initially undefined.
      checked: (ref, host) => {
        return {
          get: () => ref.value ?? host.hasAttribute('checked'),
          set: (value) => (ref.value = value),
        };
      },
    },
  }),
);

const element = new MyComponent();

// Properties are defined publicly on component (`HTMLElement`) instances.
element.checked = true;

Register The Custom Element On Definition

const MyComponent = defineComponent((shadow) => {
  ...
}, {
  // Register the component as a custom element with this tag name.
  tagName: 'my-component',
});

Extend Component Definition

import { extendComponentDefinition } from '@seahax/elemental';

// Create a new component definition factory (ie. `defineComponent`
// function) with common styles and hooks (callbacks) that can be used to
// add shared behavior to all defined components.
export const defineComponent = extendComponentDefinition({
  styles: [...],
  preInit: (shadow) => {...},
  postInit: (shadow) => {...},
  preRender: (shadow) => {...},
  postRender: (shadow) => {...},
});

Render An Element

// By tag name.
const element = h('div', {
  // Set attributes.
  class: 'my-class',
  // Set properties.
  ':id': 'my-id',
}, [
  // Set children.
  h('p', [text]),
]);

// By custom element constructor.
const element = h(MyComponent, {
  // Set attributes.
  class: 'my-class',
  // Set properties.
  ':id': 'my-id',
}, [
  // Set children.
  h('p', [text]),
]);

Update An Element

// Update an existing element.
h(element, {
  // Set attributes.
  class: 'my-class',
  // Remove attributes.
  class: null,
  // Set properties.
  ':id': 'my-id',
  // Attributes and properties that are not provided are left alone.
}, [
  // Replace all children. Children are left alone if no child array is
  // provided (undefined or omitted).
  h('p', [text]),
]);

Combine Conditional Classes

import { classes } from '@seahax/elemental';

// Using a dictionary of boolean values.
const classNames = classes({
  'my-class-0': true,
  'my-class-1': false,
  'my-class-2': undefined,
  'my-class-3': null,
  'my-class-4': true,
}); // = 'my-class-0 my-class-4'

// Using a sparse array.
const classNames = classes([
  'my-class-0',
  false,
  undefined,
  null,
  'my-class-4',
]); // = 'my-class-0 my-class-4'

Define A Router Component

import { defineRouter } from '@seahax/elemental/router';

export const Router = defineRouter({
  // (optional) Register the component as a custom element with this tag name.
  tagName: 'ce-router',
  // (optional) Define a component to render when a route parameters callback
  // throws an error.
  invalid: MyInvalidComponent,
  // (optional) Define a fallback component to render when no route matches.
  fallback: MyFallbackComponent,
})
  // Simple exact pathname match.
  .addRoute('/', MyHomeComponent)
  // Exact pathname match with search (query) parameters.
  .addRoute('/blog', MyBlogIndexComponent, (pathParams, searchParams) => {
    // Get search (query) parameters.
    const tag = searchParams.get('tag') ?? '';
    // Validate parameters.
    assert(!/[^a-z0-9-]/iu.test(tag), 'Invalid tag.');
    // Return attributes to be set on the component.
    return {
      'data-blog-tag': tag,
    };
  })
  // Dynamic pathname match with a path parameter. Path parameters match
  // exactly one path segment (does not match forward slashes).
  .addRoute('/blog/:id', MyBlogPostComponent, (pathParams) => {
    // Validate parameters.
    assert(!/[^a-z0-9-]/iu.test(pathParams.id), 'Invalid blog ID.');
    // Return attributes to be set on the component.
    return {
      'data-blog-id': pathParams.id,
    };
  })
  // Dynamic pathname match with a path splat parameter. Splats match one
  // or more (not zero) path segments at the end of the pathname.
  .addRoute('/resources/*path', MyResourcesComponent, (pathParams) => {
    return {
      'data-path': validatePath(pathParams.path),
    }
  })
  .build();

FAQs

Package last updated on 08 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts