@object-ui/fields 17.3.0 → 17.5.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 +941 -0
- package/README.md +1 -12
- package/dist/FieldEditWidget.d.ts +7 -1
- package/dist/currency.d.ts +46 -0
- package/dist/field-type-alias.d.ts +19 -1
- package/dist/index.cjs +4 -4
- package/dist/index.css +633 -0
- package/dist/index.d.ts +48 -12
- package/dist/index.js +5026 -4933
- package/dist/widgets/AddressField.d.ts +3 -11
- package/dist/widgets/FullscreenFieldEditor.d.ts +42 -152
- package/dist/widgets/GridField.d.ts +12 -2
- package/dist/widgets/OptionsEmptyState.d.ts +15 -1
- package/dist/widgets/RecordPickerDialog.d.ts +32 -6
- package/dist/widgets/SelectField.d.ts +25 -5
- package/dist/widgets/address-format.d.ts +68 -0
- package/dist/widgets/file-size-guard.d.ts +41 -0
- package/dist/widgets/nativeDateValue.d.ts +37 -0
- package/dist/widgets/toDomProps.d.ts +30 -10
- package/dist/widgets/toHostGroupProps.d.ts +91 -0
- package/dist/widgets/useRecordQuery.d.ts +7 -1
- package/package.json +17 -13
- package/src/FieldEditWidget.tsx +17 -1
- package/src/__tests__/AddressCellRenderer.test.tsx +166 -0
- package/src/__tests__/CurrencyField.minorUnits.test.tsx +191 -0
- package/src/__tests__/CurrencyField.symbol.test.tsx +198 -0
- package/src/__tests__/DateCellRenderer.test.tsx +9 -0
- package/src/__tests__/NumberCellRenderer.grouping.test.tsx +146 -0
- package/src/__tests__/PercentCellRenderer.locale.test.tsx +152 -0
- package/src/__tests__/PercentCellRenderer.test.tsx +115 -0
- package/src/__tests__/boolean-label-association-e2e.test.tsx +298 -0
- package/src/__tests__/capability-multiselect-retired.test.ts +109 -0
- package/src/__tests__/composite-group-label-e2e.test.tsx +436 -0
- package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +403 -0
- package/src/__tests__/date-formatter-residue-4272.test.ts +120 -0
- package/src/__tests__/date-locale-channel.test.tsx +305 -0
- package/src/__tests__/formatCurrency.centsDigit.test.tsx +158 -0
- package/src/__tests__/formatCurrency.minorUnits.test.tsx +221 -0
- package/src/__tests__/group-labelling-declaration.test.ts +148 -0
- package/src/__tests__/lookupCellRecordLink.test.tsx +171 -0
- package/src/__tests__/percent-cell-vs-measure-4576.test.ts +115 -0
- package/src/__tests__/percent-formatter-locale-4553.test.ts +147 -0
- package/src/__tests__/percent-tie-halfup-4590.test.ts +224 -0
- package/src/__tests__/select-label-association-e2e.test.tsx +199 -0
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +29 -270
- package/src/complex-widgets.test.tsx +22 -6
- package/src/currency.ts +130 -0
- package/src/datetime-widgets.test.tsx +94 -2
- package/src/field-type-alias.multiple.test.ts +81 -0
- package/src/field-type-alias.ts +58 -2
- package/src/index.css +44 -0
- package/src/index.tsx +489 -247
- package/src/standard-widgets.test.tsx +8 -1
- package/src/widgets/AddressField.postalCode.test.tsx +257 -0
- package/src/widgets/AddressField.tsx +79 -29
- package/src/widgets/AddressField.uniqueIds.test.tsx +2 -1
- package/src/widgets/BooleanField.tsx +41 -7
- package/src/widgets/CapabilityMultiSelectField.tsx +17 -5
- package/src/widgets/CheckboxesField.tsx +35 -2
- package/src/widgets/CurrencyField.tsx +55 -15
- package/src/widgets/DateField.tsx +11 -2
- package/src/widgets/DateTimeField.tsx +12 -3
- package/src/widgets/FileField.tsx +27 -9
- package/src/widgets/FormulaField.tsx +6 -1
- package/src/widgets/FullscreenFieldEditor.tsx +47 -258
- package/src/widgets/GeolocationField.tsx +29 -3
- package/src/widgets/GridField.test.tsx +226 -0
- package/src/widgets/GridField.tsx +114 -10
- package/src/widgets/ImageField.maxSize.test.tsx +154 -0
- package/src/widgets/ImageField.tsx +54 -4
- package/src/widgets/LookupField.hydration.test.tsx +1 -1
- package/src/widgets/LookupField.tsx +8 -4
- package/src/widgets/MultiSelectField.tsx +56 -2
- package/src/widgets/OptionsEmptyState.tsx +16 -0
- package/src/widgets/RadioField.tsx +35 -3
- package/src/widgets/RatingField.tsx +15 -2
- package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +191 -0
- package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +124 -0
- package/src/widgets/RecordPickerDialog.tsx +64 -15
- package/src/widgets/RichTextField.tsx +1 -1
- package/src/widgets/SelectField.tsx +36 -5
- package/src/widgets/UserField.tsx +3 -2
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +25 -1
- package/src/widgets/address-format.ts +120 -0
- package/src/widgets/file-size-guard.ts +55 -0
- package/src/widgets/nativeDateValue.test.ts +115 -0
- package/src/widgets/nativeDateValue.ts +112 -0
- package/src/widgets/optionWidgets.gatedOptions.test.tsx +267 -0
- package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +150 -0
- package/src/widgets/toDomProps.ts +48 -19
- package/src/widgets/toHostGroupProps.ts +102 -0
- package/src/widgets/useFieldTranslation.ts +15 -33
- package/src/widgets/useRecordQuery.ts +30 -3
- package/dist/FieldEditWidget.test.d.ts +0 -8
- package/dist/__tests__/DateCellRenderer.test.d.ts +0 -0
- package/dist/__tests__/NumberCellRenderer.test.d.ts +0 -0
- package/dist/__tests__/cell-truncation.test.d.ts +0 -0
- package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
- package/dist/__tests__/lookupCellDisplayField.test.d.ts +0 -0
- package/dist/__tests__/lookupCellDisplayName.test.d.ts +0 -0
- package/dist/__tests__/lookupCellReferenceKey.test.d.ts +0 -0
- package/dist/__tests__/registry-collision.test.d.ts +0 -0
- package/dist/__tests__/resolveFieldCurrency.test.d.ts +0 -1
- package/dist/__tests__/spec-symbol-batch7.test.d.ts +0 -8
- package/dist/__tests__/validation-feedback.test.d.ts +0 -0
- package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-props-contract.test.d.ts +0 -8
- package/dist/coerce-safe-value.test.d.ts +0 -1
- package/dist/complex-widgets.test.d.ts +0 -1
- package/dist/datetime-widgets.test.d.ts +0 -1
- package/dist/field-type-coverage.test.d.ts +0 -1
- package/dist/standard-widgets.test.d.ts +0 -0
- package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
- package/dist/widgets/CapabilityMultiSelectField.test.d.ts +0 -0
- package/dist/widgets/CheckboxesField.cascade.test.d.ts +0 -0
- package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
- package/dist/widgets/GridField.test.d.ts +0 -1
- package/dist/widgets/ImageLightbox.test.d.ts +0 -1
- package/dist/widgets/LookupField.dependsOn.test.d.ts +0 -8
- package/dist/widgets/LookupField.gateHintLabel.test.d.ts +0 -8
- package/dist/widgets/LookupField.hydration.test.d.ts +0 -1
- package/dist/widgets/LookupField.idField.test.d.ts +0 -1
- package/dist/widgets/MultiSelectField.cascade.test.d.ts +0 -0
- package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
- package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
- package/dist/widgets/PeoplePicker.test.d.ts +0 -8
- package/dist/widgets/PeoplePickerComponents.test.d.ts +0 -8
- package/dist/widgets/RadioField.cascade.test.d.ts +0 -0
- package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
- package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +0 -8
- package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +0 -8
- package/dist/widgets/SelectField.cascade.test.d.ts +0 -0
- package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
- package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
- package/dist/widgets/UserFieldPicker.test.d.ts +0 -8
- package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +0 -8
- package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +0 -8
- package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
- package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
- package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
- package/dist/widgets/deriveLookupColumns.test.d.ts +0 -1
- package/dist/widgets/file-value.test.d.ts +0 -1
- package/dist/widgets/personDisplay.test.d.ts +0 -8
- package/dist/widgets/recentLookups.test.d.ts +0 -1
- package/dist/widgets/useRecordQuery.test.d.ts +0 -8
- package/dist/widgets/useUploadingSignal.test.d.ts +0 -1
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { FieldWidgetComponentProps } from './types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export interface AddressValue {
|
|
7
|
-
street?: string;
|
|
8
|
-
city?: string;
|
|
9
|
-
state?: string;
|
|
10
|
-
zipCode?: string;
|
|
11
|
-
country?: string;
|
|
12
|
-
}
|
|
3
|
+
import { AddressValue } from './address-format';
|
|
4
|
+
export type { AddressValue } from './address-format';
|
|
13
5
|
/**
|
|
14
6
|
* Address field widget - provides a structured address input
|
|
15
|
-
* Supports street, city, state,
|
|
7
|
+
* Supports street, city, state, postal code, and country
|
|
16
8
|
*/
|
|
17
9
|
export declare function AddressField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<AddressValue>): React.JSX.Element;
|
|
@@ -1,155 +1,45 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
+
import { FullscreenEditorProps } from '@object-ui/components';
|
|
2
3
|
/**
|
|
3
|
-
* The fullscreen-edit affordance shared by every long-text widget
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* ##
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `disabled`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* before the fix: inline `disabled=true`, toggle `disabled=false`, dialog input
|
|
43
|
-
* `disabled=false`, `onChange` called with "EDITED WHILE DISABLED".
|
|
44
|
-
*
|
|
45
|
-
* The gate is shaped like the built-in path's (`FullscreenTextarea` in
|
|
46
|
-
* `components/src/renderers/form/form.tsx`, objectui#3400/PR #3401), because
|
|
47
|
-
* one form-level setting must keep producing one behaviour on both paths:
|
|
48
|
-
*
|
|
49
|
-
* - the toggle STAYS but is `disabled` — `disabled` means "not interactive,
|
|
50
|
-
* muted", not "shown plainly", which is `readonly`'s job (and `readonly`
|
|
51
|
-
* never reaches this component at all — see below);
|
|
52
|
-
* - `openFullscreen` refuses independently of the attribute, since a
|
|
53
|
-
* programmatic dispatch and a lost `pointer-events` rule both get past it;
|
|
54
|
-
* - the dialog locks on its OWN — `disabled` is not merely a static flag, it is
|
|
55
|
-
* also the form's `isSubmitting`, which flips to true while the dialog may
|
|
56
|
-
* already be open. At that moment the toggle is no longer the gate. So the
|
|
57
|
-
* editor is told (third `children` argument) and "Done" is disabled;
|
|
58
|
-
* - and `onCommit` is gated, because that is the single point where a value can
|
|
59
|
-
* leave this component for host state. Nothing native guards it: it is a
|
|
60
|
-
* click handler on a different control reading React state.
|
|
61
|
-
*
|
|
62
|
-
* "Cancel" and `Esc` stay live in every state — a dialog that goes disabled
|
|
63
|
-
* mid-edit must still be closable, or a submit in flight traps the user in it.
|
|
64
|
-
*
|
|
65
|
-
* ## Why there is no `readonly` prop
|
|
66
|
-
*
|
|
67
|
-
* Both hosts early-return a read-only DISPLAY before they compute the affordance
|
|
68
|
-
* (`TextAreaField.tsx`, `RichTextField.tsx`), so this component is never
|
|
69
|
-
* rendered for a read-only field and a `readonly` prop here would be declared
|
|
70
|
-
* with no producer — the shape this package keeps deleting (objectui#3232/#3233).
|
|
71
|
-
* A future host that renders an editor for read-only fields must add the prop
|
|
72
|
-
* AND the producer together; do not add it "for symmetry" beforehand.
|
|
73
|
-
*
|
|
74
|
-
* ## `testIdPrefix`
|
|
75
|
-
*
|
|
76
|
-
* Each host passes its own (`textarea`, `richtext`), yielding the same
|
|
77
|
-
* convention with a per-widget namespace: `<prefix>-fullscreen-toggle` /
|
|
78
|
-
* `-dialog` / `-save`. One form can contain both a textarea and a markdown
|
|
79
|
-
* field, so a single shared id would make a test unable to say which widget it
|
|
80
|
-
* had found. The editor's own test id belongs to `children` for the same
|
|
81
|
-
* reason.
|
|
82
|
-
*
|
|
83
|
-
* ## Copy (objectui#3404)
|
|
84
|
-
*
|
|
85
|
-
* Every string here goes through `useFieldTranslation()` and consumes the
|
|
86
|
-
* EXISTING `form.fullscreen.*` / `common.cancel` keys — the same ones the
|
|
87
|
-
* built-in branch consumes since objectui#3272. Nothing was missing from the
|
|
88
|
-
* ten locale packs; this path simply never read them, so a zh session opened a
|
|
89
|
-
* dialog reading `Cancel` / `Done` while a long-text field rendered by the
|
|
90
|
-
* built-in branch **in the same form** read 「取消 / 完成」.
|
|
91
|
-
*
|
|
92
|
-
* Sharing the keys rather than minting `fields.fullscreen.*` twins is the
|
|
93
|
-
* point: one form-level setting (`mobile.fullscreenLongText`) projects onto
|
|
94
|
-
* both render paths, so two copies of this copy could only drift.
|
|
95
|
-
*
|
|
96
|
-
* `useFieldTranslation` (not bare `useObjectTranslation`) because this widget
|
|
97
|
-
* is rendered without an `I18nProvider` by standalone/embedded hosts and by
|
|
98
|
-
* every bare-render test in this package. Measured before the change: with no
|
|
99
|
-
* provider mounted `useObjectTranslation().t('common.cancel')` returns
|
|
100
|
-
* `"common.cancel"` — the raw key — and the `{{label}}` interpolation collapses
|
|
101
|
-
* entirely. The safe hook's English defaults are byte-identical to the literals
|
|
102
|
-
* they replaced, so the provider-less rendering is unchanged.
|
|
103
|
-
*
|
|
104
|
-
* Focus management, `Esc`, the overlay and the close button are Radix's, via
|
|
105
|
-
* the repo's `Dialog` (`@object-ui/components`) — deliberately not re-answered
|
|
106
|
-
* here.
|
|
4
|
+
* The fullscreen-edit affordance shared by every long-text widget in this
|
|
5
|
+
* package (`field:textarea` → `TextAreaField`, `field:markdown` / `field:html`
|
|
6
|
+
* → `RichTextField`).
|
|
7
|
+
*
|
|
8
|
+
* ## This is now a thin wrapper (objectui#3398)
|
|
9
|
+
*
|
|
10
|
+
* The affordance, the dialog, the draft/commit state machine and the
|
|
11
|
+
* `readOnly` / `disabled` semantics all live in `FullscreenEditor`
|
|
12
|
+
* (`@object-ui/components`). They used to live here AND, separately, in the
|
|
13
|
+
* form renderer's built-in `textarea` branch — one form-level promise
|
|
14
|
+
* (`ObjectFormSchema.mobile.fullscreenLongText`, projected onto every long-text
|
|
15
|
+
* field as `mobile_fullscreen`) answered twice, on the two render paths.
|
|
16
|
+
*
|
|
17
|
+
* Two copies of a state machine drift, and these did: objectui#3400 measured a
|
|
18
|
+
* read-only field editable through the built-in branch's dialog, objectui#3402
|
|
19
|
+
* measured the same hole for `disabled` here, and #3393 (label in the dialog
|
|
20
|
+
* title) and #3272 (i18n) each landed on one side before the other. The
|
|
21
|
+
* maintainer's ruling of 2026-08-10 was one implementation hoisted to the
|
|
22
|
+
* dependency-legal side — and the measured import graph allows exactly that
|
|
23
|
+
* direction: this package depends on `@object-ui/components`, which declares no
|
|
24
|
+
* dependency on `@object-ui/fields` in `dependencies` or `peerDependencies`.
|
|
25
|
+
*
|
|
26
|
+
* ## Why the wrapper survives the merge rather than being deleted
|
|
27
|
+
*
|
|
28
|
+
* It carries the one thing that is genuinely about THIS package: `readOnly` is
|
|
29
|
+
* not part of the contract here. Both hosts early-return a read-only DISPLAY
|
|
30
|
+
* before they compute the affordance (`TextAreaField.tsx`, `RichTextField.tsx`),
|
|
31
|
+
* so a `readOnly` reaching this component would be a prop with no producer on
|
|
32
|
+
* this path — the shape this package keeps deleting (objectui#3232/#3233). The
|
|
33
|
+
* `Omit` below is that statement in the type system, and it is why the hoist did
|
|
34
|
+
* not simply re-export the primitive. A future host that renders an editor for
|
|
35
|
+
* read-only fields must widen this type AND pass the prop together; the
|
|
36
|
+
* primitive already answers it (no affordance at all).
|
|
37
|
+
*
|
|
38
|
+
* Everything else — `value` / `onCommit` / `label` / `testIdPrefix` /
|
|
39
|
+
* `disabled` / `children` / `footer` — passes straight through, so the hosts and
|
|
40
|
+
* their pins are unchanged. `testIdPrefix` still namespaces this package's ids
|
|
41
|
+
* (`textarea-`, `richtext-`), distinct from the built-in branch's
|
|
42
|
+
* `form-textarea-`, so a test can still say which render path it found.
|
|
107
43
|
*/
|
|
108
|
-
export
|
|
109
|
-
|
|
110
|
-
* The committed value. Seeds the draft each time the dialog opens — read at
|
|
111
|
-
* open time, not held, so a value that changes underneath while the dialog is
|
|
112
|
-
* closed is picked up on the next open.
|
|
113
|
-
*/
|
|
114
|
-
value: string;
|
|
115
|
-
/** Called once with the draft when the user confirms. */
|
|
116
|
-
onCommit: (next: string) => void;
|
|
117
|
-
/** Field label — the dialog title and part of the toggle's accessible name. */
|
|
118
|
-
label?: string;
|
|
119
|
-
/** Namespace for this widget's fullscreen test ids. See above. */
|
|
120
|
-
testIdPrefix: string;
|
|
121
|
-
/**
|
|
122
|
-
* The host field is not interactive (objectui#3402). Disables the toggle, the
|
|
123
|
-
* "Done" button and the write-back, and is handed to `children` so the host's
|
|
124
|
-
* own editor renders disabled too. See the `disabled` section above for why
|
|
125
|
-
* each of those is a separate line rather than belt-and-braces.
|
|
126
|
-
*
|
|
127
|
-
* **Producer**: the widget's `disabled` prop, which the form renderer computes
|
|
128
|
-
* as `disabled || fieldDisabled || isSubmitting || optionGroupGated` and
|
|
129
|
-
* forwards to registered widgets (`stripRegisteredFieldProps` does not strip
|
|
130
|
-
* it).
|
|
131
|
-
*/
|
|
132
|
-
disabled?: boolean;
|
|
133
|
-
/**
|
|
134
|
-
* The editor itself, rendered inside the dialog body against the draft. The
|
|
135
|
-
* host passes the SAME editor it renders inline, so "fullscreen" is a size
|
|
136
|
-
* change rather than a second, poorer editing surface.
|
|
137
|
-
*
|
|
138
|
-
* The third argument is this component's `disabled`, and the host is expected
|
|
139
|
-
* to put it on the control it renders: only the host knows which element its
|
|
140
|
-
* editor's disabled state belongs on. Ignoring it is not a write-back hole —
|
|
141
|
-
* `onCommit` is gated here regardless — but it does leave a control that looks
|
|
142
|
-
* editable while the field is not, so both in-repo hosts apply it and their
|
|
143
|
-
* tests pin it.
|
|
144
|
-
*/
|
|
145
|
-
children: (draft: string, setDraft: (next: string) => void, disabled: boolean) => React.ReactNode;
|
|
146
|
-
/** Optional footer status for the draft (e.g. a character counter). */
|
|
147
|
-
footer?: (draft: string) => React.ReactNode;
|
|
148
|
-
/**
|
|
149
|
-
* Extra classes for the toggle button. It is absolutely positioned by
|
|
150
|
-
* default and expects a `relative` ancestor; a host whose layout has a
|
|
151
|
-
* better home for it can re-place it here.
|
|
152
|
-
*/
|
|
153
|
-
toggleClassName?: string;
|
|
154
|
-
}
|
|
155
|
-
export declare function FullscreenFieldEditor({ value, onCommit, label, testIdPrefix, disabled, children, footer, toggleClassName, }: FullscreenFieldEditorProps): React.JSX.Element;
|
|
44
|
+
export type FullscreenFieldEditorProps = Omit<FullscreenEditorProps, 'readOnly'>;
|
|
45
|
+
export declare function FullscreenFieldEditor(props: FullscreenFieldEditorProps): React.JSX.Element;
|
|
@@ -11,7 +11,8 @@ import { FieldWidgetComponentProps } from './types';
|
|
|
11
11
|
*
|
|
12
12
|
* Column config (a subset of `GridColumnDefinition`):
|
|
13
13
|
* { field, label?, type?, options?, width?, required?, prefix?, step? }
|
|
14
|
-
* type ∈ 'text' | 'number' | 'currency' | 'date' | '
|
|
14
|
+
* type ∈ 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'time'
|
|
15
|
+
* | 'select' | 'lookup' | 'file'
|
|
15
16
|
*
|
|
16
17
|
* Field-level config (from `GridFieldMetadata`):
|
|
17
18
|
* columns, min_rows, max_rows, allow_add, allow_delete, total_field
|
|
@@ -19,7 +20,16 @@ import { FieldWidgetComponentProps } from './types';
|
|
|
19
20
|
export interface GridColumn {
|
|
20
21
|
field: string;
|
|
21
22
|
label?: string;
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Cell control + read/write adapter for the column.
|
|
25
|
+
*
|
|
26
|
+
* `date` / `datetime` / `time` are three DISTINCT controls, not one
|
|
27
|
+
* (objectui#3569). Collapsing `datetime` onto the `date` control did not
|
|
28
|
+
* merely under-render it — `<input type="date">` hands back a bare
|
|
29
|
+
* `YYYY-MM-DD` on change, so touching the day of a `datetime` cell silently
|
|
30
|
+
* DELETED its time component from the record.
|
|
31
|
+
*/
|
|
32
|
+
type?: 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'time' | 'select' | 'lookup' | 'file';
|
|
23
33
|
options?: Array<{
|
|
24
34
|
label: string;
|
|
25
35
|
value: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
+
import { HostGroupProps } from './toHostGroupProps';
|
|
2
3
|
/**
|
|
3
4
|
* The "this option list cannot be filled" state shared by every fixed-option
|
|
4
5
|
* widget (`SelectField` single, `MultiSelectField`, `RadioField`,
|
|
@@ -41,5 +42,18 @@ export interface OptionsEmptyStateProps {
|
|
|
41
42
|
testId?: string;
|
|
42
43
|
/** Widget-specific sizing (the dropdown is a fixed `h-9`, lists grow). */
|
|
43
44
|
className?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The host label's IDREF plumbing, when this box is the whole rendered surface
|
|
47
|
+
* of a group-labelled field (objectui#3990) — a `checkboxes` / `radio` /
|
|
48
|
+
* `multiselect` whose option list came back empty renders NOTHING but this
|
|
49
|
+
* box, so the visible label named zero elements until it landed here.
|
|
50
|
+
*
|
|
51
|
+
* A closed three-key type rather than an open props tail: only `id`,
|
|
52
|
+
* `aria-labelledby` and the `role` that makes them meaningful may reach this
|
|
53
|
+
* element, and reopening a spread is what objectui#3291 exists to prevent.
|
|
54
|
+
* Absent for the single `SelectField` — it is not group-labelled, its label
|
|
55
|
+
* still uses a plain `for`, and it must keep emitting no such attributes.
|
|
56
|
+
*/
|
|
57
|
+
hostGroupProps?: HostGroupProps;
|
|
44
58
|
}
|
|
45
|
-
export declare function OptionsEmptyState({ emptyHint, gated, dependsOnFields, testId, className, }: OptionsEmptyStateProps): React.JSX.Element;
|
|
59
|
+
export declare function OptionsEmptyState({ emptyHint, gated, dependsOnFields, testId, className, hostGroupProps, }: OptionsEmptyStateProps): React.JSX.Element;
|
|
@@ -126,13 +126,39 @@ export interface RecordPickerDialogProps {
|
|
|
126
126
|
*/
|
|
127
127
|
lookupFilters?: LookupFilterDef[];
|
|
128
128
|
/**
|
|
129
|
-
* Hard filter
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
129
|
+
* Hard filter constraint applied to every query. Unlike `lookupFilters`,
|
|
130
|
+
* entries here never surface in the filter bar and cannot be overridden by
|
|
131
|
+
* user filter input.
|
|
132
|
+
*
|
|
133
|
+
* Two shapes, discriminated STRUCTURALLY (`Array.isArray`), because the two
|
|
134
|
+
* callers speak two legitimate vocabularies and neither should be bent into
|
|
135
|
+
* the other:
|
|
136
|
+
*
|
|
137
|
+
* - **`QueryParams.$filter` record form** (`{ account: 'a1' }`) — the
|
|
138
|
+
* dependent (cascading) lookup chain, where the parent field's value MUST
|
|
139
|
+
* scope the candidate set (#2215). Merged by KEY OVERWRITE, so a cascaded
|
|
140
|
+
* value REPLACES a stale `lookupFilters` entry on the same field instead of
|
|
141
|
+
* intersecting with it. That precedence is load-bearing: an `and` of both
|
|
142
|
+
* would ask for `account = 'stale' AND account = 'a1'` and return nothing.
|
|
143
|
+
* - **A spec `ViewFilterRule[]`** (`[{ field, operator, value? }]`) — an
|
|
144
|
+
* author's `record:related_list.add.picker.filter`, handed over VERBATIM
|
|
145
|
+
* (#3831). Lowered by `mergeFilterNodes`, the repo's single filter sink, so
|
|
146
|
+
* all 19 `VIEW_FILTER_OPERATORS` reach the wire — including the four
|
|
147
|
+
* (`before`, `after`, `is_empty`, `is_not_empty`) the record form has no
|
|
148
|
+
* `$op` for. No second operator vocabulary is introduced here: two already
|
|
149
|
+
* exist (the spec's `AST_OPERATOR_MAP`, data-objectstack's
|
|
150
|
+
* `FILTER_OPERATOR_ALIASES`) and #3948 is what a third costs.
|
|
151
|
+
*
|
|
152
|
+
* The discriminator is exact rather than heuristic — every AST node is an
|
|
153
|
+
* ARRAY and a rule is a plain OBJECT, the same predicate `toFilterNode` uses.
|
|
154
|
+
*
|
|
155
|
+
* Typed `unknown` rather than `Record< string, any >` on purpose: that type
|
|
156
|
+
* ACCEPTED a rule array (TypeScript lets an array satisfy a string index of
|
|
157
|
+
* `any`), the old object-spread merge then flattened it to
|
|
158
|
+
* `{"0": {...}, "1": {...}}`, and the query filtered on columns literally
|
|
159
|
+
* named `0`/`1` — type-check green, wrong query, no diagnostic anywhere.
|
|
134
160
|
*/
|
|
135
|
-
baseFilter?:
|
|
161
|
+
baseFilter?: unknown;
|
|
136
162
|
/**
|
|
137
163
|
* Cell renderer resolver function.
|
|
138
164
|
* When provided, columns with a `type` property will be rendered using the
|
|
@@ -5,11 +5,31 @@ import { FieldWidgetComponentProps } from './types';
|
|
|
5
5
|
*
|
|
6
6
|
* A field declared `multiple: true` selects zero-or-more values (spec:
|
|
7
7
|
* `multiple` is valid on `select`), so it renders the multi-value chip picker
|
|
8
|
-
* — the same widget the `multiselect` type uses.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* — the same widget the `multiselect` type uses. Single-value selects keep the
|
|
9
|
+
* cascading dropdown below.
|
|
10
|
+
*
|
|
11
|
+
* The FORM no longer arrives here with `multiple` (objectui#3986):
|
|
12
|
+
* `mapFieldTypeToFormType` now resolves a `select` + `multiple: true` field to
|
|
13
|
+
* `field:multiselect`, so the object-form path renders `MultiSelectField`
|
|
14
|
+
* directly under its own registry id — which is what carries the
|
|
15
|
+
* `labelling: 'group'` declaration the host label needs. Deciding the widget at
|
|
16
|
+
* the type-resolution layer is what keeps the declaration and the render from
|
|
17
|
+
* disagreeing; a delegation invisible to the resolver could not.
|
|
18
|
+
*
|
|
19
|
+
* The branch below stays because it is NOT dead — measured entrances that reach
|
|
20
|
+
* it with `multiple` set, none of which consult that resolver:
|
|
21
|
+
*
|
|
22
|
+
* - **the inline grid editor** — `FieldEditWidget` looks `select` up in its own
|
|
23
|
+
* `EDIT_WIDGETS` table, which SHORT-CIRCUITS before the alias map is
|
|
24
|
+
* consulted, and forwards the whole metadata object as `field`;
|
|
25
|
+
* - **`ActionParamDialog`** — `paramToField` resolves through
|
|
26
|
+
* `resolveFormWidgetType`, which likewise returns `select` from
|
|
27
|
+
* `fieldWidgetMap` before reaching the alias map, and carries
|
|
28
|
+
* `multiple: param.multiple` on the field it builds;
|
|
29
|
+
* - **hand-written SDUI** — a `{ type: 'field:select' }` node whose metadata
|
|
30
|
+
* declares `multiple`, which addresses this widget by name.
|
|
31
|
+
*
|
|
32
|
+
* All three then inherit multi-select from here identically, with no drift.
|
|
13
33
|
*
|
|
14
34
|
* Both branches resolve per-option `visibleWhen` cascading / role-gating through
|
|
15
35
|
* the shared {@link useCascadingOptions} hook (#2715), so single and multi stay
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AddressValue } from '@objectstack/spec/data';
|
|
2
|
+
/**
|
|
3
|
+
* Address data structure — `z.input` of `@objectstack/spec`'s
|
|
4
|
+
* `AddressValueSchema`, which is what the platform stores and what
|
|
5
|
+
* `/api/v1/data/**` serves back.
|
|
6
|
+
*
|
|
7
|
+
* The postal code is spelled `postalCode` (objectstack#5143). `AddressField`
|
|
8
|
+
* used to read and write `zipCode`, a key that appears nowhere in the spec, so
|
|
9
|
+
* the stored postal code never reached the input — and whatever the user then
|
|
10
|
+
* typed into that apparently-empty box was written under a key
|
|
11
|
+
* `AddressValueSchema` strips: lost outright on a new record, and on an
|
|
12
|
+
* existing one silently discarded while the stale stored value survived. (A
|
|
13
|
+
* postal code nobody touched did survive an unrelated edit, because the write
|
|
14
|
+
* spread the whole stored object through; the issue's account of that half is
|
|
15
|
+
* corrected in `AddressField.postalCode.test.tsx`.) One name, on both sides:
|
|
16
|
+
* the contract's.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTED rather than declared since objectui#4167. rc.6 publishes
|
|
19
|
+
* `AddressValue` under this exact name, and the local copy — whose own comment
|
|
20
|
+
* already claimed to be "the part names of `AddressSchema`" — declared five of
|
|
21
|
+
* the seven parts. `countryCode` and `formatted` were missing, which is
|
|
22
|
+
* objectstack#4115's failure class precisely: a canonical CLAIM over a narrower
|
|
23
|
+
* shape, believed by the next reader. The claim is now the code.
|
|
24
|
+
*
|
|
25
|
+
* `AddressField` still renders five inputs and {@link formatAddress} still
|
|
26
|
+
* composes five parts, and that is a deliberate split rather than an omission:
|
|
27
|
+
* `formatted` is a derived one-line rendering of the parts (what this module
|
|
28
|
+
* computes) and `countryCode` is the ISO pair for `country`, neither of which a
|
|
29
|
+
* human types into a part box. Binding the type does not publish them as
|
|
30
|
+
* inputs — it stops the type from asserting the platform cannot store them, and
|
|
31
|
+
* it means a write from the widget no longer looks type-clean while dropping a
|
|
32
|
+
* key the contract carries: the `{ ...address }` spread in `handleFieldChange`
|
|
33
|
+
* preserves both, and now says so.
|
|
34
|
+
*
|
|
35
|
+
* The declaration moved here (objectui#4037) so the display cell renderer and
|
|
36
|
+
* the widget share one definition; the SOURCE of that definition is the spec.
|
|
37
|
+
*/
|
|
38
|
+
export type { AddressValue };
|
|
39
|
+
/**
|
|
40
|
+
* The shape written by builds up to and including 17.0.0-rc.1, whose postal
|
|
41
|
+
* code landed under `zipCode` (objectstack#5143). Read-time compatibility ONLY,
|
|
42
|
+
* and deliberately not part of {@link AddressValue}: authoring code must not be
|
|
43
|
+
* able to spell `zipCode`, and nothing here ever writes it back — the first
|
|
44
|
+
* edit of any part normalizes the record onto `postalCode` (see
|
|
45
|
+
* `AddressField`'s `handleFieldChange`). This is not a producer alias to be
|
|
46
|
+
* honoured forever; it exists to carry data THIS widget mis-wrote, and can go
|
|
47
|
+
* once no such data remains.
|
|
48
|
+
*/
|
|
49
|
+
export type LegacyAddressValue = AddressValue & {
|
|
50
|
+
zipCode?: string;
|
|
51
|
+
};
|
|
52
|
+
/** Postal code of a stored address, preferring the canonical key. */
|
|
53
|
+
export declare function readPostalCode(addr: AddressValue): string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Formats a stored address as a single line: `Street, City, State ZIP, Country`.
|
|
56
|
+
*
|
|
57
|
+
* Ordering and separators are NOT invented here — they are the rule
|
|
58
|
+
* `AddressField` already applied in its readonly branch, and the sub-field set
|
|
59
|
+
* its inputs expose (street / city / state / postalCode / country). Missing
|
|
60
|
+
* parts are dropped rather than spaced over, so a street-only address is
|
|
61
|
+
* `"中策路 1 号"` and never `", , ,"`.
|
|
62
|
+
*
|
|
63
|
+
* Returns `''` when no part is usable — callers decide what an empty address
|
|
64
|
+
* looks like (an `EmptyValue` placeholder for the cell renderer), and a value
|
|
65
|
+
* carrying no recognized part at all is NOT silently blanked: see
|
|
66
|
+
* `AddressCellRenderer`, which falls back to JSON so unknown data stays visible.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatAddress(addr: AddressValue): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client-side `maxSize` guard shared by the file and image widgets
|
|
3
|
+
* (objectui#4141).
|
|
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`.
|
|
24
|
+
*/
|
|
25
|
+
export type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
|
|
26
|
+
/**
|
|
27
|
+
* The rejection message for a pick that exceeds `maxSize`, or `null` when the
|
|
28
|
+
* pick is within the limit — so a caller reads it as `if (err) reject`.
|
|
29
|
+
*
|
|
30
|
+
* A falsy `maxSize` (undeclared, or an explicit `0`) means **unrestricted**,
|
|
31
|
+
* preserving the `if (maxSize && …)` semantics the file widget has always
|
|
32
|
+
* applied: an undeclared limit must never turn into a 0-byte one.
|
|
33
|
+
*
|
|
34
|
+
* Takes `name` and `size` separately instead of a `File` because the image
|
|
35
|
+
* widget's crop path validates a re-encoded `Blob`, which carries no name of its
|
|
36
|
+
* own — that name comes from the cropper's output.
|
|
37
|
+
*/
|
|
38
|
+
export declare function maxSizeError(t: TranslateFn, pick: {
|
|
39
|
+
name: string;
|
|
40
|
+
size: number;
|
|
41
|
+
}, maxSize: number | undefined): string | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
* Stored value → `<input type="date">` value (`YYYY-MM-DD`).
|
|
10
|
+
*
|
|
11
|
+
* A leading `YYYY-MM-DD` is passed through VERBATIM rather than re-parsed: a
|
|
12
|
+
* date-only string is parsed as UTC midnight by `new Date()`, so reading local
|
|
13
|
+
* calendar components back out of it shifts the day by one everywhere west of
|
|
14
|
+
* Greenwich. `2026-06-17` must stay June 17th in every zone.
|
|
15
|
+
*/
|
|
16
|
+
export declare function toDateInputValue(value: unknown): string;
|
|
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;
|
|
@@ -1,10 +1,4 @@
|
|
|
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
|
-
*/
|
|
1
|
+
import { DomProps as CoreDomProps } from '@object-ui/core';
|
|
8
2
|
/**
|
|
9
3
|
* The RUNTIME EXECUTOR of the "DOM pass-through" section of
|
|
10
4
|
* {@link FieldWidgetComponentProps} (objectui#3291).
|
|
@@ -59,13 +53,39 @@
|
|
|
59
53
|
* open spread. If a field node should be able to author one, DECLARE it on
|
|
60
54
|
* `FieldWidgetComponentProps` first and add it here — do not reopen the spread
|
|
61
55
|
* (AGENTS.md #0.1: fix the contract, never widen the consumer).
|
|
56
|
+
*
|
|
57
|
+
* ## Where the mechanism lives now, and why the key list stayed here
|
|
58
|
+
*
|
|
59
|
+
* objectui#4425 phase 2 promoted this whitelist to the SDUI widget contract
|
|
60
|
+
* generally, so the MECHANISM — filter by a declared key list plus the `aria-*`
|
|
61
|
+
* / `data-*` open families — moved to `@object-ui/core`
|
|
62
|
+
* (`utils/dom-props.ts`, `pickDomProps`), where every plugin package can reach
|
|
63
|
+
* it without depending on `@object-ui/fields` (the objectui#4409
|
|
64
|
+
* dependency-direction method: `plugin-chatbot` declares `@object-ui/core` and
|
|
65
|
+
* does not declare this package).
|
|
66
|
+
*
|
|
67
|
+
* The KEY LIST did not move, because it is not the same list. Measured on
|
|
68
|
+
* objectui#4431:
|
|
69
|
+
*
|
|
70
|
+
* - `name` and `disabled` are here and NOT in the SDUI set: both are legal
|
|
71
|
+
* only on form controls, which is what every widget in this package renders
|
|
72
|
+
* and what `FieldWidgetComponentProps` declares. `name` on the `<div>` an
|
|
73
|
+
* SDUI container widget renders is a leak — one of the 14 attributes #4431
|
|
74
|
+
* closed — and two widgets here already hand-strip it when they spread onto
|
|
75
|
+
* something that is not a control (`ObjectRefField`'s trigger,
|
|
76
|
+
* `FileField`'s dropzone).
|
|
77
|
+
* - `role` is in the SDUI set and NOT here: `SchemaRenderer` resolves it for
|
|
78
|
+
* every node from `@objectstack/spec`'s `AriaPropsSchema`, while this
|
|
79
|
+
* contract deliberately does not declare it (see "Deliberately NOT
|
|
80
|
+
* forwarded" above).
|
|
81
|
+
*
|
|
82
|
+
* So: one mechanism, two declarations, each bound by the assertions below to
|
|
83
|
+
* the contract it executes. Not two judges.
|
|
62
84
|
*/
|
|
63
85
|
declare const DOM_PASS_THROUGH_KEYS: readonly ["id", "name", "autoFocus", "tabIndex", "onBlur", "onFocus", "onClick", "className", "disabled"];
|
|
64
86
|
type DomPassThroughKey = (typeof DOM_PASS_THROUGH_KEYS)[number];
|
|
65
87
|
/** The subset of `P` this helper forwards, with each key's declared type. */
|
|
66
|
-
export type DomProps<P> =
|
|
67
|
-
[K in Extract<keyof P, `data-${string}` | `aria-${string}`>]: P[K];
|
|
68
|
-
};
|
|
88
|
+
export type DomProps<P> = CoreDomProps<P, DomPassThroughKey>;
|
|
69
89
|
/**
|
|
70
90
|
* Keep only what may legitimately become a DOM attribute, and drop everything
|
|
71
91
|
* else — renderer plumbing, and any extra key an author put on the field
|