@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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { toDomProps } from './toDomProps';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The props that make a group-labelled widget's rendered surface the thing its
|
|
13
|
+
* host label actually NAMES, on the surfaces that are not controls
|
|
14
|
+
* (objectui#3990).
|
|
15
|
+
*
|
|
16
|
+
* ## What it is for
|
|
17
|
+
*
|
|
18
|
+
* A widget declared `labelling: 'group'` (objectui#3961) is told by the form
|
|
19
|
+
* renderer: your label publishes an `id` and drops its `for`, so name yourself
|
|
20
|
+
* by IDREF. The renderer hands down exactly two facts that address the field as
|
|
21
|
+
* a WHOLE:
|
|
22
|
+
*
|
|
23
|
+
* - `id` — the host's control id (`…-form-item`), the id the label's `for`
|
|
24
|
+
* used to point at;
|
|
25
|
+
* - `aria-labelledby` — the IDREF of that visible label.
|
|
26
|
+
*
|
|
27
|
+
* Every editable branch already consumes them inside its wider `toDomProps`
|
|
28
|
+
* spread. The branches that returned EARLY did not — a field-level
|
|
29
|
+
* `readonly: true`, or an option list with zero offered options — so the label
|
|
30
|
+
* published an id that no element in the document referenced. Measured on
|
|
31
|
+
* `origin/main`, one field per row, reading the host label against the DOM:
|
|
32
|
+
*
|
|
33
|
+
* ```
|
|
34
|
+
* multiselect readonly+value consumers=0 byLabelText=0 namedByRole=[]
|
|
35
|
+
* multiselect readonly+empty consumers=0 byLabelText=0 namedByRole=[]
|
|
36
|
+
* multiselect zeroOptions consumers=0 byLabelText=0 namedByRole=[]
|
|
37
|
+
* multiselect editable consumers=1 byLabelText=1 namedByRole=[group:1]
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* All seven group-labelled types measured the same, in every readonly state.
|
|
41
|
+
* This is quieter than the dangling `for` #3961 replaced: a `for` that resolves
|
|
42
|
+
* to nothing can at least be swept for, while "a label published an id and
|
|
43
|
+
* nobody consumed it" looks like healthy markup.
|
|
44
|
+
*
|
|
45
|
+
* ## Why this narrow pair, and not the whole `toDomProps` result
|
|
46
|
+
*
|
|
47
|
+
* Everything else in the DOM pass-through addresses a CONTROL, and a readonly
|
|
48
|
+
* display has none: `aria-describedby` / `aria-required` are announced when
|
|
49
|
+
* focus lands on something focusable, `disabled` / `tabIndex` / the focus
|
|
50
|
+
* handlers only mean something on an interactive element, and `name` is
|
|
51
|
+
* DOM-legal on form controls only — on a `div` it is exactly the leak
|
|
52
|
+
* objectui#3291 sweeps for. Spreading the full result would also put the host's
|
|
53
|
+
* `className` on top of the widget's own, because the widgets whose readonly
|
|
54
|
+
* surface is a placeholder or a plain container (`EmptyValue`, `FileField`,
|
|
55
|
+
* `AddressField`) keep `className` inside `props`.
|
|
56
|
+
*
|
|
57
|
+
* ## Why `role` travels with the two keys
|
|
58
|
+
*
|
|
59
|
+
* `aria-labelledby` on a role-less `div` / `span` names NOTHING — `generic`
|
|
60
|
+
* prohibits an author name — so the pair is inert without a role that supports
|
|
61
|
+
* naming. `group` is the one that fits a readonly surface: it names a set of
|
|
62
|
+
* values (chips, checked labels, stars, file names, a formatted address)
|
|
63
|
+
* without promising the interactivity `textbox` or `radiogroup` would. It is
|
|
64
|
+
* deliberately NOT the role the same widget answers with while editable:
|
|
65
|
+
* `RadioField`'s readonly branch renders the chosen label as text with no radios
|
|
66
|
+
* left in it, and `FileField`'s renders file names with no dropzone button.
|
|
67
|
+
*
|
|
68
|
+
* The role is emitted ONLY when a host actually named the field, mirroring
|
|
69
|
+
* every editable branch's `isLabelledGroup` test: standalone rendering (the
|
|
70
|
+
* grid's inline cell editor, a bare SDUI node) hands down neither key, so every
|
|
71
|
+
* value here is `undefined`, React emits no attribute, and that markup stays
|
|
72
|
+
* byte-identical to what it was.
|
|
73
|
+
*/
|
|
74
|
+
export interface HostGroupProps {
|
|
75
|
+
/** The host's control id — the id its label would have pointed `for` at. */
|
|
76
|
+
id?: string;
|
|
77
|
+
/** IDREF of the host's visible label. */
|
|
78
|
+
'aria-labelledby'?: string;
|
|
79
|
+
/** `'group'` when — and only when — a host named this surface. */
|
|
80
|
+
role?: 'group';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read the two whole-field keys out of a widget's leftover props, plus the role
|
|
85
|
+
* that makes them mean something. See {@link HostGroupProps}.
|
|
86
|
+
*
|
|
87
|
+
* Routed through {@link toDomProps} rather than reading `props` directly so the
|
|
88
|
+
* pair keeps ONE gate: those keys reach an element only if the DOM pass-through
|
|
89
|
+
* whitelist still forwards them, and its two compile-time assertions bind that
|
|
90
|
+
* whitelist to the props contract in both directions.
|
|
91
|
+
*/
|
|
92
|
+
export function toHostGroupProps(props: {
|
|
93
|
+
id?: string;
|
|
94
|
+
'aria-labelledby'?: string;
|
|
95
|
+
}): HostGroupProps {
|
|
96
|
+
const { id, 'aria-labelledby': labelledBy } = toDomProps(props);
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
'aria-labelledby': labelledBy,
|
|
100
|
+
...(labelledBy != null ? { role: 'group' as const } : null),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -6,14 +6,13 @@ import { createSafeTranslation } from '@object-ui/i18n';
|
|
|
6
6
|
|
|
7
7
|
const FIELD_DEFAULTS: Record<string, string> = {
|
|
8
8
|
'common.selectOption': 'Select an option',
|
|
9
|
-
'common.select': 'Select
|
|
10
|
-
'common.
|
|
11
|
-
'common.loading': 'Loading...',
|
|
9
|
+
'common.select': 'Select…',
|
|
10
|
+
'common.loading': 'Loading…',
|
|
12
11
|
'common.noResults': 'No results found',
|
|
13
12
|
'common.cancel': 'Cancel',
|
|
14
13
|
'common.confirm': 'Confirm',
|
|
15
14
|
'table.selected': '{{count}} selected',
|
|
16
|
-
'table.search': 'Search
|
|
15
|
+
'table.search': 'Search…',
|
|
17
16
|
'lookup.loading': 'Loading…',
|
|
18
17
|
'lookup.noOptions': 'No options found',
|
|
19
18
|
'lookup.noRecords': 'No records found',
|
|
@@ -99,36 +98,19 @@ const FIELD_DEFAULTS: Record<string, string> = {
|
|
|
99
98
|
// depend on the number stays correct at 1 without ten plural entries per
|
|
100
99
|
// pack, and matches the sibling key's shape.
|
|
101
100
|
'fields.textarea.charactersRemaining': 'Characters remaining: {{count}}',
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
101
|
+
// The fullscreen long-text dialog's copy (objectui#3404) is NOT declared
|
|
102
|
+
// here any more. It moved with the implementation: `FullscreenFieldEditor` is
|
|
103
|
+
// a thin wrapper over `FullscreenEditor` in `@object-ui/components`
|
|
104
|
+
// (objectui#3398), and that primitive carries its own `createSafeTranslation`
|
|
105
|
+
// defaults for the same `form.fullscreen.*` / `common.cancel` keys — still
|
|
106
|
+
// the built-in branch's keys, still byte-identical English, so provider-less
|
|
107
|
+
// rendering is unchanged and the ten locale packs need no edit. Leaving the
|
|
108
|
+
// entries here would have re-created in the defaults map exactly the
|
|
109
|
+
// duplication #3398 removed from the components, and this map's own
|
|
110
|
+
// discipline is that a default is declared where it is read.
|
|
105
111
|
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// `components/src/renderers/form/form.tsx`, objectui#3272) rather than
|
|
109
|
-
// `fields.fullscreen.*` twins. One form-level setting
|
|
110
|
-
// (`ObjectFormSchema.mobile.fullscreenLongText`) projects onto BOTH render
|
|
111
|
-
// paths inside a single form, so a second copy of this copy is guaranteed
|
|
112
|
-
// drift — the shape #3231/#3263 had to undo — and would mean editing ten
|
|
113
|
-
// locale packs to say what they already say. `common.cancel` is already
|
|
114
|
-
// declared above and is reused here for exactly that reason.
|
|
115
|
-
//
|
|
116
|
-
// The values are byte-identical to the English literals they replaced, so
|
|
117
|
-
// a widget rendered with no I18nProvider (standalone/embedded hosts, and
|
|
118
|
-
// every bare-render test in this package) shows what it always did instead
|
|
119
|
-
// of a raw key. Measured before the change: with no provider mounted, bare
|
|
120
|
-
// `useObjectTranslation().t` returns the key itself
|
|
121
|
-
// (`common.cancel` → "common.cancel"), which is why this goes through
|
|
122
|
-
// `createSafeTranslation` like the built-in branch does.
|
|
123
|
-
'form.fullscreen.title': 'Edit text',
|
|
124
|
-
'form.fullscreen.description': 'Edit the full text value, then save or cancel your changes.',
|
|
125
|
-
'form.fullscreen.done': 'Done',
|
|
126
|
-
// Kept as ONE interpolated sentence with a translated generic noun standing
|
|
127
|
-
// in for a missing label — the literal's own shape
|
|
128
|
-
// (`Edit ${label ?? 'text'} fullscreen`). A second, label-less sentence key
|
|
129
|
-
// would need ten new pack entries to say the same thing.
|
|
130
|
-
'form.fullscreen.toggle': 'Edit {{label}} fullscreen',
|
|
131
|
-
'form.fullscreen.textFallback': 'text',
|
|
112
|
+
// `common.cancel` stays above: `RecordPickerDialog` and `PeoplePicker` read
|
|
113
|
+
// it independently of anything fullscreen.
|
|
132
114
|
// objectui#2600 B5 — capability picker scope group headers.
|
|
133
115
|
'capability.group.platform': 'Platform',
|
|
134
116
|
'capability.group.org': 'Organization',
|
|
@@ -56,8 +56,14 @@ export interface UseRecordQueryOptions {
|
|
|
56
56
|
* `$filter` — already merged by the caller (base `lookup_filters`, dependent
|
|
57
57
|
* lookup chain, candidate hygiene like `banned != true`, …). Compared by
|
|
58
58
|
* value, so a referentially-new-but-equal object each render will not loop.
|
|
59
|
+
*
|
|
60
|
+
* Either the `QueryParams.$filter` record form or a lowered ObjectQL AST node
|
|
61
|
+
* (an array), since the picker's merge now produces both (#3831). Typed
|
|
62
|
+
* `unknown` rather than `Record< string, any >` so an array cannot slip
|
|
63
|
+
* through a type that silently accepts it; emptiness is decided by
|
|
64
|
+
* {@link hasFilter}, not by `Object.keys`.
|
|
59
65
|
*/
|
|
60
|
-
filter?:
|
|
66
|
+
filter?: unknown;
|
|
61
67
|
/** `$expand` — related entities to include (e.g. `['primary_business_unit_id']`). */
|
|
62
68
|
expand?: string[];
|
|
63
69
|
/** `$searchFields` — narrow the server searchable set (ADR-0061). */
|
|
@@ -97,6 +103,22 @@ export interface UseRecordQueryResult {
|
|
|
97
103
|
refetch: () => void;
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Is this filter source worth sending as `$filter` at all?
|
|
108
|
+
*
|
|
109
|
+
* Array-aware on purpose. The picker's merge yields either the
|
|
110
|
+
* `QueryParams.$filter` record form or a lowered ObjectQL AST node (#3831), and
|
|
111
|
+
* `Object.keys` on an array returns its INDICES — so the record-only test read
|
|
112
|
+
* `['0','1','2']` for an AST node and was right only by accident. An empty array
|
|
113
|
+
* is "no filter" for the same reason an empty object is.
|
|
114
|
+
*/
|
|
115
|
+
function hasFilter(filter: unknown): boolean {
|
|
116
|
+
if (filter === null || filter === undefined) return false;
|
|
117
|
+
if (Array.isArray(filter)) return filter.length > 0;
|
|
118
|
+
if (typeof filter !== 'object') return false;
|
|
119
|
+
return Object.keys(filter as object).length > 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
export function useRecordQuery(options: UseRecordQueryOptions): UseRecordQueryResult {
|
|
101
123
|
const {
|
|
102
124
|
dataSource,
|
|
@@ -123,7 +145,7 @@ export function useRecordQuery(options: UseRecordQueryOptions): UseRecordQueryRe
|
|
|
123
145
|
// Stable signatures for object/array inputs so the fetch effect keys on their
|
|
124
146
|
// *value*, not a fresh reference every render.
|
|
125
147
|
const filterSignature = useMemo(
|
|
126
|
-
() => (
|
|
148
|
+
() => (hasFilter(filter) ? JSON.stringify(filter) : ''),
|
|
127
149
|
[filter],
|
|
128
150
|
);
|
|
129
151
|
const expandSignature = useMemo(() => (expand?.length ? expand.join(',') : ''), [expand]);
|
|
@@ -150,7 +172,12 @@ export function useRecordQuery(options: UseRecordQueryOptions): UseRecordQueryRe
|
|
|
150
172
|
if (searchTerm && searchTerm.trim()) params.$search = searchTerm.trim();
|
|
151
173
|
if (searchFields && searchFields.length > 0) params.$searchFields = searchFields;
|
|
152
174
|
if (sortArg) params.$orderby = { [sortArg.field]: sortArg.direction };
|
|
153
|
-
|
|
175
|
+
// `QueryParams.$filter` is declared `Record< string, any >`, which the
|
|
176
|
+
// AST-array form does not describe — the cast is at this ONE assignment
|
|
177
|
+
// rather than widening a shared type that several other producers
|
|
178
|
+
// (plugin-list's `buildEffectiveFilter`, plugin-view's ObjectView)
|
|
179
|
+
// already feed arrays through.
|
|
180
|
+
if (hasFilter(filter)) params.$filter = filter as Record<string, any>;
|
|
154
181
|
if (expand && expand.length > 0) params.$expand = expand;
|
|
155
182
|
|
|
156
183
|
const result = await dataSource.find(objectName, params);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|