@object-ui/fields 17.1.0 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +194 -0
- package/dist/FieldEditWidget.d.ts +2 -2
- package/dist/__tests__/spec-symbol-batch7.test.d.ts +8 -0
- package/dist/__tests__/widget-props-contract.test.d.ts +8 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +290 -292
- package/dist/widgets/AddressField.d.ts +2 -2
- package/dist/widgets/AutoNumberField.d.ts +2 -2
- package/dist/widgets/AvatarField.d.ts +2 -2
- package/dist/widgets/BooleanField.d.ts +2 -2
- package/dist/widgets/CapabilityMultiSelectField.d.ts +2 -2
- package/dist/widgets/CheckboxesField.d.ts +2 -2
- package/dist/widgets/CodeField.d.ts +2 -2
- package/dist/widgets/ColorField.d.ts +2 -2
- package/dist/widgets/CurrencyField.d.ts +2 -2
- package/dist/widgets/DateField.d.ts +2 -2
- package/dist/widgets/DateTimeField.d.ts +2 -2
- package/dist/widgets/EmailField.d.ts +2 -2
- package/dist/widgets/FileField.d.ts +2 -2
- package/dist/widgets/FilterConditionField.d.ts +2 -2
- package/dist/widgets/FormulaField.d.ts +2 -2
- package/dist/widgets/GeolocationField.d.ts +2 -2
- package/dist/widgets/GridField.d.ts +2 -2
- package/dist/widgets/ImageField.d.ts +2 -2
- package/dist/widgets/LocationField.d.ts +2 -2
- package/dist/widgets/LookupField.d.ts +2 -2
- package/dist/widgets/MasterDetailField.d.ts +2 -2
- package/dist/widgets/MultiSelectField.d.ts +2 -2
- package/dist/widgets/NumberField.d.ts +2 -2
- package/dist/widgets/ObjectField.d.ts +2 -2
- package/dist/widgets/ObjectRefField.d.ts +2 -2
- package/dist/widgets/PasswordField.d.ts +2 -2
- package/dist/widgets/PercentField.d.ts +2 -2
- package/dist/widgets/PhoneField.d.ts +2 -2
- package/dist/widgets/QRCodeField.d.ts +2 -2
- package/dist/widgets/RadioField.d.ts +2 -2
- package/dist/widgets/RatingField.d.ts +2 -2
- package/dist/widgets/RecipientPickerField.d.ts +2 -2
- package/dist/widgets/RichTextField.d.ts +2 -2
- package/dist/widgets/SelectField.d.ts +2 -2
- package/dist/widgets/SignatureField.d.ts +2 -2
- package/dist/widgets/SliderField.d.ts +2 -2
- package/dist/widgets/SummaryField.d.ts +2 -2
- package/dist/widgets/TagsField.d.ts +2 -2
- package/dist/widgets/TextAreaField.d.ts +2 -2
- package/dist/widgets/TextField.d.ts +2 -2
- package/dist/widgets/TimeField.d.ts +2 -2
- package/dist/widgets/UrlField.d.ts +2 -2
- package/dist/widgets/UserField.d.ts +2 -2
- package/dist/widgets/VectorField.d.ts +2 -2
- package/dist/widgets/file-value.d.ts +20 -33
- package/dist/widgets/types.d.ts +119 -3
- package/package.json +8 -8
- package/src/FieldEditWidget.tsx +6 -4
- package/src/__tests__/spec-symbol-batch7.test.ts +112 -0
- package/src/__tests__/widget-props-contract.test.tsx +129 -0
- package/src/complex-widgets.test.tsx +3 -3
- package/src/datetime-widgets.test.tsx +2 -2
- package/src/index.tsx +3 -1
- package/src/widgets/AddressField.tsx +2 -2
- package/src/widgets/AutoNumberField.tsx +2 -2
- package/src/widgets/AvatarField.tsx +2 -2
- package/src/widgets/BooleanField.tsx +2 -2
- package/src/widgets/CapabilityMultiSelectField.tsx +4 -4
- package/src/widgets/CheckboxesField.tsx +4 -4
- package/src/widgets/CodeField.tsx +2 -2
- package/src/widgets/ColorField.tsx +2 -2
- package/src/widgets/CurrencyField.tsx +2 -2
- package/src/widgets/DateField.tsx +2 -2
- package/src/widgets/DateTimeField.tsx +2 -2
- package/src/widgets/EmailField.tsx +2 -2
- package/src/widgets/FileField.tsx +2 -2
- package/src/widgets/FilterConditionField.tsx +3 -3
- package/src/widgets/FormulaField.tsx +2 -2
- package/src/widgets/GeolocationField.tsx +2 -2
- package/src/widgets/GridField.tsx +3 -3
- package/src/widgets/ImageField.tsx +2 -2
- package/src/widgets/LocationField.tsx +2 -2
- package/src/widgets/LookupField.tsx +10 -10
- package/src/widgets/MasterDetailField.tsx +2 -2
- package/src/widgets/MultiSelectField.tsx +4 -4
- package/src/widgets/NumberField.tsx +2 -2
- package/src/widgets/ObjectField.tsx +2 -2
- package/src/widgets/ObjectRefField.tsx +4 -4
- package/src/widgets/PasswordField.tsx +2 -2
- package/src/widgets/PercentField.tsx +2 -2
- package/src/widgets/PhoneField.tsx +2 -2
- package/src/widgets/QRCodeField.tsx +2 -2
- package/src/widgets/RadioField.tsx +4 -4
- package/src/widgets/RatingField.tsx +2 -2
- package/src/widgets/RecipientPickerField.tsx +4 -4
- package/src/widgets/RichTextField.tsx +2 -2
- package/src/widgets/SelectField.tsx +5 -5
- package/src/widgets/SignatureField.tsx +2 -2
- package/src/widgets/SliderField.tsx +2 -2
- package/src/widgets/SummaryField.tsx +2 -2
- package/src/widgets/TagsField.tsx +3 -3
- package/src/widgets/TextAreaField.tsx +2 -2
- package/src/widgets/TextField.tsx +2 -2
- package/src/widgets/TimeField.tsx +2 -2
- package/src/widgets/UrlField.tsx +2 -2
- package/src/widgets/UserField.tsx +2 -2
- package/src/widgets/VectorField.tsx +2 -2
- package/src/widgets/file-value.ts +21 -5
- package/src/widgets/types.ts +129 -6
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Color field widget - provides a color picker input
|
|
7
7
|
* Supports hex color values (e.g., #ff0000)
|
|
8
8
|
*/
|
|
9
|
-
export function ColorField({ value, onChange, field, readonly, ...props }:
|
|
9
|
+
export function ColorField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>) {
|
|
10
10
|
const colorField = (field || (props as any).schema) as any;
|
|
11
11
|
|
|
12
12
|
if (readonly) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
import { useLocalization } from '@object-ui/i18n';
|
|
5
5
|
import { resolveFieldCurrency } from '../currency';
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ const CURRENCY_SYMBOLS: Record<string, string> = {
|
|
|
36
36
|
USD: '$',
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export function CurrencyField({ value, onChange, field, readonly, errorMessage, className, ...props }:
|
|
39
|
+
export function CurrencyField({ value, onChange, field, readonly, errorMessage, className, ...props }: FieldWidgetComponentProps<number>) {
|
|
40
40
|
const currencyField = (field || (props as any).schema) as any;
|
|
41
41
|
// Shared precedence: field currency → currencyConfig → tenant default (ADR-0053).
|
|
42
42
|
const { currency: tenantCurrency } = useLocalization();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
import { openNativePicker } from './openNativePicker';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* DateField - Date picker input widget
|
|
8
8
|
* Uses native date input and displays locale-formatted date in readonly mode
|
|
9
9
|
*/
|
|
10
|
-
export function DateField({ value, onChange, field, readonly, ...props }:
|
|
10
|
+
export function DateField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>) {
|
|
11
11
|
if (readonly) {
|
|
12
12
|
return value ? <span className="text-sm">{new Date(value).toLocaleDateString()}</span> : <EmptyValue />;
|
|
13
13
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
import { openNativePicker } from './openNativePicker';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* DateTimeField - Combined date and time picker widget
|
|
8
8
|
* Displays both date and time in locale format when readonly
|
|
9
9
|
*/
|
|
10
|
-
export function DateTimeField({ value, onChange, field, readonly, ...props }:
|
|
10
|
+
export function DateTimeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>) {
|
|
11
11
|
if (readonly) {
|
|
12
12
|
if (!value) return <EmptyValue />;
|
|
13
13
|
const date = new Date(value);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* EmailField - Email address input with mailto link in readonly mode
|
|
7
7
|
* Renders as a clickable mailto link when readonly, standard email input otherwise
|
|
8
8
|
*/
|
|
9
|
-
export function EmailField({ value, onChange, field, readonly, errorMessage, ...props }:
|
|
9
|
+
export function EmailField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
if (readonly) {
|
|
12
12
|
if (!value) return <EmptyValue />;
|
|
@@ -3,7 +3,7 @@ import { Button, EmptyValue } from '@object-ui/components';
|
|
|
3
3
|
import { useUpload } from '@object-ui/providers';
|
|
4
4
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
5
5
|
import { Upload, X, File as FileIcon, ImageIcon, Camera, Loader2 } from 'lucide-react';
|
|
6
|
-
import {
|
|
6
|
+
import { FieldWidgetComponentProps } from './types';
|
|
7
7
|
import { useUploadingSignal } from './useUploadingSignal';
|
|
8
8
|
import {
|
|
9
9
|
fileValueForSubmit,
|
|
@@ -115,7 +115,7 @@ function useFileUploads(opts: {
|
|
|
115
115
|
* Supports single and multiple file uploads with configurable accepted file types.
|
|
116
116
|
* L2: File size validation, per-file progress indicators, error messages.
|
|
117
117
|
*/
|
|
118
|
-
export function FileField({ value, onChange, field, readonly, onUploadingChange, ...props }:
|
|
118
|
+
export function FileField({ value, onChange, field, readonly, onUploadingChange, ...props }: FieldWidgetComponentProps<any>) {
|
|
119
119
|
const { t } = useObjectTranslation();
|
|
120
120
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
121
121
|
const cameraRef = useRef<HTMLInputElement>(null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FilterBuilder, cn } from '@object-ui/components';
|
|
3
3
|
import { SchemaRendererContext } from '@object-ui/react';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FieldWidgetComponentProps } from './types';
|
|
5
5
|
import { useFieldTranslation } from './useFieldTranslation';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -298,10 +298,10 @@ export function FilterConditionField({
|
|
|
298
298
|
readonly,
|
|
299
299
|
className,
|
|
300
300
|
...props
|
|
301
|
-
}:
|
|
301
|
+
}: FieldWidgetComponentProps<string | object>) {
|
|
302
302
|
const ctx = React.useContext(SchemaRendererContext);
|
|
303
303
|
const { t } = useFieldTranslation();
|
|
304
|
-
const dataSource: any =
|
|
304
|
+
const dataSource: any = props.dataSource ?? (ctx as any)?.dataSource ?? null;
|
|
305
305
|
const dependentValues: Record<string, any> = (props as any).dependentValues ?? {};
|
|
306
306
|
const objectName = String(dependentValues.object_name ?? '');
|
|
307
307
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* FormulaField - Read-only computed field
|
|
7
7
|
* Values are computed on the backend and cannot be edited
|
|
8
8
|
*/
|
|
9
|
-
export function FormulaField({ value, field, ...props }:
|
|
9
|
+
export function FormulaField({ value, field, ...props }: FieldWidgetComponentProps<any>) {
|
|
10
10
|
const formulaField = (field || (props as any).schema) as any;
|
|
11
11
|
const returnType = formulaField?.return_type || 'text';
|
|
12
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, Button, Label, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { MapPin, Crosshair } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Geolocation data structure
|
|
@@ -16,7 +16,7 @@ export interface GeolocationValue {
|
|
|
16
16
|
* Geolocation field widget - provides a location picker with coordinates
|
|
17
17
|
* Supports manual entry and browser geolocation API
|
|
18
18
|
*/
|
|
19
|
-
export function GeolocationField({ value, onChange, field, readonly, ...props }:
|
|
19
|
+
export function GeolocationField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<GeolocationValue>) {
|
|
20
20
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
21
21
|
const location = value || {};
|
|
22
22
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { FieldWidgetComponentProps } from './types';
|
|
3
3
|
import {
|
|
4
4
|
cn,
|
|
5
5
|
Button,
|
|
@@ -306,7 +306,7 @@ export function GridField({
|
|
|
306
306
|
displayMode,
|
|
307
307
|
onAdd,
|
|
308
308
|
...props
|
|
309
|
-
}:
|
|
309
|
+
}: FieldWidgetComponentProps<Row[]> & {
|
|
310
310
|
/** When provided, each row shows an "expand" button that opens the row in a
|
|
311
311
|
* full form (the host — e.g. MasterDetailForm — renders the drawer/modal and
|
|
312
312
|
* writes the edited values back). Lets a "fat" child be edited in a real form
|
|
@@ -327,7 +327,7 @@ export function GridField({
|
|
|
327
327
|
const cfg = (field || (props as any).schema || {}) as any;
|
|
328
328
|
const allColumns: GridColumn[] = cfg.columns || [];
|
|
329
329
|
const rows: Row[] = Array.isArray(value) ? value : [];
|
|
330
|
-
const contextRecord =
|
|
330
|
+
const contextRecord = props.contextRecord;
|
|
331
331
|
|
|
332
332
|
// Per-cell CEL rule state (B2 in grids). A column with no readonlyWhen/
|
|
333
333
|
// requiredWhen resolves to its static flags (cheap fast-path — no engine
|
|
@@ -3,7 +3,7 @@ import { Button, EmptyValue } from '@object-ui/components';
|
|
|
3
3
|
import { useUpload } from '@object-ui/providers';
|
|
4
4
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
5
5
|
import { X, Image as ImageIcon, Crop as CropIcon, Loader2 } from 'lucide-react';
|
|
6
|
-
import {
|
|
6
|
+
import { FieldWidgetComponentProps } from './types';
|
|
7
7
|
import { ImageLightbox } from './ImageLightbox';
|
|
8
8
|
import { useUploadingSignal } from './useUploadingSignal';
|
|
9
9
|
import {
|
|
@@ -24,7 +24,7 @@ const ImageCropperDialog = lazy(() =>
|
|
|
24
24
|
* ImageField - Image upload widget with preview thumbnails
|
|
25
25
|
* Supports single and multiple image uploads with drag-and-drop and preview display
|
|
26
26
|
*/
|
|
27
|
-
export function ImageField({ value, onChange, field, readonly, onUploadingChange, ...props }:
|
|
27
|
+
export function ImageField({ value, onChange, field, readonly, onUploadingChange, ...props }: FieldWidgetComponentProps<any>) {
|
|
28
28
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
29
29
|
const imageField = (field || (props as any).schema) as any;
|
|
30
30
|
const multiple = imageField?.multiple || false;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* LocationField - Geographic coordinate input for latitude and longitude
|
|
7
7
|
* Stores location as { latitude, longitude } object and displays as comma-separated pair
|
|
8
8
|
*/
|
|
9
|
-
export function LocationField({ value, onChange, field, readonly, ...props }:
|
|
9
|
+
export function LocationField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
// Location is stored as { latitude, longitude } object
|
|
12
12
|
// For display, convert to "latitude, longitude" string format
|
|
@@ -10,7 +10,7 @@ import { cn,
|
|
|
10
10
|
PopoverTrigger,
|
|
11
11
|
PopoverContent, EmptyValue } from '@object-ui/components';
|
|
12
12
|
import { Search, X, Loader2, AlertCircle, Plus, TableProperties } from 'lucide-react';
|
|
13
|
-
import {
|
|
13
|
+
import { FieldWidgetComponentProps } from './types';
|
|
14
14
|
import type { DataSource, QueryParams, LookupColumnDef } from '@object-ui/types';
|
|
15
15
|
import { RecordPickerDialog, lookupFiltersToRecord } from './RecordPickerDialog';
|
|
16
16
|
import type { RecordPickerFilterColumn } from './RecordPickerDialog';
|
|
@@ -191,7 +191,7 @@ function mapFieldTypeToFilterType(
|
|
|
191
191
|
* from the referenced object using `DataSource.find()`.
|
|
192
192
|
* Falls back to static `options` when no DataSource is available.
|
|
193
193
|
*/
|
|
194
|
-
export function LookupField({ value, onChange, field, readonly, ...props }:
|
|
194
|
+
export function LookupField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>) {
|
|
195
195
|
const [isOpen, setIsOpen] = useState(false);
|
|
196
196
|
const { t } = useFieldTranslation();
|
|
197
197
|
const listboxId = React.useId();
|
|
@@ -280,13 +280,13 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
280
280
|
}, [fieldMeta?.depends_on, fieldMeta?.dependsOn]);
|
|
281
281
|
|
|
282
282
|
// Resolve dependent field values from explicit prop or SchemaRendererContext.data
|
|
283
|
-
const dependentValuesProp =
|
|
283
|
+
const dependentValuesProp = props.dependentValues;
|
|
284
284
|
|
|
285
285
|
// Resolve DataSource: explicit prop > field-level > wrapper field > SchemaRendererContext > none
|
|
286
286
|
const ctx = useContext(SchemaRendererContext);
|
|
287
287
|
const contextDataSource = ctx?.dataSource ?? null;
|
|
288
288
|
const dataSource: DataSource | null =
|
|
289
|
-
(props as
|
|
289
|
+
(props.dataSource as DataSource | null | undefined) ?? lookupField?.dataSource ?? fieldMeta?.dataSource ?? contextDataSource;
|
|
290
290
|
|
|
291
291
|
/** Resolve dependent values from the explicit prop (preferred), the form-data
|
|
292
292
|
* context provided by @object-ui/react, or finally `ctx.data` (record scope). */
|
|
@@ -369,7 +369,7 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
369
369
|
|
|
370
370
|
// Optional create-new callback
|
|
371
371
|
const onCreateNew: ((searchQuery: string) => void) | undefined =
|
|
372
|
-
|
|
372
|
+
props.onCreateNew ?? lookupField?.onCreateNew;
|
|
373
373
|
|
|
374
374
|
// State for the full Record Picker dialog (Level 2)
|
|
375
375
|
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
@@ -657,7 +657,7 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
657
657
|
// auto-fill sibling fields from it — e.g. a line-item grid copying a product's
|
|
658
658
|
// unit_price/description when the item is chosen. When provided (single
|
|
659
659
|
// select), it drives the update and the host owns the resulting value change.
|
|
660
|
-
const onSelectRecord =
|
|
660
|
+
const onSelectRecord = props.onSelectRecord;
|
|
661
661
|
|
|
662
662
|
const handleSelect = useCallback(
|
|
663
663
|
(option: LookupOption) => {
|
|
@@ -906,7 +906,7 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
906
906
|
// Compact mode (e.g. inside a line-item grid cell): show the selected value
|
|
907
907
|
// INSIDE a borderless trigger on a single line — no chip stacked above a
|
|
908
908
|
// separate "Select…" button (which double-stacks and wastes the row height).
|
|
909
|
-
const compact = !!
|
|
909
|
+
const compact = !!props.compact;
|
|
910
910
|
const singleSelectedLabel = selectedOptions[0]?.label || selectedOptions[0]?.[displayField];
|
|
911
911
|
|
|
912
912
|
// Shared field trigger — the anchor for either the inline PeoplePicker
|
|
@@ -920,8 +920,8 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
920
920
|
compact && 'h-8 rounded-none border-0 bg-transparent px-2 shadow-none focus-visible:ring-1 focus-visible:ring-ring/60',
|
|
921
921
|
)}
|
|
922
922
|
type="button"
|
|
923
|
-
disabled={dependenciesMissing ||
|
|
924
|
-
data-testid={dependenciesMissing ? 'lookup-trigger-gated' : ((
|
|
923
|
+
disabled={dependenciesMissing || props.disabled}
|
|
924
|
+
data-testid={dependenciesMissing ? 'lookup-trigger-gated' : ((props.name || lookupField?.name) ? `lookup-trigger-${props.name || lookupField.name}` : 'lookup-trigger')}
|
|
925
925
|
title={dependenciesMissing
|
|
926
926
|
? t('lookup.selectFirst', { fields: dependsOn.map(d => d.field).join(', ') })
|
|
927
927
|
: undefined}
|
|
@@ -1214,7 +1214,7 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
1214
1214
|
type="button"
|
|
1215
1215
|
// Gated exactly like the main trigger (#2215) — pre-fix this button
|
|
1216
1216
|
// opened the full unscoped table while the dependency was missing.
|
|
1217
|
-
disabled={dependenciesMissing ||
|
|
1217
|
+
disabled={dependenciesMissing || props.disabled}
|
|
1218
1218
|
onClick={() => setIsPickerOpen(true)}
|
|
1219
1219
|
aria-label={t('lookup.browseAll')}
|
|
1220
1220
|
title={dependenciesMissing
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Button, Badge, cn } from '@object-ui/components';
|
|
3
3
|
import { Plus, X, ExternalLink } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Master-Detail relationship data structure
|
|
@@ -23,7 +23,7 @@ export function MasterDetailField({
|
|
|
23
23
|
readonly,
|
|
24
24
|
className,
|
|
25
25
|
...props
|
|
26
|
-
}:
|
|
26
|
+
}: FieldWidgetComponentProps<MasterDetailValue[]>) {
|
|
27
27
|
const items = value || [];
|
|
28
28
|
const config = field || (props as any).schema;
|
|
29
29
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { Badge, EmptyValue, cn } from '@object-ui/components';
|
|
3
3
|
import type { OptionLike } from '@object-ui/core';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
import { useCascadingOptions } from './useCascadingOptions';
|
|
6
6
|
|
|
7
7
|
interface Option extends OptionLike { color?: string }
|
|
@@ -32,11 +32,11 @@ export function MultiSelectField({
|
|
|
32
32
|
emptyHint: _emptyHint,
|
|
33
33
|
dataSource: _dataSource,
|
|
34
34
|
...props
|
|
35
|
-
}:
|
|
35
|
+
}: FieldWidgetComponentProps<string[]>) {
|
|
36
36
|
const config = (field || schema) as any;
|
|
37
37
|
const rawOptions: Option[] = config?.options || [];
|
|
38
38
|
const selected: string[] = Array.isArray(value) ? value : value == null ? [] : [value as unknown as string];
|
|
39
|
-
const fieldName =
|
|
39
|
+
const fieldName = props.name || config?.name || props.id || '';
|
|
40
40
|
|
|
41
41
|
const dependsOn = config?.dependsOn ?? dependsOnProp;
|
|
42
42
|
const { options, gated, dependsOnFields } = useCascadingOptions<Option>(
|
|
@@ -107,7 +107,7 @@ export function MultiSelectField({
|
|
|
107
107
|
type="button"
|
|
108
108
|
key={value}
|
|
109
109
|
onClick={() => toggle(value)}
|
|
110
|
-
disabled={
|
|
110
|
+
disabled={props.disabled}
|
|
111
111
|
aria-pressed={active}
|
|
112
112
|
data-testid={`multiselect-option-${opt.value}`}
|
|
113
113
|
className={cn(
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { NumberFieldMetadata } from '@object-ui/types';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* NumberField - Numeric input with optional decimal precision
|
|
8
8
|
* Supports min/max/step constraints and configurable decimal precision
|
|
9
9
|
*/
|
|
10
|
-
export function NumberField({ value, onChange, field, readonly, ...props }:
|
|
10
|
+
export function NumberField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<number>) {
|
|
11
11
|
if (readonly) {
|
|
12
12
|
return value == null ? <EmptyValue /> : <span className="text-sm">{value}</span>;
|
|
13
13
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Textarea, cn, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ObjectField - JSON object editor
|
|
7
7
|
* Allows editing structured JSON data
|
|
8
8
|
*/
|
|
9
|
-
export function ObjectField({ value, onChange, field, readonly, ...props }:
|
|
9
|
+
export function ObjectField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
|
|
12
12
|
// Initialize string state based on value
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Combobox, EmptyValue, cn } from '@object-ui/components';
|
|
3
3
|
import { SchemaRendererContext } from '@object-ui/react';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FieldWidgetComponentProps } from './types';
|
|
5
5
|
import { useFieldTranslation } from './useFieldTranslation';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -32,11 +32,11 @@ export function ObjectRefField({
|
|
|
32
32
|
readonly,
|
|
33
33
|
className,
|
|
34
34
|
...props
|
|
35
|
-
}:
|
|
35
|
+
}: FieldWidgetComponentProps<string>) {
|
|
36
36
|
const ctx = React.useContext(SchemaRendererContext);
|
|
37
37
|
const { t } = useFieldTranslation();
|
|
38
|
-
const dataSource: any =
|
|
39
|
-
const disabled =
|
|
38
|
+
const dataSource: any = props.dataSource ?? (ctx as any)?.dataSource ?? null;
|
|
39
|
+
const disabled = props.disabled;
|
|
40
40
|
|
|
41
41
|
const [objects, setObjects] = React.useState<ObjectHeader[] | null>(null);
|
|
42
42
|
|
|
@@ -2,13 +2,13 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import { Input } from '@object-ui/components';
|
|
3
3
|
import { Button } from '@object-ui/components';
|
|
4
4
|
import { Eye, EyeOff } from 'lucide-react';
|
|
5
|
-
import {
|
|
5
|
+
import { FieldWidgetComponentProps } from './types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* PasswordField - Secure password input with visibility toggle
|
|
9
9
|
* Includes show/hide toggle button and masks value in readonly mode
|
|
10
10
|
*/
|
|
11
|
-
export function PasswordField({ value, onChange, field, readonly, className, ...props }:
|
|
11
|
+
export function PasswordField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps<string>) {
|
|
12
12
|
const [showPassword, setShowPassword] = useState(false);
|
|
13
13
|
const config = field || (props as any).schema;
|
|
14
14
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, Slider, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* PercentField - Percentage input with configurable decimal precision
|
|
7
7
|
* Stores values as decimals (0-1) and displays as percentages (0-100%)
|
|
8
8
|
* Includes a slider for interactive control.
|
|
9
9
|
*/
|
|
10
|
-
export function PercentField({ value, onChange, field, readonly, errorMessage, className, ...props }:
|
|
10
|
+
export function PercentField({ value, onChange, field, readonly, errorMessage, className, ...props }: FieldWidgetComponentProps<number>) {
|
|
11
11
|
const percentField = (field || (props as any).schema) as any;
|
|
12
12
|
const precision = percentField?.precision ?? 2;
|
|
13
13
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
|
-
import {
|
|
3
|
+
import { FieldWidgetComponentProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* PhoneField - Telephone number input with tel link in readonly mode
|
|
7
7
|
* Renders as a clickable tel link when readonly, standard phone input otherwise
|
|
8
8
|
*/
|
|
9
|
-
export function PhoneField({ value, onChange, field, readonly, errorMessage, ...props }:
|
|
9
|
+
export function PhoneField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
if (readonly) {
|
|
12
12
|
if (!value) return <EmptyValue />;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input, Button, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { QrCode, Copy } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* QR Code field widget - generates QR codes from text
|
|
8
8
|
* Uses a simple SVG-based QR code generator
|
|
9
9
|
*/
|
|
10
|
-
export function QRCodeField({ value, onChange, field, readonly, ...props }:
|
|
10
|
+
export function QRCodeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>) {
|
|
11
11
|
const [showQR, setShowQR] = React.useState(false);
|
|
12
12
|
const config = field || (props as any).schema;
|
|
13
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useId, useEffect } from 'react';
|
|
2
2
|
import { RadioGroup, RadioGroupItem, Label, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { isValueStillOffered, type OptionLike } from '@object-ui/core';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
import { useCascadingOptions } from './useCascadingOptions';
|
|
6
6
|
|
|
7
7
|
type Option = OptionLike;
|
|
@@ -30,11 +30,11 @@ export function RadioField({
|
|
|
30
30
|
emptyHint: _emptyHint,
|
|
31
31
|
dataSource: _dataSource,
|
|
32
32
|
...props
|
|
33
|
-
}:
|
|
33
|
+
}: FieldWidgetComponentProps<string>) {
|
|
34
34
|
const config = (field || schema) as any;
|
|
35
35
|
const rawOptions: Option[] = config?.options || [];
|
|
36
36
|
const groupId = useId();
|
|
37
|
-
const fieldName =
|
|
37
|
+
const fieldName = props.name || config?.name || props.id || '';
|
|
38
38
|
|
|
39
39
|
const dependsOn = config?.dependsOn ?? dependsOnProp;
|
|
40
40
|
const { options, gated, dependsOnFields } = useCascadingOptions<Option>(
|
|
@@ -81,7 +81,7 @@ export function RadioField({
|
|
|
81
81
|
<RadioGroup
|
|
82
82
|
value={value ?? ''}
|
|
83
83
|
onValueChange={onChange}
|
|
84
|
-
disabled={
|
|
84
|
+
disabled={props.disabled}
|
|
85
85
|
className={className}
|
|
86
86
|
>
|
|
87
87
|
{options.map((opt) => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Star } from 'lucide-react';
|
|
3
3
|
import { cn } from '@object-ui/components';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Rating field widget - provides a star rating input
|
|
8
8
|
* Supports numeric values from 0 to max (default 5)
|
|
9
9
|
*/
|
|
10
|
-
export function RatingField({ value, onChange, field, readonly, className, ...props }:
|
|
10
|
+
export function RatingField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps<number>) {
|
|
11
11
|
// Get rating-specific configuration from field metadata
|
|
12
12
|
const ratingField = (field || (props as any).schema) as any;
|
|
13
13
|
const max = ratingField?.max ?? 5;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Combobox, EmptyValue, cn } from '@object-ui/components';
|
|
3
3
|
import { SchemaRendererContext } from '@object-ui/react';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FieldWidgetComponentProps } from './types';
|
|
5
5
|
import { useFieldTranslation } from './useFieldTranslation';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -54,11 +54,11 @@ export function RecipientPickerField({
|
|
|
54
54
|
readonly,
|
|
55
55
|
className,
|
|
56
56
|
...props
|
|
57
|
-
}:
|
|
57
|
+
}: FieldWidgetComponentProps<string>) {
|
|
58
58
|
const ctx = React.useContext(SchemaRendererContext);
|
|
59
59
|
const { t } = useFieldTranslation();
|
|
60
|
-
const dataSource: any =
|
|
61
|
-
const disabled =
|
|
60
|
+
const dataSource: any = props.dataSource ?? (ctx as any)?.dataSource ?? null;
|
|
61
|
+
const disabled = props.disabled;
|
|
62
62
|
const dependentValues: Record<string, any> = (props as any).dependentValues ?? {};
|
|
63
63
|
const recipientType = String(dependentValues.recipient_type ?? '');
|
|
64
64
|
const mapping = TYPE_TO_OBJECT[recipientType];
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Textarea, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { useObjectTranslation } from '@object-ui/react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Rich text field with markdown/HTML support
|
|
8
8
|
* For now, this is a simple textarea. A full implementation would use
|
|
9
9
|
* a rich text editor like TipTap, Lexical, or Slate.
|
|
10
10
|
*/
|
|
11
|
-
export function RichTextField({ value, onChange, field, readonly, errorMessage, ...props }:
|
|
11
|
+
export function RichTextField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>) {
|
|
12
12
|
const { t } = useObjectTranslation();
|
|
13
13
|
if (readonly) {
|
|
14
14
|
return (
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { isValueStillOffered } from '@object-ui/core';
|
|
11
11
|
import { SelectFieldMetadata } from '@object-ui/types';
|
|
12
12
|
import { useFieldTranslation } from './useFieldTranslation';
|
|
13
|
-
import {
|
|
13
|
+
import { FieldWidgetComponentProps } from './types';
|
|
14
14
|
import { MultiSelectField } from './MultiSelectField';
|
|
15
15
|
import { useCascadingOptions } from './useCascadingOptions';
|
|
16
16
|
|
|
@@ -29,12 +29,12 @@ import { useCascadingOptions } from './useCascadingOptions';
|
|
|
29
29
|
* the shared {@link useCascadingOptions} hook (#2715), so single and multi stay
|
|
30
30
|
* in lockstep.
|
|
31
31
|
*/
|
|
32
|
-
export function SelectField(props:
|
|
32
|
+
export function SelectField(props: FieldWidgetComponentProps<any>) {
|
|
33
33
|
const config = (props.field || (props as any).schema) as SelectFieldMetadata | undefined;
|
|
34
34
|
if ((config as any)?.multiple) {
|
|
35
35
|
return <MultiSelectField {...props} />;
|
|
36
36
|
}
|
|
37
|
-
return <SingleSelectField {...(props as
|
|
37
|
+
return <SingleSelectField {...(props as FieldWidgetComponentProps<string>)} />;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -59,7 +59,7 @@ function SingleSelectField({
|
|
|
59
59
|
emptyHint: _emptyHint,
|
|
60
60
|
dataSource: _dataSource,
|
|
61
61
|
...props
|
|
62
|
-
}:
|
|
62
|
+
}: FieldWidgetComponentProps<string>) {
|
|
63
63
|
const config = (field || schema) as SelectFieldMetadata;
|
|
64
64
|
const rawOptions = config?.options || [];
|
|
65
65
|
const { t } = useFieldTranslation();
|
|
@@ -67,7 +67,7 @@ function SingleSelectField({
|
|
|
67
67
|
// driven by synthetic DOM events, so e2e must target the trigger/options by a
|
|
68
68
|
// deterministic testid keyed on the field name. `props.name` is the
|
|
69
69
|
// react-hook-form field name spread in by the form renderer (FormField).
|
|
70
|
-
const fieldName =
|
|
70
|
+
const fieldName = props.name || (config as any)?.name || props.id || '';
|
|
71
71
|
|
|
72
72
|
const dependsOn = (config as any)?.dependsOn ?? dependsOnProp;
|
|
73
73
|
const { options, gated, dependsOnFields } = useCascadingOptions(
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { useRef, useEffect } from 'react';
|
|
2
2
|
import { Button } from '@object-ui/components';
|
|
3
3
|
import { Eraser } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldWidgetComponentProps } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Signature field widget - provides a signature pad for capturing signatures
|
|
8
8
|
* Outputs signature as a base64 PNG image
|
|
9
9
|
*/
|
|
10
|
-
export function SignatureField({ value, onChange, readonly }:
|
|
10
|
+
export function SignatureField({ value, onChange, readonly }: FieldWidgetComponentProps<string>) {
|
|
11
11
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
12
12
|
const [isDrawing, setIsDrawing] = React.useState(false);
|
|
13
13
|
const [isEmpty, setIsEmpty] = React.useState(!value);
|