Skip to content

API Reference

Store

Import

ts
import { Store } from '@geajs/core'

Creating a Store

Extend Store, declare reactive properties as class fields, add methods that mutate them, and export a singleton instance.

ts
import { Store } from '@geajs/core'

class TodoStore extends Store {
  todos: Todo[] = []
  filter: 'all' | 'active' | 'completed' = 'all'
  draft = ''

  add(text?: string): void {
    const t = (text ?? this.draft).trim()
    if (!t) return
    this.draft = ''
    this.todos.push({ id: uid(), text: t, done: false })
  }

  toggle(id: string): void {
    const todo = this.todos.find(t => t.id === id)
    if (todo) todo.done = !todo.done
  }

  remove(id: string): void {
    this.todos = this.todos.filter(t => t.id !== id)
  }

  setFilter(filter: 'all' | 'active' | 'completed'): void {
    this.filter = filter
  }

  get filteredTodos(): Todo[] {
    const { todos, filter } = this
    if (filter === 'active') return todos.filter(t => !t.done)
    if (filter === 'completed') return todos.filter(t => t.done)
    return todos
  }

  get activeCount(): number {
    return this.todos.filter(t => !t.done).length
  }
}

export default new TodoStore()

Reactivity

The store instance is wrapped in a deep Proxy. Any mutation — direct assignment, nested property change, or array method call — is automatically tracked and batched.

ts
this.count++
this.user.name = 'Alice'
this.todos.push({ id: '1', text: 'New', done: false })
this.items.splice(2, 1)
this.items.sort((a, b) => a.order - b.order)
this.todos = this.todos.filter(t => !t.done)

Changes are batched via queueMicrotask.

observe(path, handler)

ts
const unsubscribe = store.observe([], (value, changes) => {
  console.log('State changed:', changes)
})

store.observe('todos', (value, changes) => {
  console.log('Todos changed:', value)
})

store.observe('user.profile.name', (value, changes) => {
  console.log('Name changed to:', value)
})

unsubscribe()
ParamTypeDescription
pathstring | string[]Dot-separated path or array of path parts. Empty string/array observes all changes.
handler(value: any, changes: StoreChange[]) => voidCalled with the current value and the batch of changes.

Returns: () => void — call to unsubscribe.

StoreChange

ts
interface StoreChange {
  type: 'add' | 'update' | 'delete' | 'append' | 'reorder' | 'swap'
  property: string
  target: any
  pathParts: string[]
  newValue?: any
  previousValue?: any
  start?: number
  count?: number
  permutation?: number[]
  arrayIndex?: number
  leafPathParts?: string[]
  isArrayItemPropUpdate?: boolean
  otherIndex?: number
}

Intercepted Array Methods

MethodChange type
push(...items)append
pop()delete
shift()delete
unshift(...items)add (per item)
splice(start, deleteCount, ...items)delete + add (or append)
sort(compareFn?)reorder with permutation
reverse()reorder with permutation

Iterator methods (map, filter, find, findIndex, forEach, some, every, reduce, indexOf, includes) are intercepted to provide proxied items with correct paths.


Component

Import

ts
import { Component } from '@geajs/core'

Class Component

jsx
import { Component } from '@geajs/core'
import counterStore from './counter-store'

export default class Counter extends Component {
  template() {
    return (
      <div class="counter">
        <span>{counterStore.count}</span>
        <button click={counterStore.increment}>+</button>
        <button click={counterStore.decrement}>-</button>
      </div>
    )
  }
}

Lifecycle

MethodWhen called
created(props)After constructor, before render
onAfterRender()After DOM insertion and child mounting
onAfterRenderAsync()Next requestAnimationFrame after render
dispose()Removes from DOM, cleans up observers and children

Properties

PropertyTypeDescription
idstringUnique component identifier (auto-generated)
elHTMLElementRoot DOM element (created lazily from template())
propsanyProperties passed to the component
renderedbooleanWhether the component has been rendered

DOM Helpers

MethodDescription
$(selector)First matching descendant (scoped querySelector)
$$(selector)All matching descendants (scoped querySelectorAll)

Rendering

ts
const app = new MyApp()
app.render(document.getElementById('app'))

render(rootEl, index?) inserts the component's DOM element into the given parent. Components render once; subsequent state changes trigger surgical DOM patches.

events Getter

Generated by the Vite plugin for event delegation. Maps event types to selector-handler pairs:

ts
get events() {
  return {
    click: {
      '.btn-add': this.addItem,
      '.btn-remove': this.removeItem
    },
    change: {
      '.checkbox': this.toggle
    }
  }
}

Props and Data Flow

Props follow JavaScript's native value semantics:

  • Primitives (numbers, strings, booleans) are passed by value. The child receives a copy. Reassigning the prop in the child does not affect the parent.
  • Objects and arrays are passed by reference. The child receives the parent's reactive proxy. Mutating properties on the object or calling array methods updates the parent's state and DOM automatically.
jsx
// parent passes object and primitive
<Child user={this.user} count={this.count} />

// In the child:
this.props.user.name = 'Bob'   // two-way — updates parent's DOM
this.props.count = 99           // one-way — only child's DOM updates

When the parent updates a prop, the new value flows down to the child, overwriting any local reassignment the child may have made to a primitive.

For objects and arrays, reactivity propagates at any depth. A grandchild receiving the same object proxy can mutate it, and every ancestor observing that data updates.


Function Components

jsx
export default function TodoInput({ draft, onDraftChange, onAdd }) {
  const handleKeyDown = e => {
    if (e.key === 'Enter') onAdd()
  }

  return (
    <div class="todo-input-wrap">
      <input
        type="text"
        placeholder="What needs to be done?"
        value={draft}
        input={onDraftChange}
        keydown={handleKeyDown}
      />
      <button click={onAdd}>Add</button>
    </div>
  )
}

Function components are converted to class components at build time by the Vite plugin.


JSX Syntax

Attributes

GeaHTML equivalentNotes
class="foo"class="foo"Use class, not className
class={`btn ${active ? 'on' : ''}`}Dynamic classTemplate literal
value={text}value="..."For inputs
checked={bool}checkedFor checkboxes
disabled={bool}disabledFor buttons/inputs
aria-label="Close"aria-label="Close"ARIA pass-through

Event Attributes

jsx
<button click={handleClick}>Click</button>
<input input={handleInput} />
<input change={handleChange} />
<input keydown={handleKeyDown} />
<input blur={handleBlur} />
<input focus={handleFocus} />
<span dblclick={handleDoubleClick}>Text</span>
<form submit={handleSubmit}>...</form>

Both native-style (click, change) and React-style (onClick, onChange) names are supported.

Supported: click, dblclick, input, change, keydown, keyup, blur, focus, mousedown, mouseup, submit, dragstart, dragend, dragover, dragleave, drop.

With @geajs/mobile: tap, longTap, swipeRight, swipeUp, swipeLeft, swipeDown.

Text Interpolation

jsx
<span>{count}</span>
<span>{user.name}</span>
<span>{activeCount} {activeCount === 1 ? 'item' : 'items'} left</span>

Conditional Rendering

jsx
{step === 1 && <StepOne onContinue={() => store.setStep(2)} />}
{!paymentComplete ? <PaymentForm /> : <div class="success">Done</div>}

Compiled into <template> markers with swap logic.


List Rendering

jsx
<ul>
  {todos.map(todo => (
    <TodoItem key={todo.id} todo={todo} onToggle={() => store.toggle(todo.id)} />
  ))}
</ul>

The key prop is required. Gea uses applyListChanges for efficient add, delete, append, reorder, and swap operations.


Router

Import

ts
import { router, Router, RouterView, Link, matchRoute } from '@geajs/core'
import type { RouteConfig, RouteMatch, RouteParams, RouteComponent } from '@geajs/core'

router (Singleton)

The router singleton is a Store that tracks the current URL state. Its properties are reactive.

Properties

PropertyTypeDescription
pathstringCurrent pathname (e.g. '/users/42')
hashstringCurrent hash (e.g. '#section')
searchstringCurrent search string (e.g. '?q=hello&page=2')
queryRecord<string, string>Parsed key-value pairs from search (getter)

Methods

MethodDescription
navigate(path)Push a new history entry and update path, hash, search
replace(path)Replace the current history entry (no new back-button entry)
back()Go back one entry (history.back())
forward()Go forward one entry (history.forward())
ts
router.navigate('/users/42?tab=posts#bio')
console.log(router.path)   // '/users/42'
console.log(router.search) // '?tab=posts'
console.log(router.hash)   // '#bio'
console.log(router.query)  // { tab: 'posts' }

matchRoute(pattern, path)

Pure function that tests a URL path against a route pattern and extracts named parameters.

ParamTypeDescription
patternstringRoute pattern with optional :param and * segments
pathstringActual URL pathname to match against

Returns: RouteMatch | null

ts
interface RouteMatch {
  path: string
  pattern: string
  params: Record<string, string>
}
PatternMatchesParams
/about/about{}
/users/:id/users/42{ id: '42' }
/files/*/files/docs/readme.md{ '*': 'docs/readme.md' }
/repo/:owner/*/repo/dashersw/src/index.ts{ owner: 'dashersw', '*': 'src/index.ts' }

RouterView

Renders the first matching route from a routes array. Observes router.path and swaps the rendered component when the URL changes.

PropTypeDescription
routesRouteConfig[]Array of { path, component } objects. First match wins.
jsx
<RouterView routes={[
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/users/:id', component: UserProfile },
]} />

Both class components and function components are supported. Matched params are passed as props. Navigating between URLs that match the same pattern updates props instead of re-creating the component.

Renders an <a> tag for SPA navigation. Clicks call router.navigate() instead of triggering a full page reload.

PropTypeRequiredDescription
tostringYesTarget path
labelstringYesText content of the link
classstringNoCSS class(es) for the <a> tag
jsx
<Link to="/about" label="About" />
<Link to="/users/1" label="Alice" class="nav-link" />

Modifier keys (Cmd, Ctrl, Shift, Alt) preserve native browser behavior (open in new tab, etc.).


Gea Mobile

Import

js
import { View, ViewManager, GestureHandler, Sidebar, TabView, NavBar, PullToRefresh, InfiniteScroll } from '@geajs/mobile'

View

Full-screen Component rendering to document.body by default.

PropertyTypeDefaultDescription
indexnumber0Z-axis position
supportsBackGesturebooleanfalseEnable swipe-back
backGestureTouchTargetWidthnumber50Touch area width (px)
hasSidebarbooleanfalseAllow sidebar reveal
MethodDescription
onActivation()Called when the view becomes active in a ViewManager
panIn(isBeingPulled)Animate into viewport
panOut(isBeingPulled)Animate out of viewport

ViewManager

MethodDescription
pull(view, canGoBack?)Navigate forward. canGoBack saves history.
push()Go back to previous view.
setCurrentView(view, noDispose?)Set active view without animation.
canGoBack()true if history exists.
toggleSidebar()Toggle sidebar.
getLastViewInHistory()Last view in the stack.

GestureHandler

Supported gestures: tap, longTap, swipeRight, swipeLeft, swipeUp, swipeDown.

UI Components

ComponentDescription
SidebarSlide-out navigation panel
TabViewTab-based view switching
NavBarTop navigation bar with back/menu buttons
PullToRefreshPull-down-to-refresh (emits SHOULD_REFRESH)
InfiniteScrollLoad-more-on-scroll (emits SHOULD_LOAD)

Project Setup

Vite Configuration

ts
import { defineConfig } from 'vite'
import { geaPlugin } from '@geajs/vite-plugin'

export default defineConfig({
  plugins: [geaPlugin()]
})

Entry Point

ts
import App from './app'
import './styles.css'

const app = new App()
app.render(document.getElementById('app'))

Scaffolding

bash
npm create gea@latest my-app
cd my-app
npm install
npm run dev

Released under the MIT License.