TSone

Component API

TSone uses an object-oriented component model. Each component extends Component<Props, State> and returns a VNode through protected render(): VNode.

Component<Props, State>

NameSignatureDescription
Component<Props, State>abstract class Component<Props, State>The class-based component base class, providing props, state, lifecycle hooks, and rendering.
import { Component, VNode } from '@geektech/tsone';

interface CounterProps {
  initial?: number;
}

interface CounterState {
  count: number;
}

class Counter extends Component<CounterProps, CounterState> {
  protected initState(): CounterState {
    return {
      count: this.props.initial ?? 0,
    };
  }

  protected initStyles(): void {}

  protected render(): VNode {
    return {
      tag: 'button',
      listeners: {
        click: () => {
          this.state.count += 1;
        },
      },
      children: ['count: '],
    };
  }
}

Required Methods

  • protected initState(): State: returns the initial component state, which is wrapped with reactive
  • protected initStyles(): void: initializes component styles and can use this.styleManager
  • protected render(): VNode: returns a virtual node

Lifecycle

  • beforeMount(): before the initial render
  • onMounted(): after the DOM is mounted
  • beforeUpdate(): before a state update triggers a patch
  • onUpdated(): after the patch is complete
  • beforeUnmount(): before unmounting
  • onUnmounted(): after unmounting
  • onErrorCaptured(error, instance): catches errors thrown by descendant renders or updates; return false to stop propagation

State and Props

component.setProps({ initial: 3 });
component.setState({ count: 4 });
  • props: passed when constructing the component or supplied by a parent component VNode
  • state: reactive state
  • setProps(props): merges updated props and triggers an update when mounted
  • setState(state): merges updated state

Events

const unsubscribe = component.on('submit', (payload) => {
  console.log(payload);
});

unsubscribe();

Inside a component, use this.emit(eventName, ...args) to emit an event.

Use emitters on a parent component VNode to declare event handlers. During patching, old handlers are replaced or removed.

{
  component: Editor,
  emitters: {
    saved: (payload) => this.save(payload),
  },
}

Dependency Injection

Use InjectionKey, provide, and inject to share dependencies across the application and component tree. Component injection searches the component itself, its ancestors, and then the application.

import { InjectionKey } from '@geektech/tsone';

const THEME: InjectionKey<{ mode: string }> = Symbol('theme');
app.provide(THEME, { mode: 'dark' });
const theme = this.inject(THEME, { mode: 'light' });

HTML Tag Helpers

Use Tag(tag, options) to create any HTML VNode, or use a named helper for common elements. Every helper accepts the same props, children, listeners, key, slot, and directions options.

import { Section, Tag } from '@geektech/tsone';

const panel = Section({ props: { className: 'panel' }, children: ['Content'] });
const dialog = Tag('dialog', { props: { open: true }, children: ['Hello'] });

Common helpers include Div, Span, P, Button, Input, Section, Main, Header, Footer, Nav, Article, Aside, H1 through H6, Strong, Em, Small, Pre, Code, Blockquote, Ul, Ol, Li, A, Img, Form, Label, Textarea, Select, Option, Table, Thead, Tbody, Tr, Th, and Td.

Conditionals, Lists, and Model Binding

import { Input, each } from '@geektech/tsone';

const children = each(
  this.state.users,
  (user) => ({ tag: 'li', children: [user.name] }),
  (user) => user.id
);

const field = Input({
  directions: {
    model: { path: 'profile.name' },
  },
});

const panel = {
  component: ProfilePanel,
  directions: { if: this.state.visible },
};

List Enter and Exit Animations

TransitionGroup animates initial and newly added keyed children, and delays unmounting removed children until their exit animation finishes. Its public type is TransitionAnimationType.

import { Li, TransitionGroup, each } from '@geektech/tsone';

{
  component: TransitionGroup,
  props: { tag: 'ul', type: 'fade', duration: 300 },
  children: each(
    this.state.items,
    (item) => Li({ children: [item.label] }),
    (item) => item.id
  ),
}

Available types are fade, slide-up, slide-down, slide-left, slide-right, and scale. Defaults are tag div, type fade, duration 300 ms, and ease easing. Direct children require unique keys. The component respects prefers-reduced-motion and skips animation when Web Animations is unavailable. Reordering moves retained nodes without a reorder or FLIP animation.

Single-element transitions and component caching

Transition plays a transition for a single element as show flips, applying {name}-enter-from / {name}-enter-to and {name}-leave-from / {name}-leave-to CSS classes (each paired with a -active class) and removing the element after the leave starts plus duration ms. KeepAlive keeps every keyed child component mounted (state and DOM are preserved) while showing only the active one, useful for tab switches and other state-preserving scenarios.

import { KeepAlive, Transition, createComponent } from '@geektech/tsone';

const dialog = {
  component: Transition,
  props: { show: this.state.open, name: 'fade', duration: 300 },
  children: [Dialog({ children: ['Settings'] })],
};

const tabs = {
  component: KeepAlive,
  props: { activeKey: this.state.activeTab },
  children: [
    createComponent(Editor, {}, [], 'editor'),
    createComponent(Preview, {}, [], 'preview'),
  ],
};

Form Validation

import { createForm, minLength, required, validate } from '@geektech/tsone';

const form = createForm(this.state, {
  'profile.name': [required('Please enter a name'), minLength(2)],
  'profile.age': [validate((value) => Number(value) >= 18 || 'Age must be at least 18')],
});

const result = form.validate();
const field = form.validateField('profile.name');
form.resetErrors();

Slots

class Panel extends Component<object, object> {
  protected initState() {
    return {};
  }

  protected initStyles(): void {}

  protected render(): VNode {
    return {
      tag: 'section',
      children: [
        { tag: 'slot', props: { name: 'header' } },
        { tag: 'slot', props: { name: 'default' } },
      ],
    };
  }
}