OGE logoOGE

Headless engines

Both render layers run on the same framework-free code. You can use it without the components: query rows on a server, expand a recurrence rule in a worker, render a BPMN diagram to SVG in a build step, or put your own markup over the grid's data pipeline.

@oge-ui/core @oge-ui/behavior @oge-ui/*-engine

The three layers

Framework-free packages
PackageContainsLicence
@oge-ui/core Data: query pipeline, data sources, grouping and summaries, pivot aggregation, tree utilities, CSV, state validators MIT
@oge-ui/behavior Interaction: grid, list, overlay, editor and input machines; sanitizers; config defaults and messages; Excel / PDF builders MIT
@oge-ui/<family>-engine Charts, scheduler, Gantt, kanban, BPMN and pivot layout logic Commercial

None of them imports Angular or React (a lint rule enforces it), and their public API is snapshot-checked: @oge-ui/core and @oge-ui/behavior fail CI when a signature changes without an updated API report.

Data processing in core

Headline functions of @oge-ui/core
FunctionDoes
runLoadOptions(rows, options, config?) Filter → search → sort → group and summaries, or page — the in-memory grid query
applyFilter , applySort , applyPaging , groupRows , createFilterPredicate The pipeline steps on their own
ArrayDataSource , CustomDataSource , CursorDataSource , ODataDataSource , buildODataQuery The DataSource implementations the grid and tree list read
computePivot(input) , PivotEngine Pivot aggregation (MIT — the pivot grid UI is the commercial part)
buildCsv(rows, columns, options?) , guardCsvFormula , escapeCsvCell RFC 4180 CSV with the spreadsheet formula guard
parseStateJson , sanitizeGridStateSnapshot , sanitizeTreeListStateSnapshot , sanitizePivotGridStateSnapshot Never-throwing restore of untrusted stored state
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
import { runLoadOptions } from '@oge-ui/core';

interface Order {
  id: number;
  customer: string;
  total: number;
}

const ORDERS: Order[] = [
  { id: 1, customer: 'Ada', total: 120 },
  { id: 2, customer: 'Grace', total: 80 },
  { id: 3, customer: 'Linus', total: 310 },
];

@Component({
  selector: 'demo-root',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <!-- your own markup over the grid's query engine -->
    <ul>
      @for (order of rows(); track order.id) {
        <li>{{ order.customer }} — {{ order.total }}</li>
      }
    </ul>
    <p>{{ page().totalCount }} orders of 100 or more</p>
  `,
})
export class Demo {
  // the same filter → search → sort → page pipeline the data grid runs in memory
  protected readonly page = computed(() =>
    runLoadOptions(ORDERS, {
      filter: { type: 'binary', field: 'total', op: 'ge', value: 100 },
      sort: [{ field: 'total', dir: 'desc' }],
      skip: 0,
      take: 20,
      requireTotalCount: true,
    }),
  );

  // no `group` in the query, so the result is rows, not group items
  protected readonly rows = computed(() => this.page().data as readonly Order[]);
}
import { buildCsv, parseStateJson, sanitizeGridStateSnapshot } from '@oge-ui/core';

// CSV with the formula guard on (=cmd|… is written as text, not evaluated)
const csv = buildCsv(orders, [
  { caption: 'Customer', accessor: (row) => row.customer },
  { caption: 'Total', accessor: (row) => row.total },
]);

// restore a stored grid state without trusting it
const state = sanitizeGridStateSnapshot(parseStateJson(localStorage.getItem('orders') ?? ''));

Interaction cores in behavior

@oge-ui/behavior holds the machines both render layers share — keyboard models, selection, virtualization windows, the grid's state and data cores, masks, the editor's document model — plus the message catalogs and config defaults. Machines that hold state take an OgeReactivityAdapter, which is how one implementation runs on Angular signals and on React:

// @oge-ui/behavior — how a framework-free machine holds reactive state
export interface OgeReactiveCell<T> {
  (): T;
  set(value: T): void;
}

export interface OgeReactivityAdapter {
  cell<T>(initial: T): OgeReactiveCell<T>;
  derived<T>(compute: () => T): () => T;
}

// Angular backs it with signal() / computed(); React with a store plus
// change notification; a test can use plain closures.
Selected behavior exports
ExportShape
sanitizeUrl(url, options?) , sanitizeResourceUrl(url) Scheme allowlist for href / src
ogeSanitizeEditorHtml(html, options?) The rich-text editor allowlist as a string → string function (browser or Node)
new OgeMaskCore({ mask, rules?, maskChar? }) Input mask engine (not reactive)
new OgeGridStateCore(rx) , new OgeGridDataCore(deps, rx) The grid state slices and its loading machine
new OgeSelectListCore(deps, rx) , OgeListVirtualizerCore Select-family list model and its virtualizer
prefersReducedMotion() , ogeResolveDirection() , observeDirection() Motion and direction helpers

These are the layer the components are built from, and their constructors take the dependencies a render layer wires up. For an app, the stand-alone functions (sanitizers, the editor HTML model, motion and direction helpers) are the practical entry; the stateful cores are for building a render layer of your own.

Family engines

Each commercial family keeps its engine in its own package; the Angular and the React package of the family both depend on it, so installing either brings it along.

import { expandRecurrence, parseRecurrenceRule } from '@oge-ui/scheduler-engine';

// RFC 5545 subset: FREQ, INTERVAL, COUNT / UNTIL, BYDAY, BYMONTHDAY, BYMONTH, BYSETPOS, …
const rule = parseRecurrenceRule('FREQ=WEEKLY;BYDAY=MO,WE;COUNT=6');
const dates = rule
  ? expandRecurrence(rule, new Date(2026, 9, 5, 9), new Date(2026, 9, 1), new Date(2026, 10, 1))
  : [];
import { readBpmnXml, renderDiagramSvg, writeBpmnXml } from '@oge-ui/bpmn-engine';

// on a server too: import, validate, render a static SVG, write normalized XML
const { model, warnings } = readBpmnXml(xml);
if (model) {
  const svg = renderDiagramSvg(model);
  const normalized = writeBpmnXml(model); // byte-stable: import → export → import is equal
}
import { createLinearScale, niceTicks } from '@oge-ui/charts-engine';

niceTicks(0, 87, 5); // round tick values for an axis
const y = createLinearScale({ min: 0, max: 100, rangePx: 320, inverted: true });
y.toPx(25); // domain value → pixel offset; y.ticks holds the tick values
Engine packages and headline exports
PackageHeadline exports
@oge-ui/scheduler-engineparseRecurrenceRule , serializeRecurrenceRule , expandRecurrence , OgeSchedulerCore ; iCalendar export entry
@oge-ui/gantt-enginescheduleGanttProject , criticalPathKeys , autoScheduleForward , buildGanttScale ; Excel, PDF, image and MS Project export entries
@oge-ui/bpmn-enginereadBpmnXml , writeBpmnXml , renderDiagramSvg , toBpmnJson / fromBpmnJson , OgeBpmnEditorCore
@oge-ui/charts-engineniceTicks , createLinearScale , buildCartesianData , buildCartesianScene , buildPieScene ; image and PDF export entries
@oge-ui/kanban-enginenormalizeCards , groupBoard , kanbanColumnWip , computeColumnWindow
@oge-ui/pivot-enginetoChartSeries , OgePivotGridCore , OgePivotStateCore ; Excel and PDF export entries

Licensing

  • @oge-ui/core and @oge-ui/behavior are MIT, and will stay MIT.
  • The six @oge-ui/<family>-engine packages carry their family's commercial licence, exactly like the family's Angular and React packages: free for evaluation, development and testing; production use needs a paid licence — using the engine without the components is still use of the family.
  • There is no licence key, runtime check, watermark or network call in any package; the licence is enforced by its text (ADR 0003). Terms: Licensing.