Skip to content

Latest commit

 

History

History
153 lines (119 loc) · 5.09 KB

File metadata and controls

153 lines (119 loc) · 5.09 KB
title Field Registry

Object UI uses a Field Registry system to decouple the core engine from specific UI implementations of fields. This allows for rich extensibility and plugin support.

Concept

The @object-ui/fields package serves as the "Universal Language" for rendering values.

When a component like <ObjectGrid> needs to render a date field, it doesn't import a DatePicker directly. Instead, it asks the registry:

"Hey, give me the component responsible for rendering type 'date'."

This architecture allows you to:

  1. Override standard fields (e.g. replace the native date picker with a fancy one).
  2. Add new field types (e.g. add a rating or signature field).
  3. Keep bundles small (heavy components like Code Editors are loaded only if their plugin is registered).

Usage

1. Registering a Custom Field

You can register a custom renderer globally, typically at your app's entry point.

// src/setup.tsx
import { registerFieldRenderer, type CellRendererProps } from '@object-ui/fields';

const MyRatingField = ({ value, onChange }: CellRendererProps) => {
  return (
    <div className="rating">
      {[1, 2, 3, 4, 5].map(star => (
        <span 
          key={star} 
          onClick={() => onChange?.(star)}
          style={{ color: star <= value ? 'gold' : 'grey' }}
        ></span>
      ))}
    </div>
  );
};

// Register it
registerFieldRenderer('rating', MyRatingField);

2. Using in Schema

Once registered, you can simply use the new type in your JSON schema.

{
  "type": "form",
  "fields": [
    {
      "name": "customer_satisfaction",
      "type": "rating", 
      "label": "Satisfaction"
    }
  ]
}

Standard Fields

Object UI comes with built-in support for the standard ObjectStack Protocol types:

Type Description
text Single line text
textarea Multi-line text
number Numeric input
currency Currency formatting
percent Percentage values
date Date picker
datetime Date & Time picker
boolean Checkbox / Switch
select Dropdown
lookup Reference to another object
master_detail Parent-child relationship
user Person picker — searches the sys_user object (a lookup specialized to users)
owner Record owner — a user field, typically read-only and stamped with the current user

What a number field silently rewrites

number, currency, percent and geolocation render a native type="number" input. The browser — not ObjectUI — decides what that box will accept, and it rewrites some entries before any widget code runs. Two different things can happen, and only one of them is announced.

Announced: text the browser cannot read

If the box is left holding something that is not a complete number, the browser reports validity.badInput and these widgets now say so: the control is marked aria-invalid="true" and a message is drawn under it —

Not saved: the text in this box is not a number. Enter a plain decimal (example: 1234.56).

Measured in Chromium 141, typing any of 1e, 1e-, 1e+, 5e, -, ., +, -. or e leaves the box visibly displaying what was typed while its value reads empty. Before this was announced, the field simply stored nothing and said nothing.

⚠️ NOT announced: entries the browser silently truncates

This is the important limitation, and it is deliberate rather than an oversight.

you paste / type the field stores
1.2.3 1.23
0x10 10
12abc 12

No warning is shown for these, and no widget-side check can add one. The browser filters the keystrokes or the pasted text as it arrives, so by the time ObjectUI sees the field the discarded characters are already gone — there is nothing left to detect. This is native type="number" behaviour; recovering it would mean giving up the numeric keyboard on mobile and the min/max/step spinner on every numeric field in the product.

So do not read "no warning" as "the value is correct." A warning means the browser could not read the box at all. Silence means the browser read something — which may be less than you typed. When exact input matters (reference codes, serial numbers, anything where 1.2.3 is meaningful), declare a text field, not a numeric one.

Using Renderers in Custom Components

If you are building your own custom component (like a Kanban board card), you can leverage the registry to render fields without reinventing the wheel.

import { getCellRenderer } from '@object-ui/fields';

export const KanbanCard = ({ task }: { task: { name: string; assignee: string } }) => {
  // Get the standard renderer for a 'user' type field
  const UserRenderer = getCellRenderer('user');
  
  return (
    <div className="card">
      <h3>{task.name}</h3>
      <div className="assignee">
        <UserRenderer 
          value={task.assignee} 
          field={{ type: 'user', name: 'assignee' }} 
        />
      </div>
    </div>
  );
};