@object-ui/fields 17.6.0 → 18.0.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 +6228 -0
- package/README.md +251 -9
- package/dist/FieldEditWidget.d.ts +54 -6
- package/dist/coerceToSafeValue.d.ts +54 -0
- package/dist/currency.d.ts +1 -1
- package/dist/fieldHelpText.d.ts +41 -0
- package/dist/index.cjs +5 -5
- package/dist/index.css +68 -5
- package/dist/index.d.ts +446 -69
- package/dist/index.js +5326 -3443
- package/dist/resolveActionParams.d.ts +259 -0
- package/dist/widgets/AvatarField.d.ts +19 -3
- package/dist/widgets/BooleanField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/FilterConditionField.d.ts +21 -7
- package/dist/widgets/FormulaField.d.ts +41 -0
- package/dist/widgets/GeolocationField.d.ts +9 -3
- package/dist/widgets/GridField.d.ts +73 -101
- package/dist/widgets/LocationField.d.ts +30 -3
- package/dist/widgets/LookupField.d.ts +8 -0
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +9 -2
- package/dist/widgets/OptionsEmptyState.d.ts +29 -5
- package/dist/widgets/PasswordField.d.ts +14 -0
- package/dist/widgets/PeoplePicker.d.ts +17 -2
- package/dist/widgets/PercentField.d.ts +2 -1
- package/dist/widgets/PersonRow.d.ts +13 -2
- package/dist/widgets/RecipientPickerField.d.ts +75 -0
- package/dist/widgets/RecordPickerDialog.d.ts +31 -12
- package/dist/widgets/RegistryRefField.d.ts +77 -0
- package/dist/widgets/RichTextField.d.ts +1 -1
- package/dist/widgets/SelectionTray.d.ts +13 -2
- package/dist/widgets/SummaryField.d.ts +30 -0
- package/dist/widgets/TextField.d.ts +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- package/dist/widgets/booleanValueLabel.d.ts +18 -0
- package/dist/widgets/date-picker/DatePickerField.d.ts +40 -0
- package/dist/widgets/date-picker/localeDateText.d.ts +39 -0
- package/dist/widgets/deriveLookupColumns.d.ts +1 -1
- package/dist/widgets/file-affordance.d.ts +63 -0
- package/dist/widgets/file-refused.d.ts +17 -0
- package/dist/widgets/file-size-guard.d.ts +5 -23
- package/dist/widgets/file-value.d.ts +49 -9
- package/dist/widgets/longTextCarrierHandoff.d.ts +35 -0
- package/dist/widgets/lookupColumnDisplay.d.ts +26 -9
- 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/objectRefLabel.d.ts +16 -0
- package/dist/widgets/personDisplay.d.ts +22 -4
- package/dist/widgets/recipientKinds.d.ts +85 -0
- package/dist/widgets/richTextDisplay.d.ts +32 -13
- package/dist/widgets/searchOffer.d.ts +8 -0
- package/dist/widgets/toDomProps.d.ts +26 -4
- package/dist/widgets/toHostGroupProps.d.ts +53 -0
- package/dist/widgets/toHostProps.d.ts +105 -0
- package/dist/widgets/types.d.ts +11 -3
- 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 +17 -2
- package/dist/widgets/useUploadingSignal.d.ts +8 -0
- package/package.json +11 -12
|
@@ -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";
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
* An object's name as the `object-ref` picker spells it, `Label (name)`, or the
|
|
10
|
+
* bare name when the object has no label (objectui#12613). `ObjectRefField`
|
|
11
|
+
* labels each option with it, and the console's read face for the field
|
|
12
|
+
* (`@object-ui/app-shell`'s widget reference cells) uses it for its tooltip, so
|
|
13
|
+
* the two faces spell an object one way. Published from the package barrel for
|
|
14
|
+
* that reader. No React here.
|
|
15
|
+
*/
|
|
16
|
+
export declare function objectRefOptionLabel(name: string, label: string | undefined): string;
|
|
@@ -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
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
* The recipient kinds a `recipient-picker` field stores, and where each kind's
|
|
10
|
+
* candidates and labels come from (objectui#12613).
|
|
11
|
+
*
|
|
12
|
+
* ONE table, two readers: `RecipientPickerField` (the edit widget) lists a
|
|
13
|
+
* kind's candidates from it, and the console's read face for the field
|
|
14
|
+
* (`@object-ui/app-shell`'s widget reference cells) names a stored recipient
|
|
15
|
+
* from it. They lived inside the widget until the face needed the same answer;
|
|
16
|
+
* a second copy beside the face is the drift this module exists to prevent, so
|
|
17
|
+
* a kind added here reaches both faces at once. Published from the package
|
|
18
|
+
* barrel for that reader. The picker's candidate query is not shared: it stops
|
|
19
|
+
* at 500 rows by name, and the face reads stored ids by exact id instead.
|
|
20
|
+
*
|
|
21
|
+
* No React here.
|
|
22
|
+
*/
|
|
23
|
+
export interface RecipientMapping {
|
|
24
|
+
object: string;
|
|
25
|
+
/** Which record field to persist into recipient_id. */
|
|
26
|
+
storeField: 'id' | 'name';
|
|
27
|
+
/** Candidate display-label fields, in preference order. */
|
|
28
|
+
labelFields: string[];
|
|
29
|
+
/**
|
|
30
|
+
* i18n key for the "choose one" placeholder. Keyed per type rather than
|
|
31
|
+
* interpolating the enum value into an English sentence — "Select a
|
|
32
|
+
* business unit" and "选择业务单元" share no structure (objectstack#3821).
|
|
33
|
+
*/
|
|
34
|
+
placeholderKey: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The recipient kinds whose candidates are RECORDS of one directory object,
|
|
38
|
+
* stored by the record field the evaluator matches on:
|
|
39
|
+
*
|
|
40
|
+
* user → sys_user, store `id`
|
|
41
|
+
* team → sys_team, store `id`
|
|
42
|
+
* business_unit → sys_business_unit, store `id`
|
|
43
|
+
* unit_and_subordinates → sys_business_unit, store `id`
|
|
44
|
+
*/
|
|
45
|
+
export declare const RECIPIENT_RECORD_KINDS: Readonly<Record<string, RecipientMapping>>;
|
|
46
|
+
/** A recipient kind whose candidates are catalog items of one metadata type. */
|
|
47
|
+
export interface RegistryRecipientMapping {
|
|
48
|
+
/** The metadata type listed — the registry's `GET /api/v1/meta/<type>`. */
|
|
49
|
+
type: string;
|
|
50
|
+
/** Catalog items are referenced by machine name (ADR-0131 D4). */
|
|
51
|
+
storeField: 'name';
|
|
52
|
+
/** Candidate display-label keys, in preference order. */
|
|
53
|
+
labelFields: string[];
|
|
54
|
+
/** i18n key for the "choose one" placeholder (see {@link RecipientMapping}). */
|
|
55
|
+
placeholderKey: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The recipient kinds whose candidates come from the registry (objectui#7611).
|
|
59
|
+
*
|
|
60
|
+
* ADR-0131 D3 gives positions one home, the environment registry, and D7 says
|
|
61
|
+
* a picker LISTS that registry: one source, with no second list of
|
|
62
|
+
* `sys_position` rows merged in. The read goes through the console's metadata
|
|
63
|
+
* store (`useMetadata().ensureType`), the same reader that serves apps and
|
|
64
|
+
* objects, so the list is fetched once per session and shared with every
|
|
65
|
+
* other registry consumer.
|
|
66
|
+
*/
|
|
67
|
+
export declare const RECIPIENT_REGISTRY_KINDS: Readonly<Record<string, RegistryRecipientMapping>>;
|
|
68
|
+
/**
|
|
69
|
+
* The RECORD-RELATIVE recipient kind (maintainer ruling objectstack#14103,
|
|
70
|
+
* executor objectstack#15072). It is deliberately absent from
|
|
71
|
+
* `RECIPIENT_RECORD_KINDS`: there is no target object to query and no record id to
|
|
72
|
+
* store. `recipient_id` holds the NAME of a user-valued column on the SHARED
|
|
73
|
+
* object, and the recipients are whoever that column names on each matched
|
|
74
|
+
* record — so this mode reads the object chosen in the sibling `object_name`
|
|
75
|
+
* field (the same dependency the `filter-condition` widget already reads) and
|
|
76
|
+
* offers that object's user-valued fields.
|
|
77
|
+
*/
|
|
78
|
+
export declare const FIELD_RECIPIENT_TYPE = "field";
|
|
79
|
+
/**
|
|
80
|
+
* The display label of one candidate: the first non-empty member of the
|
|
81
|
+
* kind's `labelFields`, then the record id. The picker labels its options with
|
|
82
|
+
* it and the read cell names a stored recipient with it, so a recipient reads
|
|
83
|
+
* the same in the list and in the open picker.
|
|
84
|
+
*/
|
|
85
|
+
export declare function recipientCandidateLabel(labelFields: readonly string[] | undefined, row: unknown): string;
|
|
@@ -12,6 +12,10 @@ import { CellRendererProps } from '../index.js';
|
|
|
12
12
|
* pipeline drops all of it; see {@link HtmlCellRenderer}, which is where that
|
|
13
13
|
* type belongs. Loosening this pipeline to pass raw HTML through would have
|
|
14
14
|
* "fixed" one type by moving every `markdown` cell's trust boundary.
|
|
15
|
+
*
|
|
16
|
+
* What the cell holds is decided by {@link richTextSource} (objectui#8580):
|
|
17
|
+
* `[]` is the No-value affordance, never a childless container, and an
|
|
18
|
+
* object is the text the string class prints for it, never `[object Object]`.
|
|
15
19
|
*/
|
|
16
20
|
export declare function MarkdownCellRenderer({ value }: CellRendererProps): React.ReactElement;
|
|
17
21
|
/**
|
|
@@ -27,6 +31,14 @@ export declare function MarkdownCellRenderer({ value }: CellRendererProps): Reac
|
|
|
27
31
|
* emits — headings, paragraphs, emphasis, lists, links, quotes all survive, so
|
|
28
32
|
* routing `richtext` here restores the content rather than trading a blank cell
|
|
29
33
|
* for a mangled one.
|
|
34
|
+
*
|
|
35
|
+
* What the cell holds is decided by {@link richTextSource} (objectui#8580):
|
|
36
|
+
* `[]` is the No-value affordance, never a childless `prose` container, and
|
|
37
|
+
* an object is the text the string class prints for it. The populated branch
|
|
38
|
+
* stays on {@link sanitizeHtml}: a stored string comes back from
|
|
39
|
+
* `richTextSource` verbatim, so the bytes that reach the sanitiser are the
|
|
40
|
+
* bytes that always did — pinned byte-for-byte in
|
|
41
|
+
* `__tests__/cellRenderers.childlessContainer-8580.test.tsx`.
|
|
30
42
|
*/
|
|
31
43
|
export declare function HtmlCellRenderer({ value }: CellRendererProps): React.ReactElement;
|
|
32
44
|
/**
|
|
@@ -51,22 +63,29 @@ export declare const RICH_TEXT_CELL_RENDERERS: {
|
|
|
51
63
|
/** The rich-content field types, i.e. the keys of {@link RICH_TEXT_CELL_RENDERERS}. */
|
|
52
64
|
export type RichTextFieldType = keyof typeof RICH_TEXT_CELL_RENDERERS;
|
|
53
65
|
/**
|
|
54
|
-
* The
|
|
55
|
-
*
|
|
66
|
+
* The same three types as a RUNTIME list, DERIVED from THE table rather than
|
|
67
|
+
* spelled a second time — the type-level {@link RichTextFieldType} cannot be
|
|
68
|
+
* enumerated at runtime, and a hand-written array beside it would be exactly
|
|
69
|
+
* the shape this export exists to retire.
|
|
70
|
+
*
|
|
71
|
+
* ## Why this export exists
|
|
56
72
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
73
|
+
* objectui#4831 asked, in its own body, whether the hand-written type lists
|
|
74
|
+
* that keep omitting the third of this widget's three registry keys should
|
|
75
|
+
* become "every type that resolves to the long-text widget". It was answered
|
|
76
|
+
* by adding one literal, and the omission recurred (objectui#4250,
|
|
77
|
+
* objectui#8438). This is that question answered in the other direction: a
|
|
78
|
+
* consumer that spreads this list cannot omit a key, because it never names
|
|
79
|
+
* one. Adding a fourth key to {@link RICH_TEXT_CELL_RENDERERS} extends every
|
|
80
|
+
* such consumer in the same commit that adds the key.
|
|
64
81
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
82
|
+
* ⚠️ It is NOT a general "long text" list and must not be used as one:
|
|
83
|
+
* `textarea` renders a `<Textarea>` too and is deliberately absent, because it
|
|
84
|
+
* is a different widget with its own registry key and its own metadata face.
|
|
85
|
+
* The invariant this list states is narrower and exact — *these are the keys
|
|
86
|
+
* `RichTextField` serves*.
|
|
68
87
|
*/
|
|
69
|
-
export declare
|
|
88
|
+
export declare const RICH_TEXT_FIELD_TYPES: readonly RichTextFieldType[];
|
|
70
89
|
/**
|
|
71
90
|
* The display pipeline a field type reads through, or `undefined` when the
|
|
72
91
|
* type is not one of the three rich-content types.
|
|
@@ -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 declare function objectResolvesSearch(objectSchema: unknown): boolean | undefined;
|
|
@@ -83,9 +83,31 @@ import { DomProps as CoreDomProps } from '@object-ui/core';
|
|
|
83
83
|
* the contract it executes. Not two judges.
|
|
84
84
|
*/
|
|
85
85
|
declare const DOM_PASS_THROUGH_KEYS: readonly ["id", "name", "autoFocus", "tabIndex", "onBlur", "onFocus", "onClick", "className", "disabled"];
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The keys this helper forwards.
|
|
88
|
+
*
|
|
89
|
+
* Exported so the SIBLING executor — `toHostProps`, which carries the declared
|
|
90
|
+
* NON-DOM keys (`f08bcd9af`) — can subtract this set from the contract and
|
|
91
|
+
* assert that the two together cover every declared key exactly once. Without
|
|
92
|
+
* that subtraction there is no way to state "these keys are handled elsewhere"
|
|
93
|
+
* as a compile-time fact rather than as a comment.
|
|
94
|
+
*/
|
|
95
|
+
export type DomPassThroughKey = (typeof DOM_PASS_THROUGH_KEYS)[number];
|
|
96
|
+
/**
|
|
97
|
+
* The subset of `P` this helper forwards, with each key's declared type.
|
|
98
|
+
*
|
|
99
|
+
* Not {@link FieldWidgetDomProps}: that is the contract's DOM block, the
|
|
100
|
+
* non-generic set of keys a widget may receive; this is the generic RESULT
|
|
101
|
+
* type of {@link toDomProps}, whatever `P` it is handed narrowed to this
|
|
102
|
+
* helper's key set.
|
|
103
|
+
*
|
|
104
|
+
* Not `DomProps`: `@object-ui/core` publishes that name for the general
|
|
105
|
+
* mechanism, whose default key set forwards `role` where this one forwards
|
|
106
|
+
* `name` and `disabled`. One exported name stood for both key sets until this
|
|
107
|
+
* declaration was renamed, the way `@object-ui/components` names its own
|
|
108
|
+
* specialisation `FormControlDomProps` (objectui#6349, batch 11).
|
|
109
|
+
*/
|
|
110
|
+
export type FieldDomProps<P> = CoreDomProps<P, DomPassThroughKey>;
|
|
89
111
|
/**
|
|
90
112
|
* Keep only what may legitimately become a DOM attribute, and drop everything
|
|
91
113
|
* else — renderer plumbing, and any extra key an author put on the field
|
|
@@ -107,5 +129,5 @@ export type DomProps<P> = CoreDomProps<P, DomPassThroughKey>;
|
|
|
107
129
|
* `readonly`, `error`, …) are read from `props` as before; this function only
|
|
108
130
|
* governs what gets spread.
|
|
109
131
|
*/
|
|
110
|
-
export declare function toDomProps<P extends object>(props: P):
|
|
132
|
+
export declare function toDomProps<P extends object>(props: P): FieldDomProps<P>;
|
|
111
133
|
export {};
|
|
@@ -187,3 +187,56 @@ export declare function toHostGroupProps(props: {
|
|
|
187
187
|
'aria-labelledby'?: string;
|
|
188
188
|
'aria-describedby'?: string;
|
|
189
189
|
}, surface: HostGroupSurface): HostGroupProps;
|
|
190
|
+
/**
|
|
191
|
+
* The host keys a LABELABLE surface may consume — the `labelling: 'control'`
|
|
192
|
+
* counterpart of {@link HostGroupProps} (objectui#8803).
|
|
193
|
+
*
|
|
194
|
+
* ## Why a second, smaller bag instead of reusing the group one
|
|
195
|
+
*
|
|
196
|
+
* A widget declared `'control'` is told nothing by the host: its label emits a
|
|
197
|
+
* plain `for` and expects to reach a labelable element on its own. Every
|
|
198
|
+
* editable branch answers that by spreading `toDomProps` onto the control it
|
|
199
|
+
* renders. The branches that return EARLY answer it with nothing — and for the
|
|
200
|
+
* single `SelectField` that is the zero-option box, where the `…-form-item` id
|
|
201
|
+
* reached no element at all and the label's `for` DANGLED.
|
|
202
|
+
*
|
|
203
|
+
* What such a surface needs is therefore the exact complement of the group
|
|
204
|
+
* bag:
|
|
205
|
+
*
|
|
206
|
+
* - `id` — yes. It is the id the `for` points at, and landing it is the whole
|
|
207
|
+
* repair;
|
|
208
|
+
* - `aria-describedby` — yes, for the objectui#4005 reason, unchanged by the
|
|
209
|
+
* surface being labelable: the field renders no input in this state, so
|
|
210
|
+
* this box is the only element the visible help text can be announced on;
|
|
211
|
+
* - `aria-labelledby` — ⛔ NO. The `for` already names this element. Adding an
|
|
212
|
+
* IDREF beside it is the second naming channel objectui#3978 removed;
|
|
213
|
+
* - `role` — ⛔ NO. A labelable element's own semantics are what make the name
|
|
214
|
+
* land; a synthetic role on top is the category error objectui#3991 named.
|
|
215
|
+
*
|
|
216
|
+
* `aria-invalid` / `aria-required` / `name` stay off for the reasons spelled
|
|
217
|
+
* out at length on {@link HostGroupProps}: control-channel state on a surface
|
|
218
|
+
* nobody can edit, and the DOM leak objectui#3291 sweeps for. A closed type,
|
|
219
|
+
* not an open props tail, for that same reason.
|
|
220
|
+
*/
|
|
221
|
+
export interface HostControlProps {
|
|
222
|
+
/** The host's control id — the id its label's `for` points at. */
|
|
223
|
+
id?: string;
|
|
224
|
+
/** IDREF of the host's visible help text. */
|
|
225
|
+
'aria-describedby'?: string;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Read the two whole-field keys a labelable replacement surface may carry. See
|
|
229
|
+
* {@link HostControlProps}.
|
|
230
|
+
*
|
|
231
|
+
* Routed through {@link toDomProps} for the same reason {@link toHostGroupProps}
|
|
232
|
+
* is: the keys keep ONE gate, and its compile-time assertions bind that
|
|
233
|
+
* whitelist to the props contract in both directions.
|
|
234
|
+
*
|
|
235
|
+
* Standalone rendering (the grid's inline cell editor, an action param dialog,
|
|
236
|
+
* a bare SDUI node) hands down neither key, so both values are `undefined`,
|
|
237
|
+
* React emits no attribute, and that markup stays byte-identical.
|
|
238
|
+
*/
|
|
239
|
+
export declare function toHostControlProps(props: {
|
|
240
|
+
id?: string;
|
|
241
|
+
'aria-describedby'?: string;
|
|
242
|
+
}): HostControlProps;
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
* The RUNTIME EXECUTOR of the NON-DOM half of {@link FieldWidgetComponentProps}
|
|
10
|
+
* (`f08bcd9af`) — the sibling of `toDomProps`, and deliberately a SEPARATE
|
|
11
|
+
* function rather than more entries in that whitelist.
|
|
12
|
+
*
|
|
13
|
+
* ## The defect
|
|
14
|
+
*
|
|
15
|
+
* objectui#6909 / #7009 closed the DOM half at `FieldEditWidget`: the factory
|
|
16
|
+
* hands the widget `toDomProps(props)`, so `id` / `name` / `tabIndex` /
|
|
17
|
+
* `aria-*` / `data-*` and friends finally arrive. The contract also declares a
|
|
18
|
+
* "Host plumbing" block plus two controlled-input keys the factory neither owns
|
|
19
|
+
* nor routes to the DOM, and NOTHING carried those. A host could pass any of
|
|
20
|
+
* them with no type error and the widget never received it — this package's own
|
|
21
|
+
* first-class defect class, named in `toDomProps.ts`: "a key that type-checks,
|
|
22
|
+
* reads as supported, and silently never reaches the element" (objectui#3290's
|
|
23
|
+
* `aria-required`, objectui#3222's validation slot).
|
|
24
|
+
*
|
|
25
|
+
* Measured on `main` at `71d83a6b1`, the live victim was `error`:
|
|
26
|
+
* `InlineFieldInput` (`@object-ui/plugin-detail`, since PR #7109) already
|
|
27
|
+
* passes `error={error}` to `FieldEditWidget`, and the factory dropped it on
|
|
28
|
+
* the floor — so a control that had failed validation never reported
|
|
29
|
+
* `aria-invalid` and a screen-reader user was never told. The kanban
|
|
30
|
+
* `RequiredFieldsDialog` had the same hole from the other side: it computes the
|
|
31
|
+
* validation state, renders it in red text, and had no way to hand it over.
|
|
32
|
+
*
|
|
33
|
+
* ## Why NOT more keys in `DOM_PASS_THROUGH_KEYS`
|
|
34
|
+
*
|
|
35
|
+
* Because none of them is DOM-legal. That whitelist is deliberately closed and
|
|
36
|
+
* exists precisely to stop renderer plumbing reaching an element — its own
|
|
37
|
+
* header names `error`, `emptyHint`, `dataSource`, `dependentValues` and
|
|
38
|
+
* `dependsOn` as examples of what a blacklist would have failed to catch. Put
|
|
39
|
+
* them there and a `dataSource` adapter becomes `dataSource="[object Object]"`
|
|
40
|
+
* on an `<input>`. They travel as COMPONENT props, which is what they are.
|
|
41
|
+
*
|
|
42
|
+
* ## Declared = enforced, in both directions
|
|
43
|
+
*
|
|
44
|
+
* The three assertions below make the two executors PARTITION the contract:
|
|
45
|
+
* every declared key is DOM-handled, forwarded here, or named as one the
|
|
46
|
+
* factory itself owns. A key added to `FieldWidgetComponentProps` that is none
|
|
47
|
+
* of those is a compile error, so the next non-DOM key cannot repeat this bug.
|
|
48
|
+
*
|
|
49
|
+
* ## Precedence: this is a CONDUIT, and never a second authority
|
|
50
|
+
*
|
|
51
|
+
* Several of these keys have a context-based fallback inside the widget that
|
|
52
|
+
* reads them, so DELIVERING one can change behaviour where before it could not
|
|
53
|
+
* arrive at all. The rule is stated once, here, and this function implements it
|
|
54
|
+
* by doing nothing: it forwards what the host passed and resolves NOTHING.
|
|
55
|
+
* Each key's precedence stays where its single reader already implements and
|
|
56
|
+
* documents it —
|
|
57
|
+
*
|
|
58
|
+
* - `dataSource`: **the explicit prop WINS** over `SchemaRendererContext`.
|
|
59
|
+
* `LookupField` already resolves exactly that order and says so on the line
|
|
60
|
+
* that does it: "explicit prop > field-level > wrapper field >
|
|
61
|
+
* SchemaRendererContext > none". Hosts that pass nothing keep reading the
|
|
62
|
+
* context, so the grid's inline editor is unaffected.
|
|
63
|
+
* - `dependentValues`: the explicit prop is the ONLY channel that can carry a
|
|
64
|
+
* record — the host supplies it, and there is NO context fallback. Both
|
|
65
|
+
* readers (`useCascadingOptions`, and `LookupField`'s own resolver) used to
|
|
66
|
+
* spell a `?? ctx.formValues ?? ctx.data` tail after it, but
|
|
67
|
+
* `SchemaRendererContextType` declares exactly `dataSource` / `debug` /
|
|
68
|
+
* `debugFlags` / `apiFetch` — neither member exists, so that tail was
|
|
69
|
+
* unconditionally `{}` and no host could change it. It was retired under
|
|
70
|
+
* ADR-0049 enforce-or-remove (objectui#7206). Unlike `dataSource` above,
|
|
71
|
+
* DELIVERING this key cannot displace a context value, because there has
|
|
72
|
+
* never been one to displace.
|
|
73
|
+
* - `dependsOn`: the FIELD METADATA wins over the prop — the one documented
|
|
74
|
+
* inversion, stated on the key's own doc comment and implemented as
|
|
75
|
+
* `field?.dependsOn ?? dependsOnProp` in all four option widgets.
|
|
76
|
+
* - `emptyHint`: the host's value wins when supplied (objectui#3231).
|
|
77
|
+
* - `onCreateNew`: the prop wins over `field.onCreateNew`; `onSelectRecord`
|
|
78
|
+
* has no metadata carrier at all, so the prop is its ONLY one.
|
|
79
|
+
*
|
|
80
|
+
* Resolving any of that HERE would give each key a second author — the
|
|
81
|
+
* `field || schema` shape objectui#3233 spent a release removing. One key, one
|
|
82
|
+
* resolver, in the widget that reads it.
|
|
83
|
+
*/
|
|
84
|
+
declare const HOST_PLUMBING_KEYS: readonly ["error", "onUploadingChange", "dataSource", "dependentValues", "dependsOn", "dependsOnLabels", "emptyHint", "onSelectRecord", "onCreateNew"];
|
|
85
|
+
type HostPlumbingKey = (typeof HOST_PLUMBING_KEYS)[number];
|
|
86
|
+
/** The subset of `P` this helper forwards, with each key's declared type. */
|
|
87
|
+
export type HostProps<P> = Pick<P, Extract<keyof P, HostPlumbingKey>>;
|
|
88
|
+
/**
|
|
89
|
+
* Keep only the declared NON-DOM keys — the host plumbing a widget interprets
|
|
90
|
+
* as component props — and drop everything else.
|
|
91
|
+
*
|
|
92
|
+
* The companion to `toDomProps`, and used the same way. A host that passes
|
|
93
|
+
* nothing gets an empty object, so the widget's prop set is unchanged:
|
|
94
|
+
*
|
|
95
|
+
* ```tsx
|
|
96
|
+
* <Widget {...toDomProps(props)} {...toHostProps(props)} field={field} … />
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* Iterating the props (rather than the key list) mirrors `pickDomProps`, so a
|
|
100
|
+
* key the host did not mention stays ABSENT rather than arriving as
|
|
101
|
+
* `undefined` — which keeps the factory-boundary prop set readable as "what the
|
|
102
|
+
* host actually supplied".
|
|
103
|
+
*/
|
|
104
|
+
export declare function toHostProps<P extends object>(props: P): HostProps<P>;
|
|
105
|
+
export {};
|
package/dist/widgets/types.d.ts
CHANGED
|
@@ -150,7 +150,7 @@ export type FieldWidgetComponentProps<T = any> = {
|
|
|
150
150
|
*/
|
|
151
151
|
error?: string;
|
|
152
152
|
/**
|
|
153
|
-
* Upload widgets (`file`/`image`) fire this when their in-progress state
|
|
153
|
+
* Upload widgets (`file`/`image`/`avatar`) fire this when their in-progress state
|
|
154
154
|
* flips, so a host can block submit until a presigned upload settles. Other
|
|
155
155
|
* widgets ignore it.
|
|
156
156
|
*/
|
|
@@ -173,7 +173,15 @@ export type FieldWidgetComponentProps<T = any> = {
|
|
|
173
173
|
/**
|
|
174
174
|
* Live sibling-field values driving cascading / role-gated options and
|
|
175
175
|
* dependent lookups (ADR-0058, #2215/#2284). The form renderer passes the
|
|
176
|
-
* in-progress record
|
|
176
|
+
* in-progress record.
|
|
177
|
+
*
|
|
178
|
+
* ⚠️ This used to add "widgets fall back to `SchemaRendererContext`". They
|
|
179
|
+
* never could: they spelled such a fallback (`?? ctx.formValues ?? ctx.data`)
|
|
180
|
+
* while `SchemaRendererContextType` declares exactly `dataSource` / `debug` /
|
|
181
|
+
* `debugFlags` / `apiFetch`, so neither member could be supplied by any host
|
|
182
|
+
* and the fallback was unconditionally empty. Those reads were retired under
|
|
183
|
+
* ADR-0049 enforce-or-remove (objectui#7206) — this prop is the one channel
|
|
184
|
+
* that carries a record, and a widget reached without it gates.
|
|
177
185
|
*/
|
|
178
186
|
dependentValues?: Record<string, unknown>;
|
|
179
187
|
/**
|
|
@@ -187,7 +195,7 @@ export type FieldWidgetComponentProps<T = any> = {
|
|
|
187
195
|
* sibling field in user-visible copy (the dependent-lookup gate hint).
|
|
188
196
|
*
|
|
189
197
|
* Only the form renderer knows a field's label — the widget sees its own
|
|
190
|
-
* metadata and a `
|
|
198
|
+
* metadata and a `dependsOn` list of API names. Without this map the gate
|
|
191
199
|
* sentence interpolated the raw API name into every locale, `en` included
|
|
192
200
|
* ("Select crm_account first"), which is an internal identifier leaking into
|
|
193
201
|
* the UI, not merely an untranslated word (objectstack#5407). This is the
|