@object-ui/fields 17.3.0 → 17.6.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 (265) hide show
  1. package/CHANGELOG.md +1655 -0
  2. package/README.md +1 -12
  3. package/dist/FieldEditWidget.d.ts +23 -3
  4. package/dist/currency.d.ts +46 -0
  5. package/dist/field-type-alias.d.ts +35 -1
  6. package/dist/index.cjs +4 -4
  7. package/dist/index.css +651 -0
  8. package/dist/index.d.ts +474 -85
  9. package/dist/index.js +5418 -5023
  10. package/dist/widgets/AddressField.d.ts +4 -12
  11. package/dist/widgets/AutoNumberField.d.ts +1 -1
  12. package/dist/widgets/AvatarField.d.ts +1 -1
  13. package/dist/widgets/BooleanField.d.ts +1 -1
  14. package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
  15. package/dist/widgets/CheckboxesField.d.ts +1 -1
  16. package/dist/widgets/CodeField.d.ts +1 -1
  17. package/dist/widgets/ColorField.d.ts +1 -1
  18. package/dist/widgets/CurrencyField.d.ts +1 -1
  19. package/dist/widgets/DateField.d.ts +1 -1
  20. package/dist/widgets/DateTimeField.d.ts +1 -1
  21. package/dist/widgets/EmailField.d.ts +1 -1
  22. package/dist/widgets/FileField.d.ts +18 -2
  23. package/dist/widgets/FilterConditionField.d.ts +28 -1
  24. package/dist/widgets/FormulaField.d.ts +1 -1
  25. package/dist/widgets/FullscreenFieldEditor.d.ts +49 -152
  26. package/dist/widgets/GeolocationField.d.ts +1 -1
  27. package/dist/widgets/GridField.d.ts +68 -6
  28. package/dist/widgets/ImageField.d.ts +1 -1
  29. package/dist/widgets/LocationField.d.ts +1 -1
  30. package/dist/widgets/LookupField.d.ts +1 -1
  31. package/dist/widgets/MasterDetailField.d.ts +1 -1
  32. package/dist/widgets/MultiSelectField.d.ts +1 -1
  33. package/dist/widgets/NumberField.d.ts +1 -1
  34. package/dist/widgets/ObjectField.d.ts +1 -1
  35. package/dist/widgets/ObjectRefField.d.ts +1 -1
  36. package/dist/widgets/OptionsEmptyState.d.ts +21 -1
  37. package/dist/widgets/PasswordField.d.ts +1 -1
  38. package/dist/widgets/PercentField.d.ts +1 -1
  39. package/dist/widgets/PhoneField.d.ts +1 -1
  40. package/dist/widgets/QRCodeField.d.ts +1 -1
  41. package/dist/widgets/RadioField.d.ts +1 -1
  42. package/dist/widgets/RatingField.d.ts +1 -1
  43. package/dist/widgets/RecipientPickerField.d.ts +1 -1
  44. package/dist/widgets/RecordPickerDialog.d.ts +34 -10
  45. package/dist/widgets/RichTextField.d.ts +37 -1
  46. package/dist/widgets/SelectField.d.ts +26 -6
  47. package/dist/widgets/SignatureField.d.ts +32 -2
  48. package/dist/widgets/SliderField.d.ts +37 -2
  49. package/dist/widgets/SummaryField.d.ts +1 -1
  50. package/dist/widgets/TagsField.d.ts +1 -1
  51. package/dist/widgets/TextAreaField.d.ts +6 -2
  52. package/dist/widgets/TextField.d.ts +1 -1
  53. package/dist/widgets/TimeField.d.ts +1 -1
  54. package/dist/widgets/UrlField.d.ts +1 -1
  55. package/dist/widgets/UserField.d.ts +1 -1
  56. package/dist/widgets/VectorField.d.ts +1 -1
  57. package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
  58. package/dist/widgets/address-format.d.ts +88 -0
  59. package/dist/widgets/file-size-guard.d.ts +41 -0
  60. package/dist/widgets/lookupColumnDisplay.d.ts +93 -0
  61. package/dist/widgets/nativeDateValue.d.ts +37 -0
  62. package/dist/widgets/richTextDisplay.d.ts +80 -0
  63. package/dist/widgets/toDomProps.d.ts +30 -10
  64. package/dist/widgets/toHostGroupProps.d.ts +189 -0
  65. package/dist/widgets/types.d.ts +8 -3
  66. package/dist/widgets/useRecordQuery.d.ts +7 -1
  67. package/package.json +17 -14
  68. package/dist/FieldEditWidget.test.d.ts +0 -8
  69. package/dist/__tests__/DateCellRenderer.test.d.ts +0 -0
  70. package/dist/__tests__/NumberCellRenderer.test.d.ts +0 -0
  71. package/dist/__tests__/cell-truncation.test.d.ts +0 -0
  72. package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
  73. package/dist/__tests__/lookupCellDisplayField.test.d.ts +0 -0
  74. package/dist/__tests__/lookupCellDisplayName.test.d.ts +0 -0
  75. package/dist/__tests__/lookupCellReferenceKey.test.d.ts +0 -0
  76. package/dist/__tests__/registry-collision.test.d.ts +0 -0
  77. package/dist/__tests__/resolveFieldCurrency.test.d.ts +0 -1
  78. package/dist/__tests__/spec-symbol-batch7.test.d.ts +0 -8
  79. package/dist/__tests__/validation-feedback.test.d.ts +0 -0
  80. package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
  81. package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
  82. package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
  83. package/dist/__tests__/widget-props-contract.test.d.ts +0 -8
  84. package/dist/coerce-safe-value.test.d.ts +0 -1
  85. package/dist/complex-widgets.test.d.ts +0 -1
  86. package/dist/datetime-widgets.test.d.ts +0 -1
  87. package/dist/field-type-coverage.test.d.ts +0 -1
  88. package/dist/standard-widgets.test.d.ts +0 -0
  89. package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
  90. package/dist/widgets/CapabilityMultiSelectField.test.d.ts +0 -0
  91. package/dist/widgets/CharacterCount.d.ts +0 -56
  92. package/dist/widgets/CheckboxesField.cascade.test.d.ts +0 -0
  93. package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
  94. package/dist/widgets/GridField.test.d.ts +0 -1
  95. package/dist/widgets/ImageLightbox.test.d.ts +0 -1
  96. package/dist/widgets/LookupField.dependsOn.test.d.ts +0 -8
  97. package/dist/widgets/LookupField.gateHintLabel.test.d.ts +0 -8
  98. package/dist/widgets/LookupField.hydration.test.d.ts +0 -1
  99. package/dist/widgets/LookupField.idField.test.d.ts +0 -1
  100. package/dist/widgets/MultiSelectField.cascade.test.d.ts +0 -0
  101. package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
  102. package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
  103. package/dist/widgets/PeoplePicker.test.d.ts +0 -8
  104. package/dist/widgets/PeoplePickerComponents.test.d.ts +0 -8
  105. package/dist/widgets/RadioField.cascade.test.d.ts +0 -0
  106. package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
  107. package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +0 -8
  108. package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +0 -8
  109. package/dist/widgets/SelectField.cascade.test.d.ts +0 -0
  110. package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
  111. package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
  112. package/dist/widgets/UserFieldPicker.test.d.ts +0 -8
  113. package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +0 -8
  114. package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +0 -8
  115. package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
  116. package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
  117. package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
  118. package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
  119. package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
  120. package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
  121. package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
  122. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
  123. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
  124. package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
  125. package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
  126. package/dist/widgets/deriveLookupColumns.test.d.ts +0 -1
  127. package/dist/widgets/file-value.test.d.ts +0 -1
  128. package/dist/widgets/personDisplay.test.d.ts +0 -8
  129. package/dist/widgets/recentLookups.test.d.ts +0 -1
  130. package/dist/widgets/useRecordQuery.test.d.ts +0 -8
  131. package/dist/widgets/useUploadingSignal.test.d.ts +0 -1
  132. package/src/FieldEditWidget.test.ts +0 -133
  133. package/src/FieldEditWidget.tsx +0 -191
  134. package/src/__tests__/DateCellRenderer.test.tsx +0 -73
  135. package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
  136. package/src/__tests__/cell-truncation.test.tsx +0 -96
  137. package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
  138. package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
  139. package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
  140. package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
  141. package/src/__tests__/registry-collision.test.ts +0 -91
  142. package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
  143. package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
  144. package/src/__tests__/validation-feedback.test.tsx +0 -361
  145. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
  146. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
  147. package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -598
  148. package/src/__tests__/widget-props-contract.test.tsx +0 -142
  149. package/src/coerce-safe-value.test.ts +0 -34
  150. package/src/complex-widgets.test.tsx +0 -908
  151. package/src/currency.ts +0 -14
  152. package/src/datetime-widgets.test.tsx +0 -93
  153. package/src/field-type-alias.ts +0 -100
  154. package/src/field-type-coverage.test.ts +0 -67
  155. package/src/index.tsx +0 -2503
  156. package/src/standard-widgets.test.tsx +0 -357
  157. package/src/widgets/AddressField.tsx +0 -132
  158. package/src/widgets/AddressField.uniqueIds.test.tsx +0 -92
  159. package/src/widgets/AutoNumberField.tsx +0 -18
  160. package/src/widgets/AvatarField.tsx +0 -117
  161. package/src/widgets/BooleanField.tsx +0 -52
  162. package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
  163. package/src/widgets/CapabilityMultiSelectField.tsx +0 -229
  164. package/src/widgets/CharacterCount.tsx +0 -263
  165. package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
  166. package/src/widgets/CheckboxesField.tsx +0 -136
  167. package/src/widgets/CodeField.tsx +0 -41
  168. package/src/widgets/ColorField.tsx +0 -53
  169. package/src/widgets/CurrencyField.tsx +0 -95
  170. package/src/widgets/DateField.tsx +0 -31
  171. package/src/widgets/DateTimeField.tsx +0 -37
  172. package/src/widgets/EmailField.tsx +0 -37
  173. package/src/widgets/FileField.tsx +0 -494
  174. package/src/widgets/FilterConditionField.tsx +0 -454
  175. package/src/widgets/FormulaField.tsx +0 -33
  176. package/src/widgets/FullscreenFieldEditor.tsx +0 -271
  177. package/src/widgets/GeolocationField.tsx +0 -167
  178. package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
  179. package/src/widgets/GridField.test.tsx +0 -406
  180. package/src/widgets/GridField.tsx +0 -966
  181. package/src/widgets/ImageCropperDialog.tsx +0 -245
  182. package/src/widgets/ImageField.tsx +0 -255
  183. package/src/widgets/ImageLightbox.test.tsx +0 -52
  184. package/src/widgets/ImageLightbox.tsx +0 -108
  185. package/src/widgets/LocationField.tsx +0 -57
  186. package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
  187. package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
  188. package/src/widgets/LookupField.hydration.test.tsx +0 -136
  189. package/src/widgets/LookupField.idField.test.tsx +0 -61
  190. package/src/widgets/LookupField.tsx +0 -1291
  191. package/src/widgets/MarkdownContent.tsx +0 -19
  192. package/src/widgets/MasterDetailField.tsx +0 -131
  193. package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
  194. package/src/widgets/MultiSelectField.tsx +0 -142
  195. package/src/widgets/NumberField.tsx +0 -49
  196. package/src/widgets/ObjectField.tsx +0 -94
  197. package/src/widgets/ObjectRefField.tsx +0 -121
  198. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
  199. package/src/widgets/OptionsEmptyState.test.tsx +0 -169
  200. package/src/widgets/OptionsEmptyState.tsx +0 -83
  201. package/src/widgets/PasswordField.tsx +0 -49
  202. package/src/widgets/PeoplePicker.test.tsx +0 -360
  203. package/src/widgets/PeoplePicker.tsx +0 -579
  204. package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
  205. package/src/widgets/PercentField.tsx +0 -95
  206. package/src/widgets/PersonRow.tsx +0 -117
  207. package/src/widgets/PhoneField.tsx +0 -37
  208. package/src/widgets/QRCodeField.tsx +0 -103
  209. package/src/widgets/RadioField.cascade.test.tsx +0 -141
  210. package/src/widgets/RadioField.tsx +0 -111
  211. package/src/widgets/RatingField.tsx +0 -81
  212. package/src/widgets/RecipientPickerField.test.tsx +0 -157
  213. package/src/widgets/RecipientPickerField.tsx +0 -188
  214. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
  215. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
  216. package/src/widgets/RecordPickerDialog.tsx +0 -1316
  217. package/src/widgets/RichTextField.tsx +0 -176
  218. package/src/widgets/SelectField.cascade.test.tsx +0 -138
  219. package/src/widgets/SelectField.tsx +0 -162
  220. package/src/widgets/SelectionTray.tsx +0 -110
  221. package/src/widgets/SignatureField.tsx +0 -140
  222. package/src/widgets/SliderField.tsx +0 -39
  223. package/src/widgets/SummaryField.tsx +0 -31
  224. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
  225. package/src/widgets/TagsField.placeholder.test.tsx +0 -95
  226. package/src/widgets/TagsField.tsx +0 -88
  227. package/src/widgets/TextAreaField.tsx +0 -213
  228. package/src/widgets/TextField.tsx +0 -45
  229. package/src/widgets/TimeField.tsx +0 -31
  230. package/src/widgets/UrlField.tsx +0 -46
  231. package/src/widgets/UserField.tsx +0 -58
  232. package/src/widgets/UserFieldPicker.test.tsx +0 -78
  233. package/src/widgets/VectorField.tsx +0 -29
  234. package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
  235. package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -142
  236. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
  237. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
  238. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
  239. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
  240. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
  241. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
  242. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
  243. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
  244. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
  245. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
  246. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
  247. package/src/widgets/_cell-renderer-bridge.ts +0 -19
  248. package/src/widgets/deriveLookupColumns.test.ts +0 -112
  249. package/src/widgets/deriveLookupColumns.ts +0 -108
  250. package/src/widgets/file-value.test.ts +0 -230
  251. package/src/widgets/file-value.ts +0 -246
  252. package/src/widgets/openNativePicker.ts +0 -18
  253. package/src/widgets/personDisplay.test.ts +0 -113
  254. package/src/widgets/personDisplay.ts +0 -92
  255. package/src/widgets/recentLookups.test.ts +0 -45
  256. package/src/widgets/recentLookups.ts +0 -47
  257. package/src/widgets/toDomProps.ts +0 -175
  258. package/src/widgets/types.ts +0 -242
  259. package/src/widgets/useCascadingOptions.ts +0 -46
  260. package/src/widgets/useFieldTranslation.ts +0 -150
  261. package/src/widgets/useRecordQuery.test.ts +0 -174
  262. package/src/widgets/useRecordQuery.ts +0 -264
  263. package/src/widgets/useUploadingSignal.test.tsx +0 -62
  264. package/src/widgets/useUploadingSignal.ts +0 -20
  265. package/src/withFieldCarrier.tsx +0 -75
@@ -1,121 +0,0 @@
1
- import React from 'react';
2
- import { Combobox, EmptyValue, cn } from '@object-ui/components';
3
- import { SchemaRendererContext } from '@object-ui/react';
4
- import type { FieldWidgetComponentProps } from './types';
5
- import { toDomProps } from './toDomProps';
6
- import { useFieldTranslation } from './useFieldTranslation';
7
-
8
- /**
9
- * ObjectRefField — object-name picker for form fields that store the machine
10
- * name of a registered object (e.g. `sys_sharing_rule.object_name`).
11
- *
12
- * Reached via the field `widget: 'object-ref'` hint (declared on the object
13
- * metadata; resolves as `field:object-ref`). Renders a searchable dropdown of
14
- * the platform's registered objects instead of a free-text input where the
15
- * admin has to remember a machine name.
16
- *
17
- * Object catalog source, in order of preference:
18
- * 1. `dataSource.getObjects()` — the metadata-registry list endpoint
19
- * (`GET /api/v1/meta/object`), which merges code- and DB-defined objects.
20
- * 2. Fallback: `dataSource.find('sys_metadata', { type: 'object' })` — the
21
- * same source the Setup app's object grid browses.
22
- * The stored value is always the object's `name` string. If the catalog can't
23
- * load, the current value is still shown, so the control degrades safely.
24
- */
25
- interface ObjectHeader {
26
- name: string;
27
- label?: string;
28
- }
29
-
30
- export function ObjectRefField({
31
- value,
32
- onChange,
33
- readonly,
34
- className,
35
- error,
36
- ...props
37
- }: FieldWidgetComponentProps<string>) {
38
- const ctx = React.useContext(SchemaRendererContext);
39
- const { t } = useFieldTranslation();
40
- const dataSource: any = props.dataSource ?? (ctx as any)?.dataSource ?? null;
41
- const disabled = props.disabled;
42
-
43
- const [objects, setObjects] = React.useState<ObjectHeader[] | null>(null);
44
-
45
- React.useEffect(() => {
46
- if (!dataSource) return;
47
- let cancelled = false;
48
- (async () => {
49
- try {
50
- let rows: ObjectHeader[] = [];
51
- if (typeof dataSource.getObjects === 'function') {
52
- rows = (await dataSource.getObjects()) ?? [];
53
- } else if (typeof dataSource.find === 'function') {
54
- const res = await dataSource.find('sys_metadata', {
55
- $filter: { type: 'object' },
56
- $top: 1000,
57
- });
58
- const list: any[] =
59
- res?.data ?? res?.records ?? (Array.isArray(res) ? res : []);
60
- rows = list
61
- .map((r) => ({
62
- name: String(r?.name ?? ''),
63
- label: r?.label != null ? String(r.label) : undefined,
64
- }))
65
- .filter((r) => r.name);
66
- }
67
- if (!cancelled) setObjects(rows);
68
- } catch {
69
- if (!cancelled) setObjects([]);
70
- }
71
- })();
72
- return () => {
73
- cancelled = true;
74
- };
75
- }, [dataSource]);
76
-
77
- const options = React.useMemo(() => {
78
- const opts = (objects ?? []).map((o) => ({
79
- value: o.name,
80
- label: o.label ? `${o.label} (${o.name})` : o.name,
81
- }));
82
- // Keep the current value selectable even if the catalog didn't include it
83
- // (unknown / not-yet-published object), so editing never drops it.
84
- if (value && !opts.some((o) => o.value === value)) {
85
- opts.unshift({ value, label: value });
86
- }
87
- return opts.sort((a, b) => a.label.localeCompare(b.label));
88
- }, [objects, value]);
89
-
90
- if (readonly) {
91
- if (!value) return <EmptyValue />;
92
- const label = options.find((o) => o.value === value)?.label ?? value;
93
- return <span className={className}>{label}</span>;
94
- }
95
-
96
- // DOM pass-through onto the combobox trigger — the widget's real focusable
97
- // control (objectui#3318). `name` is withheld: the trigger is a button, not
98
- // a submission control (same reasoning as #3306's SelectTrigger).
99
- const { name: _domName, ...triggerDomProps } = toDomProps(props);
100
-
101
- return (
102
- <Combobox
103
- {...triggerDomProps}
104
- options={options}
105
- value={value ?? ''}
106
- onValueChange={(v) => onChange(v as any)}
107
- placeholder={objects === null ? t('fields.objectRef.loading') : t('fields.objectRef.placeholder')}
108
- searchPlaceholder={t('fields.objectRef.search')}
109
- emptyText={objects === null ? t('common.loading') : t('fields.objectRef.empty')}
110
- disabled={disabled}
111
- // Fill the form column like every other input. Combobox's own
112
- // `w-[200px]` is a component default that left this control stranded at
113
- // a third of the row while `名称` beside it ran full width.
114
- className={cn('w-full', className)}
115
- // AFTER the spread so this widget's own computation wins (#3222).
116
- aria-invalid={!!error}
117
- />
118
- );
119
- }
120
-
121
- export default ObjectRefField;
@@ -1,88 +0,0 @@
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
-
9
- /**
10
- * The option widgets' empty/gate copy with NO `I18nProvider` mounted
11
- * (objectui#3231) — standalone/embedded usage, and every test in the repo that
12
- * renders a field widget bare.
13
- *
14
- * Routing the copy through `useFieldTranslation()` must not turn it into a raw
15
- * i18n key when nothing is configured: `createSafeTranslation` probes the
16
- * instance and falls back to its English defaults map. That is the behaviour
17
- * the removed literals used to provide unconditionally, so it gets its own pin.
18
- *
19
- * Why a separate FILE rather than another case in `OptionsEmptyState.test.tsx`:
20
- * mounting `I18nProvider` calls `initReactI18next`, which installs that
21
- * instance as react-i18next's GLOBAL default. Once any sibling test has done
22
- * so there is no "no provider" state left to observe in that module graph, and
23
- * the assertion would silently read the last locale mounted instead. Vitest's
24
- * per-file isolation (`isolate: true` on the `dom` project) is what makes this
25
- * file's observation honest.
26
- */
27
- import { describe, it, expect, vi } from 'vitest';
28
- import React from 'react';
29
- import { render, screen } from '@testing-library/react';
30
- import '@testing-library/jest-dom';
31
- import { SelectField } from './SelectField';
32
- import { MultiSelectField } from './MultiSelectField';
33
- import { RadioField } from './RadioField';
34
- import { CheckboxesField } from './CheckboxesField';
35
-
36
- const WIDGETS = [
37
- { label: 'SelectField', Widget: SelectField, type: 'select', testId: 'select-empty-province' },
38
- {
39
- label: 'MultiSelectField',
40
- Widget: MultiSelectField,
41
- type: 'multiselect',
42
- testId: 'multiselect-empty-province',
43
- },
44
- { label: 'RadioField', Widget: RadioField, type: 'radio', testId: 'radio-empty-province' },
45
- {
46
- label: 'CheckboxesField',
47
- Widget: CheckboxesField,
48
- type: 'checkboxes',
49
- testId: 'checkboxes-empty-province',
50
- },
51
- ] as const;
52
-
53
- const gatedField = (type: string) =>
54
- ({
55
- name: 'province',
56
- type,
57
- dependsOn: 'country',
58
- options: [{ label: 'Zhejiang', value: 'zj', visibleWhen: "record.country == 'cn'" }],
59
- }) as any;
60
-
61
- const unconfiguredField = (type: string) => ({ name: 'province', type, options: [] }) as any;
62
-
63
- function renderWidget(Widget: React.ComponentType<any>, field: any) {
64
- return render(
65
- <Widget
66
- value={undefined}
67
- onChange={vi.fn()}
68
- field={field}
69
- {...({ name: 'province', dependentValues: {} } as any)}
70
- />,
71
- );
72
- }
73
-
74
- describe('option widgets — English fallback survives with no i18n configured', () => {
75
- it.each(WIDGETS)('$label renders the gate sentence, not a raw key', ({ Widget, type, testId }) => {
76
- renderWidget(Widget, gatedField(type));
77
- const box = screen.getByTestId(testId);
78
- expect(box).toHaveTextContent('Select country first');
79
- expect(box.textContent).not.toContain('fields.options');
80
- });
81
-
82
- it.each(WIDGETS)('$label renders the empty sentence, not a raw key', ({ Widget, type, testId }) => {
83
- renderWidget(Widget, unconfiguredField(type));
84
- const box = screen.getByTestId(testId);
85
- expect(box).toHaveTextContent('No options available');
86
- expect(box.textContent).not.toContain('fields.options');
87
- });
88
- });
@@ -1,169 +0,0 @@
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
-
9
- /**
10
- * The empty / dependency-gated state of the fixed-option widgets (objectui#3231).
11
- *
12
- * Two invariants, pinned for ALL FOUR widgets — they are what regressed:
13
- *
14
- * 1. **declared ⇒ delivered.** `emptyHint` is declared on
15
- * `FieldWidgetComponentProps` and computed by the form renderer. Every one
16
- * of the four widgets used to destructure it into `_emptyHint` and drop it
17
- * on the floor, so the host's hint could never reach a user once the field
18
- * went through the `field:` registry (i.e. always, in the real console).
19
- * 2. **the fallback is translated.** With no host hint the widget renders its
20
- * own copy — which used to be a hardcoded English literal
21
- * (`'No options available'`, `` `Select ${…} first` ``) even though
22
- * `useFieldTranslation()` was already imported in one of the same files.
23
- *
24
- * All four are covered deliberately: they were four independent copies of the
25
- * same block, which is exactly why they drifted together and why one widget's
26
- * test would have proven nothing about the other three. They now share
27
- * `OptionsEmptyState`; these tests keep that true from the outside, so the
28
- * guarantee survives someone re-inlining the block.
29
- */
30
- import { describe, it, expect, vi } from 'vitest';
31
- import React from 'react';
32
- import { render, screen } from '@testing-library/react';
33
- import '@testing-library/jest-dom';
34
- import { I18nProvider } from '@object-ui/i18n';
35
- import { SelectField } from './SelectField';
36
- import { MultiSelectField } from './MultiSelectField';
37
- import { RadioField } from './RadioField';
38
- import { CheckboxesField } from './CheckboxesField';
39
-
40
- interface WidgetCase {
41
- label: string;
42
- Widget: React.ComponentType<any>;
43
- type: string;
44
- testId: string;
45
- }
46
-
47
- const WIDGETS: WidgetCase[] = [
48
- { label: 'SelectField', Widget: SelectField, type: 'select', testId: 'select-empty-province' },
49
- {
50
- label: 'MultiSelectField',
51
- Widget: MultiSelectField,
52
- type: 'multiselect',
53
- testId: 'multiselect-empty-province',
54
- },
55
- { label: 'RadioField', Widget: RadioField, type: 'radio', testId: 'radio-empty-province' },
56
- {
57
- label: 'CheckboxesField',
58
- Widget: CheckboxesField,
59
- type: 'checkboxes',
60
- testId: 'checkboxes-empty-province',
61
- },
62
- ];
63
-
64
- /** Gated: `country` is empty, so no option is offered yet (#2284). */
65
- const gatedField = (type: string) =>
66
- ({
67
- name: 'province',
68
- type,
69
- dependsOn: 'country',
70
- options: [
71
- { label: 'Zhejiang', value: 'zj', visibleWhen: "record.country == 'cn'" },
72
- { label: 'California', value: 'ca', visibleWhen: "record.country == 'us'" },
73
- ],
74
- }) as any;
75
-
76
- /** Not gated, simply nothing to offer — the other way a list ends up empty. */
77
- const unconfiguredField = (type: string) => ({ name: 'province', type, options: [] }) as any;
78
-
79
- function renderWidget(
80
- { Widget }: WidgetCase,
81
- field: any,
82
- extra: Record<string, unknown> = {},
83
- language?: string,
84
- ) {
85
- const element = (
86
- <Widget
87
- value={undefined}
88
- onChange={vi.fn()}
89
- field={field}
90
- {...({ name: 'province', dependentValues: {}, ...extra } as any)}
91
- />
92
- );
93
- return render(
94
- language ? (
95
- <I18nProvider config={{ defaultLanguage: language, detectBrowserLanguage: false }}>
96
- {element}
97
- </I18nProvider>
98
- ) : (
99
- element
100
- ),
101
- );
102
- }
103
-
104
- describe('option widgets — the host-supplied `emptyHint` reaches the user (objectui#3231)', () => {
105
- it.each(WIDGETS)(
106
- '$label renders the host hint instead of its own gate copy',
107
- (widget) => {
108
- // What the form renderer actually computes: a sentence built from the
109
- // controlling field's human LABEL, which the widget cannot know (its
110
- // metadata only carries the name `country`).
111
- renderWidget(widget, gatedField(widget.type), {
112
- emptyHint: 'Choose the Country field first',
113
- });
114
-
115
- const box = screen.getByTestId(widget.testId);
116
- expect(box).toHaveTextContent('Choose the Country field first');
117
- // …and NOT the widget's own copy, which is what used to render.
118
- expect(box.textContent).not.toMatch(/select country first/i);
119
- },
120
- );
121
-
122
- it.each(WIDGETS)(
123
- '$label renders the host hint for an unconfigured (ungated) list too',
124
- (widget) => {
125
- // `emptyHint` is a declared prop of the widget contract, not a private
126
- // channel of one form-renderer branch: supplied ⇒ used, always.
127
- renderWidget(widget, unconfiguredField(widget.type), {
128
- emptyHint: 'Options come from the price book',
129
- });
130
-
131
- expect(screen.getByTestId(widget.testId)).toHaveTextContent(
132
- 'Options come from the price book',
133
- );
134
- },
135
- );
136
-
137
- it.each(WIDGETS)('$label prefers a host hint over its translated fallback', (widget) => {
138
- // Even under a non-English locale the HOST wins — it is the single source.
139
- renderWidget(widget, gatedField(widget.type), { emptyHint: 'Pick a Country first' }, 'zh');
140
-
141
- const box = screen.getByTestId(widget.testId);
142
- expect(box).toHaveTextContent('Pick a Country first');
143
- expect(box.textContent).not.toContain('请先选择');
144
- });
145
- });
146
-
147
- describe('option widgets — the fallback copy is translated, never a hardcoded literal', () => {
148
- it.each(WIDGETS)('$label translates the gate copy when no host hint is given', (widget) => {
149
- renderWidget(widget, gatedField(widget.type), {}, 'zh');
150
-
151
- const box = screen.getByTestId(widget.testId);
152
- expect(box).toHaveTextContent('请先选择country');
153
- expect(box.textContent).not.toMatch(/select .* first/i);
154
- });
155
-
156
- it.each(WIDGETS)('$label translates the empty copy when no host hint is given', (widget) => {
157
- renderWidget(widget, unconfiguredField(widget.type), {}, 'zh');
158
-
159
- const box = screen.getByTestId(widget.testId);
160
- expect(box).toHaveTextContent('暂无可选项');
161
- expect(box.textContent).not.toMatch(/no options available/i);
162
- });
163
- });
164
-
165
- // The no-provider fallback lives in `OptionsEmptyState.no-provider.test.tsx`:
166
- // mounting `I18nProvider` here installs its instance as react-i18next's global
167
- // default (`initReactI18next`), so after any test above there is no longer a
168
- // "no provider" state to observe within this module graph.
169
-
@@ -1,83 +0,0 @@
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
-
9
- import React from 'react';
10
- import { cn } from '@object-ui/components';
11
- import { useFieldTranslation } from './useFieldTranslation';
12
-
13
- /**
14
- * The "this option list cannot be filled" state shared by every fixed-option
15
- * widget (`SelectField` single, `MultiSelectField`, `RadioField`,
16
- * `CheckboxesField`) — objectui#3231.
17
- *
18
- * ## One producer, one consumer
19
- *
20
- * `emptyHint` is a declared prop on `FieldWidgetComponentProps`, computed by
21
- * the form renderer (`form.tsx`, the `#2284` dependency gate) and forwarded to
22
- * these widgets. **When the host supplies it, it wins**: the host resolves the
23
- * controlling fields to their human LABELS ("Select Country first"), where a
24
- * widget on its own only knows the raw metadata names ("Select country first").
25
- * Absent a host hint — a standalone widget, the inline grid editor, an action
26
- * param dialog — the widget falls back to its own copy.
27
- *
28
- * Each of the four widgets used to inline this box, destructure `emptyHint`
29
- * into `_emptyHint` and throw it away, then render a hardcoded English literal.
30
- * Four independent copies is precisely why they drifted together, so the box
31
- * AND the copy live here only: adding a fifth option widget cannot re-introduce
32
- * the gap without deliberately re-implementing it.
33
- *
34
- * ## Fallback copy is translated
35
- *
36
- * The fallback goes through `useFieldTranslation()` (`fields.options.*`,
37
- * present in all ten locale packs) rather than an English string literal — the
38
- * gate sentence is the SAME i18n key the form renderer uses, so the two can
39
- * never say different things in the same locale.
40
- */
41
- export interface OptionsEmptyStateProps {
42
- /**
43
- * Host-computed hint. Wins over the widget's own copy whenever it is a
44
- * non-empty string — this is the whole point of the prop.
45
- */
46
- emptyHint?: string;
47
- /** The list is waiting on a `dependsOn` controlling field (vs. unconfigured). */
48
- gated: boolean;
49
- /** Controlling field names, used by the fallback gate sentence. */
50
- dependsOnFields: readonly string[];
51
- /** Widget-specific stable locator, e.g. `select-empty-${fieldName}`. */
52
- testId?: string;
53
- /** Widget-specific sizing (the dropdown is a fixed `h-9`, lists grow). */
54
- className?: string;
55
- }
56
-
57
- export function OptionsEmptyState({
58
- emptyHint,
59
- gated,
60
- dependsOnFields,
61
- testId,
62
- className,
63
- }: OptionsEmptyStateProps) {
64
- const { t } = useFieldTranslation();
65
- // The host's hint when it computed one; otherwise this widget's own copy,
66
- // translated. Never an English literal — that was the reported defect.
67
- const hint =
68
- emptyHint ||
69
- (gated
70
- ? t('fields.options.selectFirst', { fields: dependsOnFields.join(' / ') })
71
- : t('fields.options.empty'));
72
- return (
73
- <div
74
- data-testid={testId}
75
- className={cn(
76
- 'flex w-full items-center rounded-md border border-input bg-muted/30 px-3 py-2 text-sm text-muted-foreground',
77
- className,
78
- )}
79
- >
80
- {hint}
81
- </div>
82
- );
83
- }
@@ -1,49 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Input } from '@object-ui/components';
3
- import { Button } from '@object-ui/components';
4
- import { Eye, EyeOff } from 'lucide-react';
5
- import { FieldWidgetComponentProps } from './types';
6
- import { toDomProps } from './toDomProps';
7
-
8
- /**
9
- * PasswordField - Secure password input with visibility toggle
10
- * Includes show/hide toggle button and masks value in readonly mode
11
- */
12
- export function PasswordField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps<string>) {
13
- const [showPassword, setShowPassword] = useState(false);
14
- const config = field;
15
-
16
- if (readonly) {
17
- return <span className="text-sm">••••••••</span>;
18
- }
19
-
20
- const domProps = toDomProps(props);
21
-
22
- return (
23
- <div className="relative">
24
- <Input
25
- {...domProps}
26
- type={showPassword ? 'text' : 'password'}
27
- value={value || ''}
28
- onChange={(e) => onChange(e.target.value)}
29
- placeholder={config?.placeholder}
30
- disabled={readonly || domProps.disabled}
31
- className={`pr-10 ${className || ''}`}
32
- />
33
- <Button
34
- type="button"
35
- variant="ghost"
36
- size="sm"
37
- className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
38
- onClick={() => setShowPassword(!showPassword)}
39
- tabIndex={-1}
40
- >
41
- {showPassword ? (
42
- <EyeOff className="size-4 text-gray-500" />
43
- ) : (
44
- <Eye className="size-4 text-gray-500" />
45
- )}
46
- </Button>
47
- </div>
48
- );
49
- }