@object-ui/fields 5.1.1 → 5.3.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 +105 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +318 -298
- package/dist/packages/fields/src/index.d.ts +8 -1
- package/package.json +7 -7
- package/src/index.tsx +48 -19
- package/src/widgets/RichTextField.tsx +10 -3
|
@@ -23,12 +23,19 @@ export interface CellRendererProps {
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function coerceToSafeValue(value: unknown): string | number | boolean | null | undefined;
|
|
25
25
|
/**
|
|
26
|
-
* Format currency value
|
|
26
|
+
* Format currency value. When `currency` is undefined, falls back to a
|
|
27
|
+
* plain number with thousands separators (no symbol). Silently assuming
|
|
28
|
+
* USD for unconfigured currency fields was the #1 source of "why is my
|
|
29
|
+
* RMB amount showing as dollars?" bug reports.
|
|
30
|
+
*
|
|
31
|
+
* Trailing `.00` is dropped when the value is a whole number — Salesforce
|
|
32
|
+
* convention: `$1,234.50` keeps cents; `$1,234` does not.
|
|
27
33
|
*/
|
|
28
34
|
export declare function formatCurrency(value: number, currency?: string): string;
|
|
29
35
|
/**
|
|
30
36
|
* Format currency value in compact form for mobile display.
|
|
31
37
|
* E.g., $150,000 → $150K, $1,200,000 → $1.2M
|
|
38
|
+
* When `currency` is undefined, returns a compact number without symbol.
|
|
32
39
|
*/
|
|
33
40
|
export declare function formatCompactCurrency(value: number, currency?: string): string;
|
|
34
41
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/fields",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Field renderers and registry for Object UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"clsx": "^2.1.1",
|
|
28
28
|
"lucide-react": "^1.16.0",
|
|
29
29
|
"tailwind-merge": "^3.6.0",
|
|
30
|
-
"@object-ui/components": "5.
|
|
31
|
-
"@object-ui/core": "5.
|
|
32
|
-
"@object-ui/i18n": "5.
|
|
33
|
-
"@object-ui/providers": "5.
|
|
34
|
-
"@object-ui/react": "5.
|
|
35
|
-
"@object-ui/types": "5.
|
|
30
|
+
"@object-ui/components": "5.3.0",
|
|
31
|
+
"@object-ui/core": "5.3.0",
|
|
32
|
+
"@object-ui/i18n": "5.3.0",
|
|
33
|
+
"@object-ui/providers": "5.3.0",
|
|
34
|
+
"@object-ui/react": "5.3.0",
|
|
35
|
+
"@object-ui/types": "5.3.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/index.tsx
CHANGED
|
@@ -222,24 +222,49 @@ export function coerceToSafeValue(value: unknown): string | number | boolean | n
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
|
-
* Format currency value
|
|
225
|
+
* Format currency value. When `currency` is undefined, falls back to a
|
|
226
|
+
* plain number with thousands separators (no symbol). Silently assuming
|
|
227
|
+
* USD for unconfigured currency fields was the #1 source of "why is my
|
|
228
|
+
* RMB amount showing as dollars?" bug reports.
|
|
229
|
+
*
|
|
230
|
+
* Trailing `.00` is dropped when the value is a whole number — Salesforce
|
|
231
|
+
* convention: `$1,234.50` keeps cents; `$1,234` does not.
|
|
226
232
|
*/
|
|
227
|
-
export function formatCurrency(value: number, currency
|
|
233
|
+
export function formatCurrency(value: number, currency?: string): string {
|
|
234
|
+
const isWhole = Number.isFinite(value) && value === Math.trunc(value);
|
|
235
|
+
const maxFrac = isWhole ? 0 : 2;
|
|
236
|
+
if (!currency) {
|
|
237
|
+
return formatNumber(value, maxFrac);
|
|
238
|
+
}
|
|
228
239
|
try {
|
|
229
240
|
return new Intl.NumberFormat('en-US', {
|
|
230
241
|
style: 'currency',
|
|
231
242
|
currency,
|
|
243
|
+
minimumFractionDigits: 0,
|
|
244
|
+
maximumFractionDigits: maxFrac,
|
|
232
245
|
}).format(value);
|
|
233
246
|
} catch {
|
|
234
|
-
return `${currency} ${value.toFixed(
|
|
247
|
+
return `${currency} ${value.toFixed(maxFrac)}`;
|
|
235
248
|
}
|
|
236
249
|
}
|
|
237
250
|
|
|
238
251
|
/**
|
|
239
252
|
* Format currency value in compact form for mobile display.
|
|
240
253
|
* E.g., $150,000 → $150K, $1,200,000 → $1.2M
|
|
254
|
+
* When `currency` is undefined, returns a compact number without symbol.
|
|
241
255
|
*/
|
|
242
|
-
export function formatCompactCurrency(value: number, currency
|
|
256
|
+
export function formatCompactCurrency(value: number, currency?: string): string {
|
|
257
|
+
if (!currency) {
|
|
258
|
+
try {
|
|
259
|
+
const formatted = new Intl.NumberFormat('en-US', {
|
|
260
|
+
notation: 'compact',
|
|
261
|
+
maximumFractionDigits: 1,
|
|
262
|
+
}).format(value);
|
|
263
|
+
return formatted.replace(/\.0(?=[KMBT])/, '');
|
|
264
|
+
} catch {
|
|
265
|
+
return String(value);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
243
268
|
try {
|
|
244
269
|
const formatted = new Intl.NumberFormat('en-US', {
|
|
245
270
|
style: 'currency',
|
|
@@ -340,9 +365,14 @@ export function formatDate(value: string | Date | number, style?: string): strin
|
|
|
340
365
|
return formatRelativeDate(date);
|
|
341
366
|
}
|
|
342
367
|
|
|
343
|
-
// Default format: locale-aware human-readable
|
|
368
|
+
// Default format: locale-aware human-readable. Drop the year when it
|
|
369
|
+
// matches the current year — Salesforce / HubSpot / Linear all do this
|
|
370
|
+
// because the year is rarely useful for in-progress records and the
|
|
371
|
+
// verbose "2026年7月21日" form crowds cards and table cells. Past- /
|
|
372
|
+
// future-year dates keep the year so users can disambiguate.
|
|
373
|
+
const isCurrentYear = date.getFullYear() === new Date().getFullYear();
|
|
344
374
|
return date.toLocaleDateString(undefined, {
|
|
345
|
-
year: 'numeric',
|
|
375
|
+
year: isCurrentYear ? undefined : 'numeric',
|
|
346
376
|
month: 'short',
|
|
347
377
|
day: 'numeric',
|
|
348
378
|
});
|
|
@@ -399,24 +429,23 @@ export function CurrencyCellRenderer({ value, field }: CellRendererProps): React
|
|
|
399
429
|
|
|
400
430
|
const safe = coerceToSafeValue(value);
|
|
401
431
|
const currencyField = field as any;
|
|
402
|
-
// Honor
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
const currency: string =
|
|
409
|
-
currencyField.currency ||
|
|
432
|
+
// Honor `currency` (legacy/grid configs), `defaultCurrency` (canonical
|
|
433
|
+
// top-level), and `currencyConfig.defaultCurrency` (the nested shape
|
|
434
|
+
// emitted by `@objectstack/spec` Field.currency() with currencyConfig).
|
|
435
|
+
// When none is supplied, render as a plain formatted number —
|
|
436
|
+
// silently assuming USD for unconfigured currency fields was
|
|
437
|
+
// misleading for non-USD orgs (e.g. RMB amounts displayed as $).
|
|
438
|
+
const currency: string | undefined =
|
|
439
|
+
currencyField.currency ||
|
|
440
|
+
currencyField.defaultCurrency ||
|
|
441
|
+
currencyField.currencyConfig?.defaultCurrency ||
|
|
442
|
+
undefined;
|
|
410
443
|
const num = Number(safe);
|
|
411
|
-
const decimals = currencyField.precision ?? currencyField.scale ?? currencyField.decimals ?? 2;
|
|
412
444
|
const formatted = !isNaN(num)
|
|
413
445
|
? formatCurrency(num, currency)
|
|
414
446
|
: String(safe);
|
|
415
|
-
// Hide trailing fractional digits when the field opted into integer-only
|
|
416
|
-
// precision (e.g. annual_revenue stored as whole units).
|
|
417
|
-
const display = decimals === 0 ? formatted.replace(/[.,]00$/, '') : formatted;
|
|
418
447
|
|
|
419
|
-
return <span className="tabular-nums font-medium whitespace-nowrap">{
|
|
448
|
+
return <span className="tabular-nums font-medium whitespace-nowrap">{formatted}</span>;
|
|
420
449
|
}
|
|
421
450
|
|
|
422
451
|
// Fields that store percentage values as whole numbers (0-100) rather than fractions (0-1)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Textarea, EmptyValue } from '@object-ui/components';
|
|
3
|
+
import { useObjectTranslation } from '@object-ui/react';
|
|
3
4
|
import { FieldWidgetProps } from './types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -8,6 +9,7 @@ import { FieldWidgetProps } from './types';
|
|
|
8
9
|
* a rich text editor like TipTap, Lexical, or Slate.
|
|
9
10
|
*/
|
|
10
11
|
export function RichTextField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>) {
|
|
12
|
+
const { t } = useObjectTranslation();
|
|
11
13
|
if (readonly) {
|
|
12
14
|
return (
|
|
13
15
|
<div
|
|
@@ -25,13 +27,18 @@ export function RichTextField({ value, onChange, field, readonly, errorMessage,
|
|
|
25
27
|
return (
|
|
26
28
|
<div className="space-y-2">
|
|
27
29
|
<div className="flex items-center justify-between text-xs text-gray-500">
|
|
28
|
-
<span>Format: {format}</span>
|
|
29
|
-
<span className="italic">
|
|
30
|
+
<span>{t('fields.richText.format', { format, defaultValue: `Format: ${format}` })}</span>
|
|
31
|
+
<span className="italic">
|
|
32
|
+
{t('fields.richText.basicEditorHint', { defaultValue: 'Rich text editor (basic)' })}
|
|
33
|
+
</span>
|
|
30
34
|
</div>
|
|
31
35
|
<Textarea
|
|
32
36
|
value={value || ''}
|
|
33
37
|
onChange={(e) => onChange(e.target.value)}
|
|
34
|
-
placeholder={
|
|
38
|
+
placeholder={
|
|
39
|
+
richField?.placeholder ||
|
|
40
|
+
t('fields.richText.placeholder', { defaultValue: 'Enter text...' })
|
|
41
|
+
}
|
|
35
42
|
disabled={readonly || props.disabled}
|
|
36
43
|
rows={rows}
|
|
37
44
|
className={`font-mono text-sm ${props.className || ''}`}
|