@object-ui/fields 17.5.0 → 17.7.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 +6150 -0
- package/README.md +108 -8
- package/dist/FieldEditWidget.d.ts +70 -8
- package/dist/coerceToSafeValue.d.ts +54 -0
- package/dist/currency.d.ts +1 -1
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +5 -5
- package/dist/index.css +53 -11
- package/dist/index.d.ts +653 -128
- package/dist/index.js +4275 -3171
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +20 -4
- package/dist/widgets/BooleanField.d.ts +2 -2
- package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
- package/dist/widgets/CheckboxesField.d.ts +1 -1
- package/dist/widgets/CodeField.d.ts +1 -1
- package/dist/widgets/ColorField.d.ts +1 -1
- package/dist/widgets/CurrencyField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +2 -2
- package/dist/widgets/DateTimeField.d.ts +2 -2
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +43 -2
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +10 -4
- package/dist/widgets/GridField.d.ts +102 -86
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +31 -4
- package/dist/widgets/LookupField.d.ts +9 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +2 -2
- package/dist/widgets/ObjectField.d.ts +10 -3
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +38 -8
- package/dist/widgets/PasswordField.d.ts +15 -1
- package/dist/widgets/PeoplePicker.d.ts +17 -2
- package/dist/widgets/PercentField.d.ts +3 -2
- package/dist/widgets/PersonRow.d.ts +13 -2
- package/dist/widgets/PhoneField.d.ts +1 -1
- package/dist/widgets/QRCodeField.d.ts +1 -1
- package/dist/widgets/RadioField.d.ts +1 -1
- package/dist/widgets/RatingField.d.ts +1 -1
- package/dist/widgets/RecipientPickerField.d.ts +44 -1
- package/dist/widgets/RecordPickerDialog.d.ts +33 -16
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- package/dist/widgets/SelectionTray.d.ts +13 -2
- package/dist/widgets/SignatureField.d.ts +32 -2
- package/dist/widgets/SliderField.d.ts +37 -2
- package/dist/widgets/SummaryField.d.ts +1 -1
- package/dist/widgets/TagsField.d.ts +1 -1
- package/dist/widgets/TextAreaField.d.ts +6 -2
- package/dist/widgets/TextField.d.ts +2 -2
- package/dist/widgets/TimeField.d.ts +2 -2
- package/dist/widgets/UrlField.d.ts +1 -1
- package/dist/widgets/UserField.d.ts +1 -1
- package/dist/widgets/VectorField.d.ts +1 -1
- package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
- package/dist/widgets/address-format.d.ts +27 -7
- package/dist/widgets/file-affordance.d.ts +33 -0
- package/dist/widgets/file-size-guard.d.ts +5 -23
- package/dist/widgets/file-value.d.ts +38 -9
- package/dist/widgets/lookupColumnDisplay.d.ts +110 -0
- package/dist/widgets/nativeDateValue.d.ts +9 -27
- package/dist/widgets/number-format.d.ts +137 -0
- package/dist/widgets/numberBadInput.d.ts +135 -0
- package/dist/widgets/personDisplay.d.ts +22 -4
- package/dist/widgets/richTextDisplay.d.ts +116 -0
- package/dist/widgets/toDomProps.d.ts +10 -1
- package/dist/widgets/toHostGroupProps.d.ts +172 -21
- package/dist/widgets/toHostProps.d.ts +105 -0
- package/dist/widgets/types.d.ts +19 -6
- package/dist/widgets/upload-error-message.d.ts +6 -0
- package/dist/widgets/uploadingScope.d.ts +142 -0
- package/dist/widgets/useCascadingOptions.d.ts +3 -2
- package/dist/widgets/useRecordQuery.d.ts +4 -2
- package/dist/widgets/useUploadingSignal.d.ts +8 -0
- package/package.json +11 -13
- package/dist/widgets/CharacterCount.d.ts +0 -56
- package/src/FieldEditWidget.test.ts +0 -133
- package/src/FieldEditWidget.tsx +0 -207
- package/src/__tests__/AddressCellRenderer.test.tsx +0 -166
- package/src/__tests__/CurrencyField.minorUnits.test.tsx +0 -191
- package/src/__tests__/CurrencyField.symbol.test.tsx +0 -198
- package/src/__tests__/DateCellRenderer.test.tsx +0 -82
- package/src/__tests__/NumberCellRenderer.grouping.test.tsx +0 -146
- package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
- package/src/__tests__/PercentCellRenderer.locale.test.tsx +0 -152
- package/src/__tests__/PercentCellRenderer.test.tsx +0 -115
- package/src/__tests__/boolean-label-association-e2e.test.tsx +0 -298
- package/src/__tests__/capability-multiselect-retired.test.ts +0 -109
- package/src/__tests__/cell-truncation.test.tsx +0 -96
- package/src/__tests__/composite-group-label-e2e.test.tsx +0 -436
- package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +0 -403
- package/src/__tests__/date-formatter-residue-4272.test.ts +0 -120
- package/src/__tests__/date-locale-channel.test.tsx +0 -305
- package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
- package/src/__tests__/formatCurrency.centsDigit.test.tsx +0 -158
- package/src/__tests__/formatCurrency.minorUnits.test.tsx +0 -221
- package/src/__tests__/group-labelling-declaration.test.ts +0 -148
- package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
- package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
- package/src/__tests__/lookupCellRecordLink.test.tsx +0 -171
- package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
- package/src/__tests__/percent-cell-vs-measure-4576.test.ts +0 -115
- package/src/__tests__/percent-formatter-locale-4553.test.ts +0 -147
- package/src/__tests__/percent-tie-halfup-4590.test.ts +0 -224
- package/src/__tests__/registry-collision.test.ts +0 -91
- package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
- package/src/__tests__/select-label-association-e2e.test.tsx +0 -199
- package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
- package/src/__tests__/validation-feedback.test.tsx +0 -361
- package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
- package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -357
- package/src/__tests__/widget-props-contract.test.tsx +0 -142
- package/src/coerce-safe-value.test.ts +0 -34
- package/src/complex-widgets.test.tsx +0 -924
- package/src/currency.ts +0 -144
- package/src/datetime-widgets.test.tsx +0 -185
- package/src/field-type-alias.multiple.test.ts +0 -81
- package/src/field-type-alias.ts +0 -156
- package/src/field-type-coverage.test.ts +0 -67
- package/src/index.css +0 -44
- package/src/index.tsx +0 -2745
- package/src/standard-widgets.test.tsx +0 -364
- package/src/widgets/AddressField.postalCode.test.tsx +0 -257
- package/src/widgets/AddressField.tsx +0 -182
- package/src/widgets/AddressField.uniqueIds.test.tsx +0 -93
- package/src/widgets/AutoNumberField.tsx +0 -18
- package/src/widgets/AvatarField.tsx +0 -117
- package/src/widgets/BooleanField.tsx +0 -86
- package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
- package/src/widgets/CapabilityMultiSelectField.tsx +0 -241
- package/src/widgets/CharacterCount.tsx +0 -263
- package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
- package/src/widgets/CheckboxesField.tsx +0 -169
- package/src/widgets/CodeField.tsx +0 -41
- package/src/widgets/ColorField.tsx +0 -53
- package/src/widgets/CurrencyField.tsx +0 -135
- package/src/widgets/DateField.tsx +0 -40
- package/src/widgets/DateTimeField.tsx +0 -46
- package/src/widgets/EmailField.tsx +0 -37
- package/src/widgets/FileField.tsx +0 -512
- package/src/widgets/FilterConditionField.tsx +0 -454
- package/src/widgets/FormulaField.tsx +0 -38
- package/src/widgets/FullscreenFieldEditor.tsx +0 -60
- package/src/widgets/GeolocationField.tsx +0 -193
- package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
- package/src/widgets/GridField.test.tsx +0 -632
- package/src/widgets/GridField.tsx +0 -1070
- package/src/widgets/ImageCropperDialog.tsx +0 -245
- package/src/widgets/ImageField.maxSize.test.tsx +0 -154
- package/src/widgets/ImageField.tsx +0 -305
- package/src/widgets/ImageLightbox.test.tsx +0 -52
- package/src/widgets/ImageLightbox.tsx +0 -108
- package/src/widgets/LocationField.tsx +0 -57
- package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
- package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
- package/src/widgets/LookupField.hydration.test.tsx +0 -136
- package/src/widgets/LookupField.idField.test.tsx +0 -61
- package/src/widgets/LookupField.tsx +0 -1295
- package/src/widgets/MarkdownContent.tsx +0 -19
- package/src/widgets/MasterDetailField.tsx +0 -131
- package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
- package/src/widgets/MultiSelectField.tsx +0 -196
- package/src/widgets/NumberField.tsx +0 -49
- package/src/widgets/ObjectField.tsx +0 -94
- package/src/widgets/ObjectRefField.tsx +0 -121
- package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
- package/src/widgets/OptionsEmptyState.test.tsx +0 -169
- package/src/widgets/OptionsEmptyState.tsx +0 -99
- package/src/widgets/PasswordField.tsx +0 -49
- package/src/widgets/PeoplePicker.test.tsx +0 -360
- package/src/widgets/PeoplePicker.tsx +0 -579
- package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
- package/src/widgets/PercentField.tsx +0 -95
- package/src/widgets/PersonRow.tsx +0 -117
- package/src/widgets/PhoneField.tsx +0 -37
- package/src/widgets/QRCodeField.tsx +0 -103
- package/src/widgets/RadioField.cascade.test.tsx +0 -141
- package/src/widgets/RadioField.tsx +0 -143
- package/src/widgets/RatingField.tsx +0 -94
- package/src/widgets/RecipientPickerField.test.tsx +0 -157
- package/src/widgets/RecipientPickerField.tsx +0 -188
- package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +0 -191
- package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +0 -124
- package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
- package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
- package/src/widgets/RecordPickerDialog.tsx +0 -1365
- package/src/widgets/RichTextField.tsx +0 -176
- package/src/widgets/SelectField.cascade.test.tsx +0 -138
- package/src/widgets/SelectField.tsx +0 -193
- package/src/widgets/SelectionTray.tsx +0 -110
- package/src/widgets/SignatureField.tsx +0 -140
- package/src/widgets/SliderField.tsx +0 -39
- package/src/widgets/SummaryField.tsx +0 -31
- package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
- package/src/widgets/TagsField.placeholder.test.tsx +0 -95
- package/src/widgets/TagsField.tsx +0 -88
- package/src/widgets/TextAreaField.tsx +0 -213
- package/src/widgets/TextField.tsx +0 -45
- package/src/widgets/TimeField.tsx +0 -31
- package/src/widgets/UrlField.tsx +0 -46
- package/src/widgets/UserField.tsx +0 -59
- package/src/widgets/UserFieldPicker.test.tsx +0 -78
- package/src/widgets/VectorField.tsx +0 -29
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -166
- package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
- package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
- package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
- package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
- package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
- package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
- package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
- package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
- package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
- package/src/widgets/_cell-renderer-bridge.ts +0 -19
- package/src/widgets/address-format.ts +0 -120
- package/src/widgets/deriveLookupColumns.test.ts +0 -112
- package/src/widgets/deriveLookupColumns.ts +0 -108
- package/src/widgets/file-size-guard.ts +0 -55
- package/src/widgets/file-value.test.ts +0 -230
- package/src/widgets/file-value.ts +0 -246
- package/src/widgets/nativeDateValue.test.ts +0 -115
- package/src/widgets/nativeDateValue.ts +0 -112
- package/src/widgets/openNativePicker.ts +0 -18
- package/src/widgets/optionWidgets.gatedOptions.test.tsx +0 -267
- package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +0 -150
- package/src/widgets/personDisplay.test.ts +0 -113
- package/src/widgets/personDisplay.ts +0 -92
- package/src/widgets/recentLookups.test.ts +0 -45
- package/src/widgets/recentLookups.ts +0 -47
- package/src/widgets/toDomProps.ts +0 -204
- package/src/widgets/toHostGroupProps.ts +0 -102
- package/src/widgets/types.ts +0 -242
- package/src/widgets/useCascadingOptions.ts +0 -46
- package/src/widgets/useFieldTranslation.ts +0 -132
- package/src/widgets/useRecordQuery.test.ts +0 -174
- package/src/widgets/useRecordQuery.ts +0 -291
- package/src/widgets/useUploadingSignal.test.tsx +0 -62
- package/src/widgets/useUploadingSignal.ts +0 -20
- package/src/withFieldCarrier.tsx +0 -75
|
@@ -52,17 +52,37 @@ export type LegacyAddressValue = AddressValue & {
|
|
|
52
52
|
/** Postal code of a stored address, preferring the canonical key. */
|
|
53
53
|
export declare function readPostalCode(addr: AddressValue): string | undefined;
|
|
54
54
|
/**
|
|
55
|
-
* Formats a stored address as a single line
|
|
55
|
+
* Formats a stored address as a single line, in the part order the reader's
|
|
56
|
+
* locale writes addresses in.
|
|
56
57
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
58
|
+
* Two orders, and the second is exactly the first reversed:
|
|
59
|
+
*
|
|
60
|
+
* - **small-to-large** (default; `en` and every Latin/Cyrillic/Arabic locale
|
|
61
|
+
* this repo ships) — `Street, City, State ZIP, Country`. Unchanged from
|
|
62
|
+
* what `AddressField`'s readonly branch has always produced, so English and
|
|
63
|
+
* provider-less rendering are byte-identical to before.
|
|
64
|
+
* - **large-to-small** ({@link LARGE_TO_SMALL_LANGUAGES}) — `Country, ZIP
|
|
65
|
+
* State, City, Street`. A `zh` console rendering a Chinese address
|
|
66
|
+
* largest-first is the whole point of objectui#4028: `中国, 310000 浙江,
|
|
67
|
+
* 杭州, 中策路 1 号` rather than the US-ordered line it produced before.
|
|
68
|
+
*
|
|
69
|
+
* Only the ORDER varies. The separator stays `', '` in both, because that is
|
|
70
|
+
* not what the issue reports and because these lines carry mixed-script data —
|
|
71
|
+
* a US address read on a `zh` console would look stranger under fullwidth
|
|
72
|
+
* punctuation than under the comma it already has.
|
|
73
|
+
*
|
|
74
|
+
* The state/postal-code pair keeps its one comma-delimited group in both
|
|
75
|
+
* orders, space-separated inside it ("CA 94102" / "310000 浙江"); either part
|
|
76
|
+
* alone occupies the group by itself. Missing parts are dropped rather than
|
|
77
|
+
* spaced over, so a street-only address is `"中策路 1 号"` and never `", , ,"`.
|
|
62
78
|
*
|
|
63
79
|
* Returns `''` when no part is usable — callers decide what an empty address
|
|
64
80
|
* looks like (an `EmptyValue` placeholder for the cell renderer), and a value
|
|
65
81
|
* carrying no recognized part at all is NOT silently blanked: see
|
|
66
82
|
* `AddressCellRenderer`, which falls back to JSON so unknown data stays visible.
|
|
83
|
+
*
|
|
84
|
+
* @param locale - BCP-47 tag from `useDisplayLocale()`. Omitted (tests, and any
|
|
85
|
+
* caller with no React context) means the small-to-large default, which is
|
|
86
|
+
* what this function did unconditionally before objectui#4028.
|
|
67
87
|
*/
|
|
68
|
-
export declare function formatAddress(addr: AddressValue): string;
|
|
88
|
+
export declare function formatAddress(addr: AddressValue, locale?: string): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { FileValueView } from './file-value.js';
|
|
3
|
+
/**
|
|
4
|
+
* One file: a keyboard-reachable view/download link when it resolves, and the
|
|
5
|
+
* caller's own `fallback` text when it does not.
|
|
6
|
+
*
|
|
7
|
+
* The anchor's accessible name is the file NAME, carried as its link text, so
|
|
8
|
+
* this needs no `aria-label` and therefore no new translation key — the name
|
|
9
|
+
* itself already comes off the i18n channel at every call site (a value that
|
|
10
|
+
* carries no name of its own falls back to `fields.file.fileFallback`).
|
|
11
|
+
*
|
|
12
|
+
* `target="_blank"` + `rel="noopener noreferrer"` mirrors `UrlCellRenderer`,
|
|
13
|
+
* and the click is `stopPropagation`-guarded so opening a file from inside a
|
|
14
|
+
* grid row does not also trigger row navigation — the same guard
|
|
15
|
+
* `ImageCellRenderer` puts on its thumbnail.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ No `download` attribute: whether the browser views the file inline or
|
|
18
|
+
* saves it is the storage endpoint's decision, signed into the
|
|
19
|
+
* `content-disposition` it returns. Forcing one of the two here would override
|
|
20
|
+
* a choice the platform already makes per file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function FileValueAffordance({ view, fallback, className, icon, }: {
|
|
23
|
+
view: FileValueView;
|
|
24
|
+
/**
|
|
25
|
+
* What to draw when the value resolves to no URL. Required rather than
|
|
26
|
+
* defaulted: every call site already had a text rendering of its own, and a
|
|
27
|
+
* default here would quietly replace one of them.
|
|
28
|
+
*/
|
|
29
|
+
fallback: React.ReactNode;
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Suppressed where the row already draws its own icon or thumbnail. */
|
|
32
|
+
icon?: boolean;
|
|
33
|
+
}): React.ReactElement;
|
|
@@ -1,28 +1,10 @@
|
|
|
1
|
+
import { TranslateFn } from '@object-ui/i18n';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (objectui#
|
|
4
|
-
*
|
|
5
|
-
* Why this is shared rather than mirrored per widget: the guard is the only
|
|
6
|
-
* thing standing between a declared `maxSize` and a real `presigned → PUT →
|
|
7
|
-
* complete` round trip, and `paramToField` copies `maxSize` onto the field
|
|
8
|
-
* config for **every** param type regardless of which widget ends up rendering
|
|
9
|
-
* it. So a widget that re-derives the check drifts silently — the image param
|
|
10
|
-
* shipped receiving the constraint and never reading it, while the file param
|
|
11
|
-
* rejected the identical pick, and nothing failed until QA drove the two side by
|
|
12
|
-
* side. One exported function makes the difference visible: a widget either
|
|
13
|
-
* calls it or conspicuously does not.
|
|
14
|
-
*
|
|
15
|
-
* Both widgets deliberately share the `fields.file.exceedsMaxSize` key too. The
|
|
16
|
-
* sentence names a file and a limit and says nothing file-widget-specific, it is
|
|
17
|
-
* already translated in all 11 built-in locales, and a parallel `fields.image.*`
|
|
18
|
-
* twin would be the same sentence maintained twice — drifting the moment one
|
|
19
|
-
* translator rewords one of them.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* Minimal shape of the i18next `t` this module needs, matching the established
|
|
23
|
-
* `TranslateFn` pattern in `app-shell/src/providers/writeWarningToast`.
|
|
3
|
+
* Minimal shape of the i18next `t` this module needs — RE-EXPORTED from its one
|
|
4
|
+
* authority in `@object-ui/i18n`, never re-declared (objectui#8261). The
|
|
5
|
+
* sibling widgets that import it from here keep doing so; no import path moves.
|
|
24
6
|
*/
|
|
25
|
-
export type TranslateFn
|
|
7
|
+
export type { TranslateFn } from '@object-ui/i18n';
|
|
26
8
|
/**
|
|
27
9
|
* The rejection message for a pick that exceeds `maxSize`, or `null` when the
|
|
28
10
|
* pick is within the limit — so a caller reads it as `if (err) reject`.
|
|
@@ -3,7 +3,12 @@ import { isFileIdToken } from '@objectstack/spec/data';
|
|
|
3
3
|
export interface FileValueView {
|
|
4
4
|
/** `sys_file` id, when the value carries one. */
|
|
5
5
|
id?: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Best available display name: the value's own, else the caller's
|
|
8
|
+
* `fallbackName`. Empty only when the caller passed an empty fallback to ask
|
|
9
|
+
* for "no name" (the image cell does, so a nameless image reaches its
|
|
10
|
+
* translated alt — objectui#10493).
|
|
11
|
+
*/
|
|
7
12
|
name: string;
|
|
8
13
|
/** Resolvable URL, when the value carries one. A bare reference does not. */
|
|
9
14
|
url?: string;
|
|
@@ -29,9 +34,8 @@ export interface FileValueView {
|
|
|
29
34
|
* Why it matters that this one is shared rather than duplicated: the regex is a
|
|
30
35
|
* WIRE decision. Widening it server-side (say, ids grow past 64 chars) while a
|
|
31
36
|
* copy here keeps the old bound turns every new id into "not a reference", and
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* regex.
|
|
37
|
+
* `fileValueForSubmit` then refuses every upload as incomplete — loud, but
|
|
38
|
+
* pointing at the adapter while the cause is a regex two packages away.
|
|
35
39
|
*/
|
|
36
40
|
export { isFileIdToken };
|
|
37
41
|
/**
|
|
@@ -75,13 +79,38 @@ export interface UploadResultLike {
|
|
|
75
79
|
meta?: Record<string, unknown>;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
|
-
*
|
|
82
|
+
* A completed upload that surfaced no `sys_file` id, refused at the point of
|
|
83
|
+
* submit (objectui#7699).
|
|
79
84
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
85
|
+
* The adapter reported success, so this is not a transport failure and the
|
|
86
|
+
* widgets do not quote an adapter message for it: what the user needs to know
|
|
87
|
+
* is that the field was NOT changed. Named — `name` and `code` — so a caller
|
|
88
|
+
* can tell it from a thrown network error without reading the message, and
|
|
89
|
+
* carrying the file's name so a host that reports it can say which pick.
|
|
83
90
|
*/
|
|
84
|
-
export declare
|
|
91
|
+
export declare class UploadIncompleteError extends Error {
|
|
92
|
+
readonly code: "UPLOAD_INCOMPLETE";
|
|
93
|
+
/** The pick's own name when the caller knew it, else the adapter's stored object name. */
|
|
94
|
+
readonly fileName: string;
|
|
95
|
+
constructor(fileName: string);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* What to store in the field for a completed upload: the bare `sys_file` id
|
|
99
|
+
* the adapter surfaced in `meta.fileId` — the reference form, the only form
|
|
100
|
+
* the stored contract admits.
|
|
101
|
+
*
|
|
102
|
+
* Throws {@link UploadIncompleteError} when the adapter surfaced no id-shaped
|
|
103
|
+
* `fileId`. ⛔ There is no fallback: the inline blob this function used to
|
|
104
|
+
* build in that case (`{ name, original_name, size, mime_type, url }`) is a
|
|
105
|
+
* shape no deployment's stored contract accepts, and a client that sent it
|
|
106
|
+
* turned a successful-looking upload into a save the backend refused (see the
|
|
107
|
+
* module header's "Submitting"). The callers catch the refusal per pick and
|
|
108
|
+
* render it in the same error row a transport failure lands in.
|
|
109
|
+
*
|
|
110
|
+
* @param originalName the pick's own file name, used to name the refusal;
|
|
111
|
+
* the adapter's stored object name stands in when the caller has none.
|
|
112
|
+
*/
|
|
113
|
+
export declare function fileValueForSubmit(result: UploadResultLike, originalName?: string): string;
|
|
85
114
|
/**
|
|
86
115
|
* The display view of a just-completed upload.
|
|
87
116
|
*
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { LookupColumnDef } from '@object-ui/types';
|
|
3
|
+
/**
|
|
4
|
+
* Cell renderer function signature — matches `getCellRenderer` from
|
|
5
|
+
* `@object-ui/fields`. Re-declared here (rather than imported from
|
|
6
|
+
* RecordPickerDialog) so this module sits BELOW both consumers in the import
|
|
7
|
+
* graph and neither surface can pull the other in.
|
|
8
|
+
*/
|
|
9
|
+
export type LookupCellRendererResolver = (fieldType: string) => React.FC<{
|
|
10
|
+
value: any;
|
|
11
|
+
field: any;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* A select option as the object schema declares it — `{ value, label }` plus
|
|
15
|
+
* whatever the renderer also reads (`color`, …), which the i18n translation
|
|
16
|
+
* carries through untouched.
|
|
17
|
+
*/
|
|
18
|
+
export type SchemaOption = {
|
|
19
|
+
value: any;
|
|
20
|
+
label: string;
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
};
|
|
23
|
+
/** Translate a schema field's options through the shared i18n option path. */
|
|
24
|
+
export type OptionTranslator = (objectName: string, fieldName: string, options: SchemaOption[]) => SchemaOption[];
|
|
25
|
+
/**
|
|
26
|
+
* Normalise a lookup_columns entry (string | LookupColumnDef) into a
|
|
27
|
+
* concrete LookupColumnDef object.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeColumn(col: string | LookupColumnDef): LookupColumnDef;
|
|
30
|
+
/**
|
|
31
|
+
* Pretty-print a field name as a column header label.
|
|
32
|
+
* Converts snake_case / camelCase to Title Case.
|
|
33
|
+
*/
|
|
34
|
+
export declare function fieldToLabel(field: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a field's select options from the referenced object's schema field
|
|
37
|
+
* definition (`fieldsMeta[field]`), translated through the shared i18n option
|
|
38
|
+
* path.
|
|
39
|
+
*
|
|
40
|
+
* This is the SINGLE source for every surface that needs option labels: the
|
|
41
|
+
* picker table cells (#3333), the picker's filter panel select inputs (#3336)
|
|
42
|
+
* and the inline dropdown's option preview (#5492) — so a picker column, the
|
|
43
|
+
* filter input for that same column and the dropdown row can never disagree
|
|
44
|
+
* about what an option is called.
|
|
45
|
+
*
|
|
46
|
+
* Returns `undefined` when the schema field declares no options: a select
|
|
47
|
+
* field with no authored options genuinely has nothing to offer, and
|
|
48
|
+
* synthesising entries from the loaded page's raw stored values would paper
|
|
49
|
+
* over the metadata gap with a list that changes per page.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveSchemaOptions(meta: {
|
|
52
|
+
options?: unknown;
|
|
53
|
+
} | undefined, objectName: string, fieldName: string, translateOptions: OptionTranslator): SchemaOption[] | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Field descriptors handed to the type-aware cell renderers, enriched from the
|
|
56
|
+
* referenced object's schema (`fieldsMeta`) the same way the list view enriches
|
|
57
|
+
* its columns. This is what lets a `select` column resolve its option label
|
|
58
|
+
* (options + i18n) instead of title-casing the raw value (#3333), and what
|
|
59
|
+
* carries `reference` through to the lookup cell renderer so
|
|
60
|
+
* an unresolved foreign-key id resolves to a name (#5492).
|
|
61
|
+
*
|
|
62
|
+
* Columns whose def carries no `type` inherit the schema field's type, so
|
|
63
|
+
* string-authored `lookup_columns` format identically to object-authored ones.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildLookupColumnDescriptors(columns: LookupColumnDef[], fieldsMeta: Record<string, any> | undefined, objectName: string, translateOptions: OptionTranslator): Record<string, any>;
|
|
66
|
+
export interface LookupColumnRenderContext {
|
|
67
|
+
/** Field descriptors from {@link buildLookupColumnDescriptors}. */
|
|
68
|
+
descriptors: Record<string, any>;
|
|
69
|
+
/** Type-aware renderer resolver; when absent every cell degrades to text. */
|
|
70
|
+
cellRenderer?: LookupCellRendererResolver;
|
|
71
|
+
/**
|
|
72
|
+
* The referenced object's schema. When given, the display column renders the
|
|
73
|
+
* record's title through `@object-ui/core`'s `getRecordDisplayName`, the same
|
|
74
|
+
* call the lookup dropdown's option label and the read cell make
|
|
75
|
+
* (objectui#10486).
|
|
76
|
+
*/
|
|
77
|
+
objectSchema?: Record<string, unknown> | null;
|
|
78
|
+
/**
|
|
79
|
+
* The lookup field's DECLARED display field, handed to the resolver as its
|
|
80
|
+
* `titleField`. `undefined` when the field declares none: the `name` default
|
|
81
|
+
* the display column is keyed on is a guess, and a guess never outranks the
|
|
82
|
+
* referenced object's own declarations.
|
|
83
|
+
*/
|
|
84
|
+
titleField?: string;
|
|
85
|
+
/** The column the record title stands in for (the display column's key). */
|
|
86
|
+
displayField?: string;
|
|
87
|
+
/** Locale for the plain-text `$date` fallback. */
|
|
88
|
+
displayLocale?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Render one column of one candidate record.
|
|
92
|
+
*
|
|
93
|
+
* Order:
|
|
94
|
+
* 1. the record title, for the display column only and only when the
|
|
95
|
+
* referenced object's schema is given: `getRecordDisplayName` with the
|
|
96
|
+
* lookup's declared display field as its `titleField`, i.e. the field's
|
|
97
|
+
* own `displayField`, then the object's `nameField`, then its deprecated
|
|
98
|
+
* `titleFormat` template, then type-aware derivation (ADR-0079; the lookup
|
|
99
|
+
* branch of objectui#9436's ruling C1). The resolver's own floor
|
|
100
|
+
* (`Untitled` / `Record #<id>`) is not a title here, so a record with
|
|
101
|
+
* nothing nameable falls through to step 2 and keeps its column's value.
|
|
102
|
+
* 2. the type-aware cell renderer for the column's field descriptor.
|
|
103
|
+
* 3. a plain-text fallback for columns with no descriptor / no resolver.
|
|
104
|
+
*
|
|
105
|
+
* Step 3 never blanks a populated value: an object with no recognised shape
|
|
106
|
+
* keeps its JSON and a primitive keeps its `String()` form. That floor is
|
|
107
|
+
* deliberate — an unresolved reference showing its bare id is strictly better
|
|
108
|
+
* than a silently empty column (objectui#5492).
|
|
109
|
+
*/
|
|
110
|
+
export declare function renderLookupColumnValue(record: any, col: LookupColumnDef, ctx: LookupColumnRenderContext): React.ReactNode;
|
|
@@ -6,32 +6,14 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Value adapters for the native date/time controls (objectui#3127).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* They live in `@object-ui/core` (`utils/native-date-value.ts`), next to the
|
|
12
|
+
* `isRealCalendarDate` judgement they share, so `@object-ui/components`' data
|
|
13
|
+
* table can use the same set without a package cycle (objectui#10625). This
|
|
14
|
+
* module re-exports them unchanged, so every importer here, and the public
|
|
15
|
+
* `@object-ui/fields` barrel, names the same symbols as before. The design
|
|
16
|
+
* notes (the round trip, the shape kept in form state, the impossible stored
|
|
17
|
+
* day) are in the `core` module's header.
|
|
15
18
|
*/
|
|
16
|
-
export
|
|
17
|
-
/**
|
|
18
|
-
* Stored value → `<input type="datetime-local">` value (`YYYY-MM-DDTHH:mm`),
|
|
19
|
-
* expressed in the viewer's local wall clock — the same instant the detail
|
|
20
|
-
* page shows, so opening the editor never appears to move the time.
|
|
21
|
-
*
|
|
22
|
-
* A value that is ALREADY a zone-less local datetime is truncated to minutes
|
|
23
|
-
* in place instead of being round-tripped through `Date`, which would
|
|
24
|
-
* reinterpret it and then re-render it, twice risking an off-by-a-zone shift.
|
|
25
|
-
*/
|
|
26
|
-
export declare function toDateTimeInputValue(value: unknown): string;
|
|
27
|
-
/**
|
|
28
|
-
* `<input type="datetime-local">` value → stored value (ISO-8601, UTC).
|
|
29
|
-
*
|
|
30
|
-
* The control hands us a zone-less local wall clock; `new Date()` reads that
|
|
31
|
-
* form as local time, which is the inverse of what `toDateTimeInputValue`
|
|
32
|
-
* wrote — the pair is what keeps read and write on one basis.
|
|
33
|
-
*
|
|
34
|
-
* An unparseable string is returned untouched rather than blanked: dropping a
|
|
35
|
-
* value the user can see in the box would be a data loss dressed up as a fix.
|
|
36
|
-
*/
|
|
37
|
-
export declare function fromDateTimeInputValue(value: string): string;
|
|
19
|
+
export { toDateInputValue, toDateTimeInputValue, fromDateTimeInputValue, isImpossibleStoredDay, } from '@object-ui/core';
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { FieldMetadata } from '@object-ui/types';
|
|
2
|
+
import { PercentScale } from '@objectstack/spec/data';
|
|
3
|
+
/**
|
|
4
|
+
* Format a numeric value the way the `number` field declaring it says to
|
|
5
|
+
* display it.
|
|
6
|
+
*
|
|
7
|
+
* @param value the numeric value. Callers decide what counts as empty or as
|
|
8
|
+
* not a number before calling: this formats a number, it does
|
|
9
|
+
* not judge one.
|
|
10
|
+
* @param field the field metadata; its `type`, `scale` and `useGrouping` are
|
|
11
|
+
* the inputs read.
|
|
12
|
+
* @param locale the display locale tag, from `useDisplayLocale()`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatNumberFieldValue(value: number, field: FieldMetadata, locale: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The display width of a currency amount: the resolved currency's own ISO 4217
|
|
17
|
+
* minor-unit count (2 for USD / CNY, 0 for JPY, 3 for KWD), or the historical 2
|
|
18
|
+
* when no currency is resolved, since there is then no minor unit to ask about.
|
|
19
|
+
*
|
|
20
|
+
* A currency's decimal places are the CURRENCY's, not a field setting: the
|
|
21
|
+
* maintainer ruling recorded on objectstack-ai/objectstack#19910 (batch #218
|
|
22
|
+
* item 2), executed by objectui#10276. ⛔ Not the field-level `precision` (the
|
|
23
|
+
* TOTAL digit count of a decimal(p, s) column), ⛔ not `scale` (taken off the
|
|
24
|
+
* `currency` type by ruling B on objectstack-ai/objectstack#19629) and ⛔ not
|
|
25
|
+
* `currencyConfig.precision`. `CurrencyField`'s comment beside its call carries
|
|
26
|
+
* the full reasoning.
|
|
27
|
+
*
|
|
28
|
+
* One function, so the width a currency amount is DISPLAYED at
|
|
29
|
+
* ({@link formatCurrency}) and the width `CurrencyField` EDITS it at (its
|
|
30
|
+
* `step` and its blur rounding) cannot part.
|
|
31
|
+
*/
|
|
32
|
+
export declare function currencyDisplayWidth(currency: string | undefined): number;
|
|
33
|
+
/**
|
|
34
|
+
* Format a currency amount: the ONE call every display face of a `currency`
|
|
35
|
+
* field makes. `CurrencyCellRenderer` in a table, `CurrencyField`'s read-only
|
|
36
|
+
* branch in a form, and every face that takes the cell's formatter by
|
|
37
|
+
* reference all reach it, so they agree by construction (objectui#11444).
|
|
38
|
+
*
|
|
39
|
+
* When `currency` is undefined the amount is a plain number with thousands
|
|
40
|
+
* separators and no symbol. Silently assuming USD for unconfigured currency
|
|
41
|
+
* fields was the #1 source of "why is my RMB amount showing as dollars?" bug
|
|
42
|
+
* reports.
|
|
43
|
+
*
|
|
44
|
+
* The width is the DECLARED one, {@link currencyDisplayWidth}, for every
|
|
45
|
+
* amount: `$3,456.00`, `$3,456.50`, `¥3,456`, `KWD 3,456.000`. Triage ruled
|
|
46
|
+
* that the protocol's convention is the declared width (objectui#11444, comment
|
|
47
|
+
* 5946462862), the family's one width source (ruling A′, `resolveFieldScale`),
|
|
48
|
+
* and retired the objectui#4033 whole-amount trimming this function used to
|
|
49
|
+
* apply: a whole amount dropped its fraction (`$3,456`, `KWD 3,456`) while the
|
|
50
|
+
* read-only form showed the same amount at the currency's width (`$3,456.00`),
|
|
51
|
+
* so one stored value read two ways.
|
|
52
|
+
*
|
|
53
|
+
* ONE width for both `Intl` bounds, never a range (objectui#4332). The symbol
|
|
54
|
+
* branch once passed `minimumFractionDigits: 0` against a larger maximum, and
|
|
55
|
+
* `Intl` then emits the SHORTEST representation in range, so a genuine cents
|
|
56
|
+
* value of `.50` printed as `.5` (`$1,234.5`).
|
|
57
|
+
*
|
|
58
|
+
* The width is the CURRENCY's own minor-unit count, not a literal 2
|
|
59
|
+
* (objectui#4361). Passing 2 for every currency on earth overrode what `Intl`
|
|
60
|
+
* already knows: a yen amount was printed with cents it does not have
|
|
61
|
+
* (`¥1,234.50`) and a dinar amount one digit short (`KWD 1.50`).
|
|
62
|
+
*
|
|
63
|
+
* Deliberately passes NO `scale` to `formatDisplayNumber`: `scale: 0` without a
|
|
64
|
+
* currency reads as an ordinal there and drops the grouping separators
|
|
65
|
+
* (objectui#4033), and an amount is never an ordinal.
|
|
66
|
+
*/
|
|
67
|
+
export declare function formatCurrency(value: number, currency?: string, locale?: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* The storage scale a percent FACE reads a field's stored number at: the
|
|
70
|
+
* spec's `percentScaleOf` (`@objectstack/spec/data`) by reference, plus the
|
|
71
|
+
* one type the percent cell draws that the spec does not answer for
|
|
72
|
+
* (objectui#11475).
|
|
73
|
+
*
|
|
74
|
+
* `PercentCellRenderer` reads it, and so does every face that renders the
|
|
75
|
+
* cell's number for the same field (the grid footer, the record detail chip,
|
|
76
|
+
* the gantt tooltip, the mobile card, the object metric tile), so one stored
|
|
77
|
+
* value reads one magnitude on all of them. ⛔ None of them reads the storage
|
|
78
|
+
* off the VALUE: a magnitude guess cannot tell a fraction-stored `1` (100%)
|
|
79
|
+
* from one percentage point.
|
|
80
|
+
*
|
|
81
|
+
* - **`percent`, and a textual field the `format: 'percent'` hint promotes to
|
|
82
|
+
* the percent cell:** `percentScaleOf` asked about `percent`, the face's own
|
|
83
|
+
* type, with the field's declared `max`. A percent field stores a fraction
|
|
84
|
+
* unless it declares a `max` above 1. Asking about `percent` rather than the
|
|
85
|
+
* bag's own `type` is what lets a promoted textual field, or a bag whose
|
|
86
|
+
* `type` was rewritten to the renderer key, read by the percent convention
|
|
87
|
+
* too; it is the same reason `PercentField` asks `resolveFieldScale` about
|
|
88
|
+
* `percent`. `percentScaleOf` answers every percent field, so the result is
|
|
89
|
+
* never `undefined`.
|
|
90
|
+
* - **`progress`: `whole`, stated here.** The spec's `percentScaleOf` answers
|
|
91
|
+
* `undefined` for it (it keys on `percent` alone), so the spec states no
|
|
92
|
+
* percent storage for a `progress` field, and an undeclared key follows the
|
|
93
|
+
* implementation: the one editor a `progress` field has, `SliderField`,
|
|
94
|
+
* stores the slider's position on a `0`–`100` range unless the field
|
|
95
|
+
* declares other bounds, and every first-party `progress` field declares
|
|
96
|
+
* `min: 0, max: 100`. So its number IS percentage points.
|
|
97
|
+
*
|
|
98
|
+
* @param field the field definition the face holds. Only `type` and `max`
|
|
99
|
+
* are read, and a `max` that is not a number is not a
|
|
100
|
+
* declaration.
|
|
101
|
+
*/
|
|
102
|
+
export declare function percentCellScale(field: {
|
|
103
|
+
type?: string;
|
|
104
|
+
max?: unknown;
|
|
105
|
+
} | null | undefined): PercentScale;
|
|
106
|
+
/**
|
|
107
|
+
* Render a percent on a value ALREADY in display magnitude (`80` means 80%):
|
|
108
|
+
* the ONE rendering call every display face of a `percent` field makes.
|
|
109
|
+
* `formatPercent` (the table cell, and every face that takes the cell's
|
|
110
|
+
* formatter by reference) and `PercentField`'s read-only branch in a form both
|
|
111
|
+
* end here, so
|
|
112
|
+
* the locale's percent convention, the grouping and the width cannot part
|
|
113
|
+
* between them (objectui#11444). The read-only face printed
|
|
114
|
+
* `toDisplay(value).toFixed(scale)` plus a literal `%`: a `scale: 2` field
|
|
115
|
+
* holding `0.25` read `25.00%` in a `de` form while its cell read `25,00 %`.
|
|
116
|
+
*
|
|
117
|
+
* The SCALING is the caller's, and it is a separate statement on purpose:
|
|
118
|
+
* both callers scale at the storage the field DECLARES, read through the
|
|
119
|
+
* spec's `percentScaleOf` (a fraction unless the field declares a `max` above
|
|
120
|
+
* 1, the rule the edit input writes by). `formatPercent` takes it as its
|
|
121
|
+
* `percentScale` argument, which the cell answers with
|
|
122
|
+
* {@link percentCellScale}; `PercentField` asks `percentScaleOf` itself, so
|
|
123
|
+
* its read-only text and its input never show one stored value at two
|
|
124
|
+
* magnitudes. Until objectui#11475 the cell guessed the storage from the
|
|
125
|
+
* value's magnitude instead, so a fraction-stored `1` (100%) read `100%` in a
|
|
126
|
+
* read-only form and `1%` in its cell, and a whole-stored `0.5` read `0.5%`
|
|
127
|
+
* and `50%`.
|
|
128
|
+
* ⛔ Do not add a caller with a scaling rule of its own, and ⛔ never one keyed
|
|
129
|
+
* on the column's NAME: objectui#9452 removed exactly that from
|
|
130
|
+
* `PercentCellRenderer`.
|
|
131
|
+
*
|
|
132
|
+
* @param displayValue the percentage points to render.
|
|
133
|
+
* @param precision the decimal places, fixed at both bounds: the width the
|
|
134
|
+
* caller resolved (`resolveFieldScale`, ruling A′).
|
|
135
|
+
* @param locale the display locale tag, from `useDisplayLocale()`.
|
|
136
|
+
*/
|
|
137
|
+
export declare function formatPercentPoints(displayValue: number, precision: number, locale?: string): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { TranslateFn } from './file-size-guard.js';
|
|
3
|
+
/**
|
|
4
|
+
* ONE reading of "this box is showing text the browser cannot read", shared by
|
|
5
|
+
* every `type="number"` widget in this package (objectui#6780).
|
|
6
|
+
*
|
|
7
|
+
* ## The defect this closes
|
|
8
|
+
*
|
|
9
|
+
* A `type="number"` box can DISPLAY one thing and hold another, with nothing
|
|
10
|
+
* said. Measured on Chromium 141.0.7390.37 (Playwright 1.62.1), typing `1e`
|
|
11
|
+
* into an empty number input:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* .value = "" <- what every widget here reads
|
|
15
|
+
* validity.badInput = true <- what the browser actually thinks
|
|
16
|
+
* pixels vs an untouched box: DIFFERENT <- the box IS still showing `1e`
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* So the widget emitted `null`, `aria-invalid` stayed `"false"`, and no
|
|
20
|
+
* diagnostic was drawn: objectui#6716's silent-refusal class, on a money field.
|
|
21
|
+
*
|
|
22
|
+
* ## Why `validity.badInput` and not a regex of our own
|
|
23
|
+
*
|
|
24
|
+
* It is the PLATFORM's own predicate — the browser stating, in its own words,
|
|
25
|
+
* whether it can read the text — rather than a second renderer-side dialect of
|
|
26
|
+
* "what a number is" (AGENTS.md #0.1).
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ objectui#6765 recorded `badInput` as "the ONE signal happy-dom and Chromium
|
|
29
|
+
* agree on". objectui#6780 measured that and NARROWED it, because the
|
|
30
|
+
* difference decides which strings a unit test may drive: in Chromium
|
|
31
|
+
* `badInput` is never true for a programmatic `.value` write (it reports that
|
|
32
|
+
* the UA cannot read the USER's input), so on the only route a happy-dom test
|
|
33
|
+
* has, the two engines agree on nothing. The agreement this guard rests on is
|
|
34
|
+
* between happy-dom's programmatic verdict and Chromium's TYPED verdict, and it
|
|
35
|
+
* holds for a measured subset — four strings agree on "bad", seven agree on
|
|
36
|
+
* "fine", and eight disagree. The full matrix, and the lists the tests are
|
|
37
|
+
* allowed to use, are in `__tests__/numberInputBrowserReadings.ts`.
|
|
38
|
+
*
|
|
39
|
+
* ⭐ And it is NOT objectui#6715's anchored guard wearing a different hat.
|
|
40
|
+
* That one was measured to be a provable no-op here — it rejects only strings
|
|
41
|
+
* this test environment fabricates. This one fires on keyboard-reachable
|
|
42
|
+
* states, MEASURED in Chromium by typing each key by key into an empty box:
|
|
43
|
+
*
|
|
44
|
+
* ```
|
|
45
|
+
* typed "1e" "1e-" "1e+" "-" "." "+" "-." "e" "5e" -> badInput TRUE
|
|
46
|
+
* typed "1." "1e5" "1.2.3" "0x10" "12abc" -> badInput false
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* and never on anything the browser can actually emit: `12`, `1.23`, `010`,
|
|
50
|
+
* `15`, `1`, `12.345` all read `badInput === false`.
|
|
51
|
+
*
|
|
52
|
+
* ## ⛔ What this deliberately does NOT do: change what is emitted
|
|
53
|
+
*
|
|
54
|
+
* objectui#6716's shape REFUSES (its `onChange` never fires and the prior value
|
|
55
|
+
* stands). Here that would destroy the very text the diagnostic points at, so
|
|
56
|
+
* these widgets keep emitting exactly what they emitted before and only stop
|
|
57
|
+
* being silent about it. MEASURED, in both halves:
|
|
58
|
+
*
|
|
59
|
+
* - Chromium: after typing `1e`, a script write of `.value` clears the raw
|
|
60
|
+
* display and flips `badInput` back to `false` — even writing `""`.
|
|
61
|
+
* - React 19.2.8's own `updateInput` (`react-dom/cjs/react-dom-client.
|
|
62
|
+
* development.js`) restores a number input with
|
|
63
|
+
* `if ((0 === value && "" === element.value) || element.value != value)
|
|
64
|
+
* element.value = "" + getToStringValue(value);`
|
|
65
|
+
*
|
|
66
|
+
* So for a box that already held `5`, refusing leaves `props.value` at `5`,
|
|
67
|
+
* `element.value` at `""`, and React writes `"5"` back — wiping the `5e` the
|
|
68
|
+
* user is looking at and the message is about. Emitting keeps `props.value` at
|
|
69
|
+
* `""`, the write is skipped, and the raw text survives, which is what
|
|
70
|
+
* objectui#6716 requires of a refusal ("keeps the refused text in the box, so
|
|
71
|
+
* the message has something to point at").
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ The text itself is NOT quotable. The browser displays it but never exposes
|
|
74
|
+
* it — `.value` is `""` and there is no other channel — so unlike
|
|
75
|
+
* objectui#6715's residue message this one cannot name what was typed. It
|
|
76
|
+
* points at the box instead.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* The one sentence, in objectui#6716's `Not saved: …` shape, read from the
|
|
80
|
+
* package's locale channel (objectui#8148).
|
|
81
|
+
*
|
|
82
|
+
* objectui#6755 ruled that a widget's OWN refusal sentence goes through
|
|
83
|
+
* `useFieldTranslation` + `FIELD_DEFAULTS`; `320374d2a` applied it to
|
|
84
|
+
* `LocationField`'s residue arm. This was the FIFTH such sentence and the only
|
|
85
|
+
* SHARED one — the four already keyed are each one widget's, while this literal
|
|
86
|
+
* served `NumberField`, `CurrencyField`, `PercentField` and both of
|
|
87
|
+
* `GeolocationField`'s boxes through {@link useBadInputRefusal}. Its sharpest
|
|
88
|
+
* consequence: `GeolocationField` sits beside `LocationField`, whose refusals
|
|
89
|
+
* are all keyed, so two adjacent coordinate widgets refused bad input in two
|
|
90
|
+
* different languages on one form.
|
|
91
|
+
*
|
|
92
|
+
* ⭐ `example` stays a HOLE rather than being keyed per widget, and that is
|
|
93
|
+
* what keeps the decimal numerals out of every pack. Five different values
|
|
94
|
+
* reach this one sentence — `1234`, `1234.56`, `12.5`, `30.2741`, `120.1551` —
|
|
95
|
+
* so a per-widget key would need five keys times ten packs, and each pack would
|
|
96
|
+
* then hold a decimal numeral it could legitimately re-punctuate. A pack that
|
|
97
|
+
* wrote `1234,56` would read as the `latitude, longitude` PAIR the adjacent
|
|
98
|
+
* widget asks for; the hole is filled by the widget in ASCII and no pack ever
|
|
99
|
+
* spells a digit.
|
|
100
|
+
*
|
|
101
|
+
* The English value is byte-identical to the literal it replaces, so English
|
|
102
|
+
* and provider-less rendering are unchanged.
|
|
103
|
+
*/
|
|
104
|
+
export declare function badInputMessage(t: TranslateFn, example: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* The widget's OWN refusal state, and the reader that fills it.
|
|
107
|
+
*
|
|
108
|
+
* Named `refusal`, never `error`: `error` is the published validation slot on
|
|
109
|
+
* the widget contract (objectui#3222) with exactly one author, the form
|
|
110
|
+
* renderer. This is the widget's own reading, which no host can produce — the
|
|
111
|
+
* same two-name split `LocationField` uses (objectui#6716).
|
|
112
|
+
*
|
|
113
|
+
* `readBadInput` is called from BOTH arms, because neither alone sees every
|
|
114
|
+
* route (MEASURED in Chromium):
|
|
115
|
+
*
|
|
116
|
+
* - TYPED `1e` into an empty box fires 2 `input` events — the `e` keystroke
|
|
117
|
+
* moves `.value` from `"1"` to `""`, so React does deliver an `onChange`.
|
|
118
|
+
* - PASTED `1e` into an empty box fires 1 `input` event, but `.value` never
|
|
119
|
+
* leaves `""`; React's own input-value tracking suppresses the synthetic
|
|
120
|
+
* `onChange`, so the CHANGE arm never runs. Blur fires, and `badInput` is
|
|
121
|
+
* still `true` at blur time — which is the whole reason for the blur arm.
|
|
122
|
+
*/
|
|
123
|
+
export declare function useBadInputRefusal(example: string): {
|
|
124
|
+
refusal: string | null;
|
|
125
|
+
readBadInput: (target: HTMLInputElement | null | undefined) => boolean;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* The drawn diagnostic — objectui#6716's exact markup, in one spelling so the
|
|
129
|
+
* four widgets of this class cannot drift apart.
|
|
130
|
+
*/
|
|
131
|
+
export declare function BadInputMessage({ refusal }: {
|
|
132
|
+
refusal: string | null;
|
|
133
|
+
}): React.JSX.Element | null;
|
|
134
|
+
/** The class that marks the refused control, in one spelling. */
|
|
135
|
+
export declare const BAD_INPUT_BORDER = "border-red-500 focus-visible:ring-red-500";
|
|
@@ -13,8 +13,22 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** Resolve a possibly-dotted path (e.g. `primary_business_unit_id.name`) against a record. */
|
|
15
15
|
export declare function resolvePath(record: any, path: string): any;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The fields a person's display name is read from, in order: the configured
|
|
18
|
+
* display field, then `name`, `username`, `label`. A caller that gates the name
|
|
19
|
+
* by field-level security filters THIS list and hands the rest to
|
|
20
|
+
* {@link getPersonName} (objectui#10535): a denied rung is skipped, so the name
|
|
21
|
+
* falls through to the next readable one — the row a stripping backend serves
|
|
22
|
+
* reads the same.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPersonNameFields(nameField?: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Best display name for a person record, tolerating expanded / raw shapes: the
|
|
27
|
+
* first field of the name ladder the record holds a value for. `nameField` is
|
|
28
|
+
* the configured display field, whose ladder is {@link getPersonNameFields}, or
|
|
29
|
+
* the ladder itself, already filtered to the fields the viewer may read.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getPersonName(record: any, nameField?: string | readonly string[]): string;
|
|
18
32
|
/**
|
|
19
33
|
* Up-to-2-char initials for the avatar fallback.
|
|
20
34
|
* - "John Doe" → "JD"; "John" → "JO"
|
|
@@ -23,8 +37,12 @@ export declare function getPersonName(record: any, nameField?: string): string;
|
|
|
23
37
|
export declare function getPersonInitials(name: string): string;
|
|
24
38
|
/** Secondary line: resolve each field path, drop empties, join with " · ". */
|
|
25
39
|
export declare function getPersonSubtitle(record: any, fields?: string[]): string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Avatar image URL (defaults to `sys_user.image`); undefined when absent, and
|
|
42
|
+
* when `avatarField` is `null` — no avatar field to read, e.g. one field-level
|
|
43
|
+
* security withholds (objectui#10433).
|
|
44
|
+
*/
|
|
45
|
+
export declare function getPersonAvatarUrl(record: any, avatarField?: string | null): string | undefined;
|
|
28
46
|
/** Record id, tolerating `id` / `_id` / a custom id field. */
|
|
29
47
|
export declare function getPersonId(record: any, idField?: string): any;
|
|
30
48
|
/**
|