API
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>
| Name | Signature | Description |
|---|---|---|
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 reactiveprotected initStyles(): void: initializes component styles and can use this.styleManagerprotected render(): VNode: returns a virtual node
Lifecycle
beforeMount(): before the initial renderonMounted(): after the DOM is mountedbeforeUpdate(): before a state update triggers a patchonUpdated(): after the patch is completebeforeUnmount(): before unmountingonUnmounted(): after unmountingonErrorCaptured(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 VNodestate: reactive statesetProps(props): merges updated props and triggers an update when mountedsetState(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' } },
],
};
}
}