@object-ui/fields 17.2.0 → 17.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +592 -0
  2. package/dist/__tests__/cell-truncation.test.d.ts +0 -0
  3. package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
  4. package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
  5. package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
  6. package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
  7. package/dist/index.cjs +5 -5
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.js +2881 -2566
  10. package/dist/widgets/AddressField.d.ts +1 -1
  11. package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
  12. package/dist/widgets/AvatarField.d.ts +1 -1
  13. package/dist/widgets/CharacterCount.d.ts +56 -0
  14. package/dist/widgets/CheckboxesField.d.ts +1 -1
  15. package/dist/widgets/CodeField.d.ts +1 -1
  16. package/dist/widgets/ColorField.d.ts +1 -1
  17. package/dist/widgets/CurrencyField.d.ts +1 -1
  18. package/dist/widgets/EmailField.d.ts +1 -1
  19. package/dist/widgets/FileField.d.ts +1 -1
  20. package/dist/widgets/FilterConditionField.d.ts +1 -1
  21. package/dist/widgets/FullscreenFieldEditor.d.ts +155 -0
  22. package/dist/widgets/GeolocationField.d.ts +1 -1
  23. package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
  24. package/dist/widgets/ImageField.d.ts +1 -1
  25. package/dist/widgets/LocationField.d.ts +1 -1
  26. package/dist/widgets/LookupField.d.ts +1 -1
  27. package/dist/widgets/LookupField.gateHintLabel.test.d.ts +8 -0
  28. package/dist/widgets/MultiSelectField.d.ts +1 -1
  29. package/dist/widgets/ObjectField.d.ts +1 -1
  30. package/dist/widgets/ObjectRefField.d.ts +1 -1
  31. package/dist/widgets/OptionsEmptyState.d.ts +45 -0
  32. package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
  33. package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
  34. package/dist/widgets/PercentField.d.ts +1 -1
  35. package/dist/widgets/PhoneField.d.ts +1 -1
  36. package/dist/widgets/QRCodeField.d.ts +1 -1
  37. package/dist/widgets/RadioField.d.ts +1 -1
  38. package/dist/widgets/RatingField.d.ts +1 -1
  39. package/dist/widgets/RecipientPickerField.d.ts +1 -1
  40. package/dist/widgets/RecordPickerDialog.d.ts +17 -1
  41. package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +8 -0
  42. package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +8 -0
  43. package/dist/widgets/RichTextField.d.ts +19 -1
  44. package/dist/widgets/TagsField.d.ts +1 -1
  45. package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
  46. package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
  47. package/dist/widgets/TextAreaField.d.ts +44 -5
  48. package/dist/widgets/UrlField.d.ts +1 -1
  49. package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
  50. package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
  51. package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
  52. package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
  53. package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
  54. package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
  55. package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
  56. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
  57. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
  58. package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
  59. package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
  60. package/dist/widgets/toDomProps.d.ts +91 -0
  61. package/dist/widgets/types.d.ts +128 -33
  62. package/dist/withFieldCarrier.d.ts +56 -0
  63. package/package.json +13 -13
  64. package/src/__tests__/cell-truncation.test.tsx +96 -0
  65. package/src/__tests__/field-carrier-sdui.test.tsx +169 -0
  66. package/src/__tests__/spec-symbol-batch7.test.ts +29 -15
  67. package/src/__tests__/validation-feedback.test.tsx +22 -13
  68. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +265 -0
  69. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +338 -0
  70. package/src/__tests__/widget-dom-leak-e2e.test.tsx +598 -0
  71. package/src/__tests__/widget-props-contract.test.tsx +22 -9
  72. package/src/index.tsx +74 -26
  73. package/src/widgets/AddressField.tsx +31 -12
  74. package/src/widgets/AddressField.uniqueIds.test.tsx +92 -0
  75. package/src/widgets/AvatarField.tsx +9 -2
  76. package/src/widgets/BooleanField.tsx +3 -3
  77. package/src/widgets/CharacterCount.tsx +263 -0
  78. package/src/widgets/CheckboxesField.tsx +31 -14
  79. package/src/widgets/CodeField.tsx +9 -2
  80. package/src/widgets/ColorField.tsx +11 -2
  81. package/src/widgets/CurrencyField.tsx +5 -4
  82. package/src/widgets/DateField.tsx +2 -2
  83. package/src/widgets/DateTimeField.tsx +2 -2
  84. package/src/widgets/EmailField.tsx +5 -5
  85. package/src/widgets/FileField.tsx +13 -3
  86. package/src/widgets/FilterConditionField.tsx +11 -0
  87. package/src/widgets/FormulaField.tsx +1 -1
  88. package/src/widgets/FullscreenFieldEditor.tsx +271 -0
  89. package/src/widgets/GeolocationField.tsx +21 -6
  90. package/src/widgets/GeolocationField.uniqueIds.test.tsx +88 -0
  91. package/src/widgets/GridField.tsx +1 -1
  92. package/src/widgets/ImageField.tsx +9 -2
  93. package/src/widgets/LocationField.tsx +9 -2
  94. package/src/widgets/LookupField.gateHintLabel.test.tsx +135 -0
  95. package/src/widgets/LookupField.tsx +41 -6
  96. package/src/widgets/MasterDetailField.tsx +1 -1
  97. package/src/widgets/MultiSelectField.tsx +30 -14
  98. package/src/widgets/NumberField.tsx +3 -3
  99. package/src/widgets/ObjectField.tsx +17 -8
  100. package/src/widgets/ObjectRefField.tsx +10 -0
  101. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +88 -0
  102. package/src/widgets/OptionsEmptyState.test.tsx +169 -0
  103. package/src/widgets/OptionsEmptyState.tsx +83 -0
  104. package/src/widgets/PasswordField.tsx +3 -3
  105. package/src/widgets/PeoplePicker.tsx +7 -1
  106. package/src/widgets/PercentField.tsx +5 -4
  107. package/src/widgets/PhoneField.tsx +5 -5
  108. package/src/widgets/QRCodeField.tsx +9 -2
  109. package/src/widgets/RadioField.tsx +24 -14
  110. package/src/widgets/RatingField.tsx +18 -3
  111. package/src/widgets/RecipientPickerField.tsx +18 -0
  112. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +611 -0
  113. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +112 -0
  114. package/src/widgets/RecordPickerDialog.tsx +173 -8
  115. package/src/widgets/RichTextField.tsx +151 -24
  116. package/src/widgets/SelectField.tsx +48 -17
  117. package/src/widgets/SliderField.tsx +1 -1
  118. package/src/widgets/SummaryField.tsx +1 -1
  119. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +68 -0
  120. package/src/widgets/TagsField.placeholder.test.tsx +95 -0
  121. package/src/widgets/TagsField.tsx +23 -2
  122. package/src/widgets/TextAreaField.tsx +171 -93
  123. package/src/widgets/TextField.tsx +3 -3
  124. package/src/widgets/TimeField.tsx +2 -2
  125. package/src/widgets/UrlField.tsx +5 -5
  126. package/src/widgets/UserField.tsx +1 -1
  127. package/src/widgets/VectorField.tsx +1 -1
  128. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +248 -0
  129. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +116 -0
  130. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +244 -0
  131. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +195 -0
  132. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +417 -0
  133. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +287 -0
  134. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +160 -0
  135. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +161 -0
  136. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +346 -0
  137. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +246 -0
  138. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +175 -0
  139. package/src/widgets/toDomProps.ts +175 -0
  140. package/src/widgets/types.ts +135 -35
  141. package/src/widgets/useFieldTranslation.ts +60 -0
  142. package/src/withFieldCarrier.tsx +75 -0
@@ -14,4 +14,4 @@ export interface AddressValue {
14
14
  * Address field widget - provides a structured address input
15
15
  * Supports street, city, state, zip code, and country
16
16
  */
17
- export declare function AddressField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<AddressValue>): React.JSX.Element;
17
+ export declare function AddressField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<AddressValue>): React.JSX.Element;
File without changes
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * Avatar field widget - provides an avatar/profile picture uploader
5
5
  * Supports image URLs or file uploads
6
6
  */
7
- export declare function AvatarField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function AvatarField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -0,0 +1,56 @@
1
+ import { default as React } from 'react';
2
+ export interface CharacterCountProps {
3
+ /**
4
+ * Length of the text this surface is counting. The inline surface passes the
5
+ * committed value's length; the fullscreen dialog passes the DRAFT's, which
6
+ * is the number that is true of what the user is looking at.
7
+ */
8
+ length: number;
9
+ /**
10
+ * The declared cap. Callers render this component only when the field
11
+ * declares one, so it is non-optional here — a counter with no cap to count
12
+ * against has nothing to say and must not be mounted at all (an unconditional
13
+ * live region in every long-text field is the objectui#3408 defect back
14
+ * again).
15
+ */
16
+ maxLength: number;
17
+ /**
18
+ * The description node's `id`, minted by the host from `useId()`.
19
+ *
20
+ * The host MUST also name it in the edited control's `aria-describedby` —
21
+ * this component renders the sentence, only the host knows which element is
22
+ * being described, and a description nothing references is a node no screen
23
+ * reader will ever reach. That association is asserted from the OUTSIDE (the
24
+ * tests resolve `aria-describedby` and concatenate the referenced nodes' text,
25
+ * exactly as an assistive technology does) rather than trusted here.
26
+ *
27
+ * Two surfaces of one widget therefore need two ids: the dialog's draft and
28
+ * the inline value diverge the moment the user types in the dialog, so one
29
+ * shared id would point both controls at whichever sentence rendered last.
30
+ */
31
+ descriptionId: string;
32
+ /**
33
+ * Render node 3, the threshold-gated debounced `aria-live` status region.
34
+ *
35
+ * Required on purpose — see the component doc above. `false` removes the
36
+ * region from the DOM entirely (not merely empties it), which is the only
37
+ * form of "no live region" a test can tell apart from "a live region that
38
+ * happens to be silent right now".
39
+ */
40
+ announceNearLimit: boolean;
41
+ /**
42
+ * Classes for the visible digits. Required because placement is entirely the
43
+ * surface's business — inline pins them to the textarea's bottom-right
44
+ * corner, the dialog lays them out as a footer item — and a default borrowed
45
+ * from either one would position the other wrongly while looking deliberate.
46
+ */
47
+ className: string;
48
+ /**
49
+ * `data-testid` for the visible digits, namespaced per surface
50
+ * (`textarea-character-count`, `textarea-fullscreen-character-count`) so a
51
+ * test that finds digits can say WHICH surface it found them on. Same
52
+ * convention, same reason, as `FullscreenFieldEditor`'s `testIdPrefix`.
53
+ */
54
+ testId: string;
55
+ }
56
+ export declare function CharacterCount({ length, maxLength, descriptionId, announceNearLimit, className, testId, }: CharacterCountProps): React.JSX.Element;
@@ -12,4 +12,4 @@ import { FieldWidgetComponentProps } from './types';
12
12
  * first" hint while a dependency is empty, and selections no longer offered
13
13
  * (parent changed / predicate flipped) are pruned from the array.
14
14
  */
15
- export declare function CheckboxesField({ value, onChange, field, readonly, className, schema, dependentValues, dependsOn: dependsOnProp, emptyHint: _emptyHint, dataSource: _dataSource, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
15
+ export declare function CheckboxesField({ value, onChange, field, readonly, className, dependentValues, dependsOn: dependsOnProp, emptyHint, dataSource: _dataSource, error, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
@@ -5,4 +5,4 @@ import { FieldWidgetComponentProps } from './types';
5
5
  * Uses a simple textarea with monospace font
6
6
  * For advanced code editing, use the @object-ui/plugin-editor component
7
7
  */
8
- export declare function CodeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
8
+ export declare function CodeField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * Color field widget - provides a color picker input
5
5
  * Supports hex color values (e.g., #ff0000)
6
6
  */
7
- export declare function ColorField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function ColorField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldWidgetComponentProps } from './types';
3
- export declare function CurrencyField({ value, onChange, field, readonly, errorMessage, className, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
3
+ export declare function CurrencyField({ value, onChange, field, readonly, error, className, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * EmailField - Email address input with mailto link in readonly mode
5
5
  * Renders as a clickable mailto link when readonly, standard email input otherwise
6
6
  */
7
- export declare function EmailField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function EmailField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -5,7 +5,7 @@ import { FieldWidgetComponentProps } from './types';
5
5
  * Supports single and multiple file uploads with configurable accepted file types.
6
6
  * L2: File size validation, per-file progress indicators, error messages.
7
7
  */
8
- export declare function FileField({ value, onChange, field, readonly, onUploadingChange, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
8
+ export declare function FileField({ value, onChange, field, readonly, onUploadingChange, error, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
9
9
  /**
10
10
  * FileCell — compact upload control for a line-item grid cell (objectui#2360).
11
11
  *
@@ -32,5 +32,5 @@ export declare function kvToCondition(field: string, v: any, idx: number): Build
32
32
  * @internal exported for tests
33
33
  */
34
34
  export declare function isMatchAllCriteria(parsed: any): boolean;
35
- export declare function FilterConditionField({ value, onChange, readonly, className, ...props }: FieldWidgetComponentProps<string | object>): React.JSX.Element;
35
+ export declare function FilterConditionField({ value, onChange, readonly, className, error, ...props }: FieldWidgetComponentProps<string | object>): React.JSX.Element;
36
36
  export default FilterConditionField;
@@ -0,0 +1,155 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * The fullscreen-edit affordance shared by every long-text widget: the "expand"
4
+ * button plus the full-height dialog it opens.
5
+ *
6
+ * ## Why this is shared rather than copied
7
+ *
8
+ * `ObjectFormSchema.mobile.fullscreenLongText` is ONE form-level promise, and
9
+ * `ObjectForm` projects it onto EVERY long-text field as `mobile_fullscreen`
10
+ * (`field:textarea` → `TextAreaField`, `field:markdown` / `field:html` →
11
+ * `RichTextField`). A user who turns the setting on gets one behaviour, so the
12
+ * behaviour has one implementation. objectui#3301 is what the other
13
+ * arrangement costs: the flag reached `RichTextField` for as long as the
14
+ * feature has existed and that widget simply never read it, so half the
15
+ * documented promise ("textarea/rich-text get an expand button") silently did
16
+ * nothing. A second hand-written copy of the state machine is the same failure
17
+ * with an extra step — it drifts, and nothing reports the drift.
18
+ *
19
+ * What is genuinely per-widget is the EDITOR, so that is the only thing
20
+ * injected: `children` renders it against the draft. Everything a user can
21
+ * observe about the *interaction* — when the affordance appears, that the
22
+ * dialog seeds from the committed value, that typing stays local until "Done",
23
+ * that "Cancel" discards — lives here, once.
24
+ *
25
+ * ## Draft semantics (unchanged from the original `TextAreaField` behaviour)
26
+ *
27
+ * The dialog edits a LOCAL draft seeded from `value` at open time and commits
28
+ * once, on "Done". It deliberately does not stream every keystroke to the host:
29
+ * these widgets sit in a react-hook-form field, and per-keystroke `onChange`
30
+ * during a modal edit would mark the form dirty (and fire validation) for an
31
+ * edit the user may still cancel. "Cancel" therefore needs no undo — nothing
32
+ * was written.
33
+ *
34
+ * ## `disabled` (objectui#3402)
35
+ *
36
+ * The dialog is a SECOND editing surface for the same value, so a field that is
37
+ * not interactive has to be not interactive here too. It was not: hosts landed
38
+ * `disabled` on their inline control only, this component never declared the
39
+ * prop at all, and a disabled long-text field therefore sat correctly greyed out
40
+ * next to a live expand button — click it, type anything, press "Done", and the
41
+ * edit went into form state through `onCommit`. Measured on `origin/main`
42
+ * before the fix: inline `disabled=true`, toggle `disabled=false`, dialog input
43
+ * `disabled=false`, `onChange` called with "EDITED WHILE DISABLED".
44
+ *
45
+ * The gate is shaped like the built-in path's (`FullscreenTextarea` in
46
+ * `components/src/renderers/form/form.tsx`, objectui#3400/PR #3401), because
47
+ * one form-level setting must keep producing one behaviour on both paths:
48
+ *
49
+ * - the toggle STAYS but is `disabled` — `disabled` means "not interactive,
50
+ * muted", not "shown plainly", which is `readonly`'s job (and `readonly`
51
+ * never reaches this component at all — see below);
52
+ * - `openFullscreen` refuses independently of the attribute, since a
53
+ * programmatic dispatch and a lost `pointer-events` rule both get past it;
54
+ * - the dialog locks on its OWN — `disabled` is not merely a static flag, it is
55
+ * also the form's `isSubmitting`, which flips to true while the dialog may
56
+ * already be open. At that moment the toggle is no longer the gate. So the
57
+ * editor is told (third `children` argument) and "Done" is disabled;
58
+ * - and `onCommit` is gated, because that is the single point where a value can
59
+ * leave this component for host state. Nothing native guards it: it is a
60
+ * click handler on a different control reading React state.
61
+ *
62
+ * "Cancel" and `Esc` stay live in every state — a dialog that goes disabled
63
+ * mid-edit must still be closable, or a submit in flight traps the user in it.
64
+ *
65
+ * ## Why there is no `readonly` prop
66
+ *
67
+ * Both hosts early-return a read-only DISPLAY before they compute the affordance
68
+ * (`TextAreaField.tsx`, `RichTextField.tsx`), so this component is never
69
+ * rendered for a read-only field and a `readonly` prop here would be declared
70
+ * with no producer — the shape this package keeps deleting (objectui#3232/#3233).
71
+ * A future host that renders an editor for read-only fields must add the prop
72
+ * AND the producer together; do not add it "for symmetry" beforehand.
73
+ *
74
+ * ## `testIdPrefix`
75
+ *
76
+ * Each host passes its own (`textarea`, `richtext`), yielding the same
77
+ * convention with a per-widget namespace: `<prefix>-fullscreen-toggle` /
78
+ * `-dialog` / `-save`. One form can contain both a textarea and a markdown
79
+ * field, so a single shared id would make a test unable to say which widget it
80
+ * had found. The editor's own test id belongs to `children` for the same
81
+ * reason.
82
+ *
83
+ * ## Copy (objectui#3404)
84
+ *
85
+ * Every string here goes through `useFieldTranslation()` and consumes the
86
+ * EXISTING `form.fullscreen.*` / `common.cancel` keys — the same ones the
87
+ * built-in branch consumes since objectui#3272. Nothing was missing from the
88
+ * ten locale packs; this path simply never read them, so a zh session opened a
89
+ * dialog reading `Cancel` / `Done` while a long-text field rendered by the
90
+ * built-in branch **in the same form** read 「取消 / 完成」.
91
+ *
92
+ * Sharing the keys rather than minting `fields.fullscreen.*` twins is the
93
+ * point: one form-level setting (`mobile.fullscreenLongText`) projects onto
94
+ * both render paths, so two copies of this copy could only drift.
95
+ *
96
+ * `useFieldTranslation` (not bare `useObjectTranslation`) because this widget
97
+ * is rendered without an `I18nProvider` by standalone/embedded hosts and by
98
+ * every bare-render test in this package. Measured before the change: with no
99
+ * provider mounted `useObjectTranslation().t('common.cancel')` returns
100
+ * `"common.cancel"` — the raw key — and the `{{label}}` interpolation collapses
101
+ * entirely. The safe hook's English defaults are byte-identical to the literals
102
+ * they replaced, so the provider-less rendering is unchanged.
103
+ *
104
+ * Focus management, `Esc`, the overlay and the close button are Radix's, via
105
+ * the repo's `Dialog` (`@object-ui/components`) — deliberately not re-answered
106
+ * here.
107
+ */
108
+ export interface FullscreenFieldEditorProps {
109
+ /**
110
+ * The committed value. Seeds the draft each time the dialog opens — read at
111
+ * open time, not held, so a value that changes underneath while the dialog is
112
+ * closed is picked up on the next open.
113
+ */
114
+ value: string;
115
+ /** Called once with the draft when the user confirms. */
116
+ onCommit: (next: string) => void;
117
+ /** Field label — the dialog title and part of the toggle's accessible name. */
118
+ label?: string;
119
+ /** Namespace for this widget's fullscreen test ids. See above. */
120
+ testIdPrefix: string;
121
+ /**
122
+ * The host field is not interactive (objectui#3402). Disables the toggle, the
123
+ * "Done" button and the write-back, and is handed to `children` so the host's
124
+ * own editor renders disabled too. See the `disabled` section above for why
125
+ * each of those is a separate line rather than belt-and-braces.
126
+ *
127
+ * **Producer**: the widget's `disabled` prop, which the form renderer computes
128
+ * as `disabled || fieldDisabled || isSubmitting || optionGroupGated` and
129
+ * forwards to registered widgets (`stripRegisteredFieldProps` does not strip
130
+ * it).
131
+ */
132
+ disabled?: boolean;
133
+ /**
134
+ * The editor itself, rendered inside the dialog body against the draft. The
135
+ * host passes the SAME editor it renders inline, so "fullscreen" is a size
136
+ * change rather than a second, poorer editing surface.
137
+ *
138
+ * The third argument is this component's `disabled`, and the host is expected
139
+ * to put it on the control it renders: only the host knows which element its
140
+ * editor's disabled state belongs on. Ignoring it is not a write-back hole —
141
+ * `onCommit` is gated here regardless — but it does leave a control that looks
142
+ * editable while the field is not, so both in-repo hosts apply it and their
143
+ * tests pin it.
144
+ */
145
+ children: (draft: string, setDraft: (next: string) => void, disabled: boolean) => React.ReactNode;
146
+ /** Optional footer status for the draft (e.g. a character counter). */
147
+ footer?: (draft: string) => React.ReactNode;
148
+ /**
149
+ * Extra classes for the toggle button. It is absolutely positioned by
150
+ * default and expects a `relative` ancestor; a host whose layout has a
151
+ * better home for it can re-place it here.
152
+ */
153
+ toggleClassName?: string;
154
+ }
155
+ export declare function FullscreenFieldEditor({ value, onCommit, label, testIdPrefix, disabled, children, footer, toggleClassName, }: FullscreenFieldEditorProps): React.JSX.Element;
@@ -12,4 +12,4 @@ export interface GeolocationValue {
12
12
  * Geolocation field widget - provides a location picker with coordinates
13
13
  * Supports manual entry and browser geolocation API
14
14
  */
15
- export declare function GeolocationField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<GeolocationValue>): React.JSX.Element;
15
+ export declare function GeolocationField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<GeolocationValue>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * ImageField - Image upload widget with preview thumbnails
5
5
  * Supports single and multiple image uploads with drag-and-drop and preview display
6
6
  */
7
- export declare function ImageField({ value, onChange, field, readonly, onUploadingChange, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
7
+ export declare function ImageField({ value, onChange, field, readonly, onUploadingChange, error, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * LocationField - Geographic coordinate input for latitude and longitude
5
5
  * Stores location as { latitude, longitude } object and displays as comma-separated pair
6
6
  */
7
- export declare function LocationField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
7
+ export declare function LocationField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -15,4 +15,4 @@ export interface LookupOption {
15
15
  * from the referenced object using `DataSource.find()`.
16
16
  * Falls back to static `options` when no DataSource is available.
17
17
  */
18
- export declare function LookupField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
18
+ export declare function LookupField({ value, onChange, field, readonly, error: fieldError, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -14,4 +14,4 @@ import { FieldWidgetComponentProps } from './types';
14
14
  * parent first" hint while a dependency is empty, and selections no longer
15
15
  * offered (parent changed / predicate flipped) are dropped from the array.
16
16
  */
17
- export declare function MultiSelectField({ value, onChange, field, readonly, className, schema, dependentValues, dependsOn: dependsOnProp, emptyHint: _emptyHint, dataSource: _dataSource, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
17
+ export declare function MultiSelectField({ value, onChange, field, readonly, className, dependentValues, dependsOn: dependsOnProp, emptyHint, dataSource: _dataSource, error, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * ObjectField - JSON object editor
5
5
  * Allows editing structured JSON data
6
6
  */
7
- export declare function ObjectField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
7
+ export declare function ObjectField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -1,4 +1,4 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldWidgetComponentProps } from './types';
3
- export declare function ObjectRefField({ value, onChange, readonly, className, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
3
+ export declare function ObjectRefField({ value, onChange, readonly, className, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
4
4
  export default ObjectRefField;
@@ -0,0 +1,45 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * The "this option list cannot be filled" state shared by every fixed-option
4
+ * widget (`SelectField` single, `MultiSelectField`, `RadioField`,
5
+ * `CheckboxesField`) — objectui#3231.
6
+ *
7
+ * ## One producer, one consumer
8
+ *
9
+ * `emptyHint` is a declared prop on `FieldWidgetComponentProps`, computed by
10
+ * the form renderer (`form.tsx`, the `#2284` dependency gate) and forwarded to
11
+ * these widgets. **When the host supplies it, it wins**: the host resolves the
12
+ * controlling fields to their human LABELS ("Select Country first"), where a
13
+ * widget on its own only knows the raw metadata names ("Select country first").
14
+ * Absent a host hint — a standalone widget, the inline grid editor, an action
15
+ * param dialog — the widget falls back to its own copy.
16
+ *
17
+ * Each of the four widgets used to inline this box, destructure `emptyHint`
18
+ * into `_emptyHint` and throw it away, then render a hardcoded English literal.
19
+ * Four independent copies is precisely why they drifted together, so the box
20
+ * AND the copy live here only: adding a fifth option widget cannot re-introduce
21
+ * the gap without deliberately re-implementing it.
22
+ *
23
+ * ## Fallback copy is translated
24
+ *
25
+ * The fallback goes through `useFieldTranslation()` (`fields.options.*`,
26
+ * present in all ten locale packs) rather than an English string literal — the
27
+ * gate sentence is the SAME i18n key the form renderer uses, so the two can
28
+ * never say different things in the same locale.
29
+ */
30
+ export interface OptionsEmptyStateProps {
31
+ /**
32
+ * Host-computed hint. Wins over the widget's own copy whenever it is a
33
+ * non-empty string — this is the whole point of the prop.
34
+ */
35
+ emptyHint?: string;
36
+ /** The list is waiting on a `dependsOn` controlling field (vs. unconfigured). */
37
+ gated: boolean;
38
+ /** Controlling field names, used by the fallback gate sentence. */
39
+ dependsOnFields: readonly string[];
40
+ /** Widget-specific stable locator, e.g. `select-empty-${fieldName}`. */
41
+ testId?: string;
42
+ /** Widget-specific sizing (the dropdown is a fixed `h-9`, lists grow). */
43
+ className?: string;
44
+ }
45
+ export declare function OptionsEmptyState({ emptyHint, gated, dependsOnFields, testId, className, }: OptionsEmptyStateProps): React.JSX.Element;
File without changes
@@ -5,4 +5,4 @@ import { FieldWidgetComponentProps } from './types';
5
5
  * Stores values as decimals (0-1) and displays as percentages (0-100%)
6
6
  * Includes a slider for interactive control.
7
7
  */
8
- export declare function PercentField({ value, onChange, field, readonly, errorMessage, className, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
8
+ export declare function PercentField({ value, onChange, field, readonly, error, className, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * PhoneField - Telephone number input with tel link in readonly mode
5
5
  * Renders as a clickable tel link when readonly, standard phone input otherwise
6
6
  */
7
- export declare function PhoneField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function PhoneField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * QR Code field widget - generates QR codes from text
5
5
  * Uses a simple SVG-based QR code generator
6
6
  */
7
- export declare function QRCodeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function QRCodeField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -12,4 +12,4 @@ import { FieldWidgetComponentProps } from './types';
12
12
  * first" hint while a dependency is empty, and a value no longer offered (parent
13
13
  * changed / predicate flipped) is cleared.
14
14
  */
15
- export declare function RadioField({ value, onChange, field, readonly, className, schema, dependentValues, dependsOn: dependsOnProp, emptyHint: _emptyHint, dataSource: _dataSource, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
15
+ export declare function RadioField({ value, onChange, field, readonly, className, dependentValues, dependsOn: dependsOnProp, emptyHint, dataSource: _dataSource, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * Rating field widget - provides a star rating input
5
5
  * Supports numeric values from 0 to max (default 5)
6
6
  */
7
- export declare function RatingField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
7
+ export declare function RatingField({ value, onChange, field, readonly, className, error, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
@@ -1,4 +1,4 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldWidgetComponentProps } from './types';
3
- export declare function RecipientPickerField({ value, onChange, readonly, className, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
3
+ export declare function RecipientPickerField({ value, onChange, readonly, className, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
4
4
  export default RecipientPickerField;
@@ -140,6 +140,22 @@ export interface RecordPickerDialogProps {
140
140
  * Typically pass `getCellRenderer` from @object-ui/fields.
141
141
  */
142
142
  cellRenderer?: CellRendererResolver;
143
+ /**
144
+ * The referenced object's schema `fields` map (field name → field
145
+ * definition). When provided, cell renderers receive the FULL field
146
+ * metadata — `options`, `currency`, `scale`, `precision`, `format`,
147
+ * `reference_to`, … — exactly like the list view enriches its columns from
148
+ * the object schema. Without it a `select` column falls back to
149
+ * title-casing the raw stored value instead of resolving the option label
150
+ * (#3333: `manufacturing` rendered as "Manufacturing" instead of the
151
+ * authored option label).
152
+ *
153
+ * The filter bar reads the same map: a `select` filter column with no
154
+ * authored `options` takes them from the schema field here, so the filter
155
+ * panel's dropdown offers exactly the options the table cells render
156
+ * (#3336 — it used to open empty, leaving the field unfilterable).
157
+ */
158
+ fieldsMeta?: Record<string, any>;
143
159
  /**
144
160
  * Filter bar column definitions.
145
161
  * When provided, shows an inline filter bar below the search input.
@@ -185,4 +201,4 @@ export interface RecordPickerDialogProps {
185
201
  * states, and single/multi-select. Responsive: mobile-friendly width
186
202
  * via Tailwind breakpoints.
187
203
  */
188
- export declare function RecordPickerDialog({ open, onOpenChange, title, multiple, dataSource, objectName, columns: columnsProp, displayField, titleFormat, idField, pageSize, value, onSelect, onSelectRecords, lookupFilters, baseFilter, cellRenderer, filterColumns, renderFilterBar, renderGrid, }: RecordPickerDialogProps): React.JSX.Element;
204
+ export declare function RecordPickerDialog({ open, onOpenChange, title, multiple, dataSource, objectName, columns: columnsProp, displayField, titleFormat, idField, pageSize, value, onSelect, onSelectRecords, lookupFilters, baseFilter, cellRenderer, fieldsMeta, filterColumns, renderFilterBar, renderGrid, }: RecordPickerDialogProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -4,5 +4,23 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * Rich text field with markdown/HTML support
5
5
  * For now, this is a simple textarea. A full implementation would use
6
6
  * a rich text editor like TipTap, Lexical, or Slate.
7
+ *
8
+ * Reached by forms as `field:markdown` and `field:html`; both resolve here.
9
+ *
10
+ * ## Fullscreen editing (objectui#3301)
11
+ *
12
+ * `ObjectForm` stamps `mobile_fullscreen` onto the metadata of every long-text
13
+ * field — textarea AND rich-text — when `ObjectFormSchema.mobile
14
+ * .fullscreenLongText` is set, and `ObjectFormSchema.mobile`'s own JSDoc has
15
+ * always promised "textarea/rich-text get an expand button". This widget never
16
+ * read the flag, so for `field:markdown` / `field:html` that promise did
17
+ * nothing at all: the producer stamped, and no consumer existed.
18
+ *
19
+ * The flag is read off `field` and nowhere else — the single metadata carrier
20
+ * since objectui#3233 — matching `TextAreaField` read for read, so a
21
+ * misspelled flag stays inert in both widgets rather than being caught by a
22
+ * tolerant fallback in one of them. The affordance and dialog themselves come
23
+ * from the shared `FullscreenFieldEditor`, so one form-level setting keeps
24
+ * producing one behaviour across both widgets.
7
25
  */
8
- export declare function RichTextField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
26
+ export declare function RichTextField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -5,4 +5,4 @@ import { FieldWidgetComponentProps } from './types';
5
5
  * comma) to add it; click a tag's × to remove it. The stored value is a
6
6
  * string[]. Used for the `tags` field type.
7
7
  */
8
- export declare function TagsField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
8
+ export declare function TagsField({ value, onChange, field, readonly, className, error, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
File without changes
@@ -4,9 +4,48 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * TextAreaField - Multi-line text input widget
5
5
  * Supports configurable row count and preserves whitespace in readonly mode.
6
6
  *
7
- * Mobile UX (round 3): when the host form passes `mobileFullscreen` (or the
8
- * field schema sets `mobile_fullscreen: true`), an "expand" affordance opens
9
- * a fullscreen edit dialog — much easier on phones than tapping a 4-row
10
- * textarea trapped between other fields.
7
+ * Mobile UX (round 3): when the FIELD METADATA carries `mobile_fullscreen:
8
+ * true`, an "expand" affordance opens a fullscreen edit dialog — much easier
9
+ * on phones than tapping a 4-row textarea trapped between other fields.
10
+ *
11
+ * That flag has exactly one producer: `ObjectForm` stamps it onto every
12
+ * long-text field when `ObjectFormSchema.mobile.fullscreenLongText` is set
13
+ * (`plugin-form/src/ObjectForm.tsx`). It reaches this widget on `field` — the
14
+ * single metadata carrier since objectui#3233. A `SchemaRenderer`-hosted node
15
+ * arrives there too: the registry adapter (`withFieldCarrier`) maps the SDUI
16
+ * `schema` node onto `field` before the widget sees it.
17
+ *
18
+ * The affordance, the dialog and the draft/commit semantics live in the shared
19
+ * `FullscreenFieldEditor` — the same producer stamps the same flag on
20
+ * rich-text fields, and `RichTextField` renders it from there too
21
+ * (objectui#3301). Only the EDITOR differs per widget; here it is a
22
+ * full-height `Textarea`.
23
+ *
24
+ * There is deliberately NO widget-prop override. A `mobileFullscreen`
25
+ * (camelCase) prop was read here and written by nobody in the repo, and the
26
+ * snake_case `mobile_fullscreen` prop cannot arrive either: the form renderer
27
+ * strips both `mobile_fullscreen` and `fullscreen` from the props it forwards
28
+ * to registered widgets (`stripRegisteredFieldProps` in
29
+ * `components/src/renderers/form/form.tsx`). Reading keys nobody produces
30
+ * documented a contract that never held and invited the next author to pass a
31
+ * silently-ignored prop, so the reads are gone (objectui#3232). If a host
32
+ * override is ever genuinely needed, declare ONE key on
33
+ * `FieldWidgetComponentProps`, stop stripping it, and have a host pass it.
34
+ *
35
+ * ## The character counter (objectui#3408, shared across surfaces in #3417)
36
+ *
37
+ * Both editing surfaces below — the inline `<Textarea>` and the fullscreen
38
+ * dialog's — render the SAME `CharacterCount`, which owns the three-node
39
+ * GOV.UK shape (decorative digits / accessible description / gated status
40
+ * region) and the debounce and threshold behind it. Until #3417 the fullscreen
41
+ * footer had its own hand-written copy that was digits and nothing else, which
42
+ * is what a second copy of a UI always costs: it drifts, and nothing reports
43
+ * the drift.
44
+ *
45
+ * This widget's remaining job is the part only it can do — mint the two
46
+ * description ids and name each one in the `aria-describedby` of the control
47
+ * it describes. The surfaces differ in exactly one behaviour, and it is
48
+ * declared rather than implied: `announceNearLimit`. Inline announces
49
+ * (unchanged since #3408); the fullscreen dialog does not.
11
50
  */
12
- export declare function TextAreaField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
51
+ export declare function TextAreaField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types';
4
4
  * UrlField - URL input with clickable link in readonly mode
5
5
  * Validates URLs to only render http/https links for security
6
6
  */
7
- export declare function UrlField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function UrlField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;