fluentui-extended 2026.8.30 → 2026.8.35
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/README.md +41 -1
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +105 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -293,6 +293,45 @@ Run the test harness with `npm run harness` to see all examples in action.
|
|
|
293
293
|
| `disabled` | `boolean` | `false` | Disable the lookup |
|
|
294
294
|
| `open` | `boolean` | - | Controlled open state for the dropdown |
|
|
295
295
|
| `onOpenChange` | `(open: boolean) => void` | - | Callback when dropdown open state changes |
|
|
296
|
+
| `disableClientFilter` | `boolean` | `false` | Disable client-side filtering of options. Use this when filtering is performed server-side via `onSearchChange` |
|
|
297
|
+
| `searchFields` | `string` | - | Hidden searchable text (never rendered). Use this to include additional searchable content (codes, IDs) while displaying JSX in `secondaryText` |
|
|
298
|
+
|
|
299
|
+
### Client-Side Filtering
|
|
300
|
+
|
|
301
|
+
By default, the Lookup component filters options client-side as the user types. The filtering logic works as follows:
|
|
302
|
+
|
|
303
|
+
1. **Primary field (`text`)** — Always searched, regardless of other props
|
|
304
|
+
2. **Search fields (`searchFields`)** — If provided, this hidden text is searched (useful when `secondaryText` is JSX)
|
|
305
|
+
3. **Secondary text (`secondaryText`)** — Only searched if it's a string (JSX elements are skipped)
|
|
306
|
+
|
|
307
|
+
This allows you to use rich JSX (badges, icons) in `secondaryText` while still providing searchable text via `searchFields`:
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
const options: LookupOption[] = [
|
|
311
|
+
{
|
|
312
|
+
key: 'PROD-001',
|
|
313
|
+
text: 'Acme Widget', // Always searchable
|
|
314
|
+
searchFields: 'PROD-001 SKU-12345 acme-widget', // Hidden searchable text
|
|
315
|
+
secondaryText: ( // Rich display (not searchable)
|
|
316
|
+
<span style={{ display: 'flex', gap: 4 }}>
|
|
317
|
+
<Badge size="small">PROD-001</Badge>
|
|
318
|
+
<Badge size="small" color="brand">SKU-12345</Badge>
|
|
319
|
+
</span>
|
|
320
|
+
),
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Server-Side Filtering:** When using `onSearchChange` to fetch results from an API, set `disableClientFilter={true}` to prevent the client from re-filtering server results:
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
<Lookup
|
|
329
|
+
options={apiResults}
|
|
330
|
+
onSearchChange={(searchText) => fetchFromApi(searchText)}
|
|
331
|
+
disableClientFilter={true} // API already filtered the results
|
|
332
|
+
loading={isLoading}
|
|
333
|
+
/>
|
|
334
|
+
```
|
|
296
335
|
|
|
297
336
|
### Cross-Document Support (Dynamics 365 Iframes)
|
|
298
337
|
|
|
@@ -327,6 +366,7 @@ interface LookupOption {
|
|
|
327
366
|
key: string; // Unique identifier (required)
|
|
328
367
|
text: string; // Display text (required)
|
|
329
368
|
secondaryText?: ReactNode; // Secondary line - string, Badge, or JSX
|
|
369
|
+
searchFields?: string; // Hidden searchable text (never rendered)
|
|
330
370
|
icon?: ReactNode; // Icon component (e.g., <BuildingRegular />)
|
|
331
371
|
details?: LookupOptionDetail[]; // Expandable details (chevron appears)
|
|
332
372
|
data?: unknown; // Custom data payload for your app
|
|
@@ -339,7 +379,7 @@ interface LookupOptionDetail {
|
|
|
339
379
|
}
|
|
340
380
|
```
|
|
341
381
|
|
|
342
|
-
> **Note:** Both `secondaryText` and `details` support React elements, not just strings. See [Rich Secondary Text](#rich-secondary-text-with-react-elements) for examples.
|
|
382
|
+
> **Note:** Both `secondaryText` and `details` support React elements, not just strings. See [Rich Secondary Text](#rich-secondary-text-with-react-elements) for examples. When using JSX in `secondaryText`, use `searchFields` to provide hidden searchable text (see [Client-Side Filtering](#client-side-filtering)).
|
|
343
383
|
|
|
344
384
|
## Keyboard Navigation
|
|
345
385
|
|
package/dist/index.d.mts
CHANGED
|
@@ -14,6 +14,13 @@ interface LookupOption {
|
|
|
14
14
|
text: string;
|
|
15
15
|
/** Optional secondary text - can be a string or React element */
|
|
16
16
|
secondaryText?: React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Optional searchable text that is never rendered. Use this to include
|
|
19
|
+
* additional searchable content (codes, IDs, etc.) without affecting display.
|
|
20
|
+
* Client-side filtering will search this field in addition to `text` and
|
|
21
|
+
* string `secondaryText`.
|
|
22
|
+
*/
|
|
23
|
+
searchFields?: string;
|
|
17
24
|
/** Optional icon to display */
|
|
18
25
|
icon?: React.ReactNode;
|
|
19
26
|
/** Optional expandable details */
|
|
@@ -64,6 +71,12 @@ interface LookupProps extends Omit<InputProps, 'onChange' | 'value'> {
|
|
|
64
71
|
* Use together with `open` for controlled mode, or standalone to observe changes.
|
|
65
72
|
*/
|
|
66
73
|
onOpenChange?: (open: boolean) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Disable client-side filtering of options. Use this when filtering is
|
|
76
|
+
* performed server-side via `onSearchChange` and the returned options are
|
|
77
|
+
* already filtered. Defaults to `false` (client-side filtering enabled).
|
|
78
|
+
*/
|
|
79
|
+
disableClientFilter?: boolean;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
declare const Lookup: React.FC<LookupProps>;
|
|
@@ -127,6 +140,8 @@ interface QueryBuilderCondition {
|
|
|
127
140
|
nestedLogic?: 'and' | 'or';
|
|
128
141
|
/** Fields available for the related entity (loaded dynamically) */
|
|
129
142
|
nestedFields?: QueryBuilderField[];
|
|
143
|
+
/** True when the attribute from parsed FetchXML didn't match any known field */
|
|
144
|
+
isUnknownField?: boolean;
|
|
130
145
|
}
|
|
131
146
|
interface QueryBuilderGroup {
|
|
132
147
|
id: string;
|
|
@@ -203,6 +218,8 @@ interface QueryBuilderProps {
|
|
|
203
218
|
onFetchEntityFields?: (entityLogicalName: string) => Promise<QueryBuilderField[]>;
|
|
204
219
|
/** Debug/trace callback for logging component behavior */
|
|
205
220
|
onTrace?: (message: string, data?: any) => void;
|
|
221
|
+
/** Enable verbose console.debug tracing (disabled by default) */
|
|
222
|
+
debug?: boolean;
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ interface LookupOption {
|
|
|
14
14
|
text: string;
|
|
15
15
|
/** Optional secondary text - can be a string or React element */
|
|
16
16
|
secondaryText?: React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Optional searchable text that is never rendered. Use this to include
|
|
19
|
+
* additional searchable content (codes, IDs, etc.) without affecting display.
|
|
20
|
+
* Client-side filtering will search this field in addition to `text` and
|
|
21
|
+
* string `secondaryText`.
|
|
22
|
+
*/
|
|
23
|
+
searchFields?: string;
|
|
17
24
|
/** Optional icon to display */
|
|
18
25
|
icon?: React.ReactNode;
|
|
19
26
|
/** Optional expandable details */
|
|
@@ -64,6 +71,12 @@ interface LookupProps extends Omit<InputProps, 'onChange' | 'value'> {
|
|
|
64
71
|
* Use together with `open` for controlled mode, or standalone to observe changes.
|
|
65
72
|
*/
|
|
66
73
|
onOpenChange?: (open: boolean) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Disable client-side filtering of options. Use this when filtering is
|
|
76
|
+
* performed server-side via `onSearchChange` and the returned options are
|
|
77
|
+
* already filtered. Defaults to `false` (client-side filtering enabled).
|
|
78
|
+
*/
|
|
79
|
+
disableClientFilter?: boolean;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
declare const Lookup: React.FC<LookupProps>;
|
|
@@ -127,6 +140,8 @@ interface QueryBuilderCondition {
|
|
|
127
140
|
nestedLogic?: 'and' | 'or';
|
|
128
141
|
/** Fields available for the related entity (loaded dynamically) */
|
|
129
142
|
nestedFields?: QueryBuilderField[];
|
|
143
|
+
/** True when the attribute from parsed FetchXML didn't match any known field */
|
|
144
|
+
isUnknownField?: boolean;
|
|
130
145
|
}
|
|
131
146
|
interface QueryBuilderGroup {
|
|
132
147
|
id: string;
|
|
@@ -203,6 +218,8 @@ interface QueryBuilderProps {
|
|
|
203
218
|
onFetchEntityFields?: (entityLogicalName: string) => Promise<QueryBuilderField[]>;
|
|
204
219
|
/** Debug/trace callback for logging component behavior */
|
|
205
220
|
onTrace?: (message: string, data?: any) => void;
|
|
221
|
+
/** Enable verbose console.debug tracing (disabled by default) */
|
|
222
|
+
debug?: boolean;
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
/**
|
package/dist/index.js
CHANGED
|
@@ -281,6 +281,7 @@ var Lookup = ({
|
|
|
281
281
|
footer,
|
|
282
282
|
open: controlledOpen,
|
|
283
283
|
onOpenChange,
|
|
284
|
+
disableClientFilter = false,
|
|
284
285
|
...inputProps
|
|
285
286
|
}) => {
|
|
286
287
|
const styles = useLookupStyles();
|
|
@@ -316,6 +317,9 @@ var Lookup = ({
|
|
|
316
317
|
[selectedOptionProp, options, selectedKey, internalSelectedOption]
|
|
317
318
|
);
|
|
318
319
|
const filteredOptions = React3__namespace.useMemo(() => {
|
|
320
|
+
if (disableClientFilter) {
|
|
321
|
+
return options;
|
|
322
|
+
}
|
|
319
323
|
if (!searchText || searchText.length < minSearchLength) {
|
|
320
324
|
return options;
|
|
321
325
|
}
|
|
@@ -324,12 +328,15 @@ var Lookup = ({
|
|
|
324
328
|
if (opt.text.toLowerCase().includes(lowerSearch)) {
|
|
325
329
|
return true;
|
|
326
330
|
}
|
|
331
|
+
if (opt.searchFields && opt.searchFields.toLowerCase().includes(lowerSearch)) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
327
334
|
if (typeof opt.secondaryText === "string") {
|
|
328
335
|
return opt.secondaryText.toLowerCase().includes(lowerSearch);
|
|
329
336
|
}
|
|
330
337
|
return false;
|
|
331
338
|
});
|
|
332
|
-
}, [options, searchText, minSearchLength]);
|
|
339
|
+
}, [options, searchText, minSearchLength, disableClientFilter]);
|
|
333
340
|
const highlightedOptionId = React3__namespace.useMemo(() => {
|
|
334
341
|
if (!isOpen || highlightedIndex < 0 || highlightedIndex >= filteredOptions.length) {
|
|
335
342
|
return void 0;
|
|
@@ -964,6 +971,72 @@ var useQueryBuilderStyles = reactComponents.makeStyles({
|
|
|
964
971
|
backgroundColor: reactComponents.tokens.colorPaletteRedBackground1,
|
|
965
972
|
borderRadius: reactComponents.tokens.borderRadiusSmall
|
|
966
973
|
},
|
|
974
|
+
conditionUnknownBanner: {
|
|
975
|
+
display: "flex",
|
|
976
|
+
alignItems: "center",
|
|
977
|
+
gap: reactComponents.tokens.spacingHorizontalXS,
|
|
978
|
+
paddingTop: "2px",
|
|
979
|
+
paddingBottom: "2px",
|
|
980
|
+
paddingLeft: reactComponents.tokens.spacingHorizontalXS,
|
|
981
|
+
paddingRight: reactComponents.tokens.spacingHorizontalXS,
|
|
982
|
+
marginBottom: reactComponents.tokens.spacingVerticalXS,
|
|
983
|
+
backgroundColor: reactComponents.tokens.colorStatusWarningBackground1,
|
|
984
|
+
borderRadius: reactComponents.tokens.borderRadiusSmall,
|
|
985
|
+
fontSize: reactComponents.tokens.fontSizeBase100,
|
|
986
|
+
color: reactComponents.tokens.colorStatusWarningForeground3
|
|
987
|
+
},
|
|
988
|
+
conditionUnknownText: {
|
|
989
|
+
flex: "1 1 auto"
|
|
990
|
+
},
|
|
991
|
+
conditionUnknownButton: {
|
|
992
|
+
fontSize: reactComponents.tokens.fontSizeBase100,
|
|
993
|
+
minWidth: "0",
|
|
994
|
+
paddingTop: "0",
|
|
995
|
+
paddingBottom: "0",
|
|
996
|
+
paddingLeft: reactComponents.tokens.spacingHorizontalXS,
|
|
997
|
+
paddingRight: reactComponents.tokens.spacingHorizontalXS,
|
|
998
|
+
height: "20px",
|
|
999
|
+
color: reactComponents.tokens.colorStatusWarningForeground3
|
|
1000
|
+
},
|
|
1001
|
+
dialogSurfaceNarrow: {
|
|
1002
|
+
maxWidth: "600px"
|
|
1003
|
+
},
|
|
1004
|
+
dialogSurfaceCompact: {
|
|
1005
|
+
maxWidth: "500px"
|
|
1006
|
+
},
|
|
1007
|
+
dialogUploadContent: {
|
|
1008
|
+
display: "flex",
|
|
1009
|
+
flexDirection: "column",
|
|
1010
|
+
gap: reactComponents.tokens.spacingVerticalM
|
|
1011
|
+
},
|
|
1012
|
+
dialogValidationContent: {
|
|
1013
|
+
display: "flex",
|
|
1014
|
+
flexDirection: "column",
|
|
1015
|
+
gap: reactComponents.tokens.spacingVerticalL
|
|
1016
|
+
},
|
|
1017
|
+
monacoTextarea: {
|
|
1018
|
+
minHeight: "200px",
|
|
1019
|
+
fontFamily: reactComponents.tokens.fontFamilyMonospace
|
|
1020
|
+
},
|
|
1021
|
+
uploadErrorText: {
|
|
1022
|
+
color: reactComponents.tokens.colorStatusDangerForeground1
|
|
1023
|
+
},
|
|
1024
|
+
validationSectionTitle: {
|
|
1025
|
+
display: "flex",
|
|
1026
|
+
alignItems: "center",
|
|
1027
|
+
gap: reactComponents.tokens.spacingHorizontalXXS
|
|
1028
|
+
},
|
|
1029
|
+
validationApiRow: {
|
|
1030
|
+
display: "flex",
|
|
1031
|
+
alignItems: "center",
|
|
1032
|
+
gap: reactComponents.tokens.spacingHorizontalS
|
|
1033
|
+
},
|
|
1034
|
+
emptyRelatedEntity: {
|
|
1035
|
+
paddingTop: reactComponents.tokens.spacingVerticalS,
|
|
1036
|
+
paddingBottom: reactComponents.tokens.spacingVerticalS,
|
|
1037
|
+
color: reactComponents.tokens.colorNeutralForeground3,
|
|
1038
|
+
fontSize: reactComponents.tokens.fontSizeBase200
|
|
1039
|
+
},
|
|
967
1040
|
validationIcon: {
|
|
968
1041
|
marginRight: reactComponents.tokens.spacingHorizontalXS
|
|
969
1042
|
},
|
|
@@ -2242,7 +2315,8 @@ var createParsedCondition = (attr, operator, value, value2, fieldMatch, valueDis
|
|
|
2242
2315
|
value,
|
|
2243
2316
|
value2: value2 ?? "",
|
|
2244
2317
|
...valueDisplayName ? { valueDisplayName } : {},
|
|
2245
|
-
...entityAlias ? { entityAlias } : {}
|
|
2318
|
+
...entityAlias ? { entityAlias } : {},
|
|
2319
|
+
...!fieldMatch ? { isUnknownField: true } : {}
|
|
2246
2320
|
});
|
|
2247
2321
|
var parseConditionElement = (condEl, fields) => {
|
|
2248
2322
|
const attr = condEl.getAttribute("attribute") || "";
|
|
@@ -2889,14 +2963,16 @@ var enrichOptionsetFields = async (fields, entityName, trace) => {
|
|
|
2889
2963
|
var QueryBuilder = (props) => {
|
|
2890
2964
|
const styles = useQueryBuilderStyles();
|
|
2891
2965
|
const trace = React3__namespace.useCallback((message, data) => {
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2966
|
+
if (props.debug) {
|
|
2967
|
+
console.debug(
|
|
2968
|
+
"%c FluentUI-Extended ",
|
|
2969
|
+
"background: #845EF7; color: white; padding: 2px 4px; border-radius: 2px; font-weight: bold;",
|
|
2970
|
+
message,
|
|
2971
|
+
data || ""
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2898
2974
|
props.onTrace?.(message, data);
|
|
2899
|
-
}, [props]);
|
|
2975
|
+
}, [props.debug, props.onTrace]);
|
|
2900
2976
|
const [loading, setLoading] = React3__namespace.useState(false);
|
|
2901
2977
|
const [availableFields, setAvailableFields] = React3__namespace.useState(
|
|
2902
2978
|
props.fields && props.fields.length > 0 ? props.fields : FALLBACK_FIELDS
|
|
@@ -3125,6 +3201,9 @@ var QueryBuilder = (props) => {
|
|
|
3125
3201
|
if (condition.kind === "relatedEntity") {
|
|
3126
3202
|
return condition;
|
|
3127
3203
|
}
|
|
3204
|
+
if (condition.isUnknownField && !availableFields.some((f) => f.id === condition.fieldId)) {
|
|
3205
|
+
return condition;
|
|
3206
|
+
}
|
|
3128
3207
|
const matchedField = availableFields.find((field) => field.id === condition.fieldId) || fallbackField;
|
|
3129
3208
|
const operators = getOperatorsForType(matchedField.dataType);
|
|
3130
3209
|
const nextOperator = operators.some((item) => item.value === condition.operator) ? condition.operator : operators[0]?.value;
|
|
@@ -3632,16 +3711,16 @@ var QueryBuilder = (props) => {
|
|
|
3632
3711
|
onClick: onOpenUploadDialog
|
|
3633
3712
|
},
|
|
3634
3713
|
"Import FetchXML"
|
|
3635
|
-
), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Dialog, { open: uploadDialogOpen, onOpenChange: (_, data) => setUploadDialogOpen(data.open) }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogSurface, {
|
|
3714
|
+
), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Dialog, { open: uploadDialogOpen, onOpenChange: (_, data) => setUploadDialogOpen(data.open) }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogSurface, { className: styles.dialogSurfaceNarrow }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogBody, null, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogTitle, null, "Import FetchXML"), /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogContent, { className: styles.dialogUploadContent }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, null, "Paste your FetchXML below to rebuild the query:"), /* @__PURE__ */ React3__namespace.createElement(
|
|
3636
3715
|
reactComponents.Textarea,
|
|
3637
3716
|
{
|
|
3638
3717
|
placeholder: "<fetch><entity name='account'><filter>...</filter></entity></fetch>",
|
|
3639
3718
|
value: uploadXmlText,
|
|
3640
3719
|
onChange: (_, data) => setUploadXmlText(data.value),
|
|
3641
|
-
|
|
3720
|
+
className: styles.monacoTextarea,
|
|
3642
3721
|
resize: "vertical"
|
|
3643
3722
|
}
|
|
3644
|
-
), uploadError && /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, {
|
|
3723
|
+
), uploadError && /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.uploadErrorText }, uploadError)), /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogActions, null, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogTrigger, { disableButtonEnhancement: true }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Button, { appearance: "secondary" }, "Cancel")), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Button, { appearance: "primary", onClick: onApplyUploadedXml, disabled: !uploadXmlText.trim() }, "Apply"))))), props.showValidateButton !== false && /* @__PURE__ */ React3__namespace.createElement(
|
|
3645
3724
|
reactComponents.Button,
|
|
3646
3725
|
{
|
|
3647
3726
|
size: "small",
|
|
@@ -3650,7 +3729,7 @@ var QueryBuilder = (props) => {
|
|
|
3650
3729
|
onClick: onValidate
|
|
3651
3730
|
},
|
|
3652
3731
|
"Validate"
|
|
3653
|
-
), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Dialog, { open: validationDialogOpen, onOpenChange: (_, data) => setValidationDialogOpen(data.open) }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogSurface, {
|
|
3732
|
+
), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Dialog, { open: validationDialogOpen, onOpenChange: (_, data) => setValidationDialogOpen(data.open) }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogSurface, { className: styles.dialogSurfaceCompact }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogBody, null, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogTitle, null, "Query Validation"), /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogContent, { className: styles.dialogValidationContent }, /* @__PURE__ */ React3__namespace.createElement("div", null, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { weight: "semibold", className: styles.validationSectionTitle }, validationResult?.isValid ? /* @__PURE__ */ React3__namespace.createElement("span", { className: styles.validationSuccess }, /* @__PURE__ */ React3__namespace.createElement(reactIcons.CheckmarkCircleRegular, { className: styles.validationIcon }), "Query Structure: Valid") : /* @__PURE__ */ React3__namespace.createElement("span", { className: styles.validationError }, /* @__PURE__ */ React3__namespace.createElement(reactIcons.WarningRegular, { className: styles.validationIcon }), "Query Structure: Errors Found")), !validationResult?.isValid && /* @__PURE__ */ React3__namespace.createElement("ul", { className: styles.validationErrorList }, validationResult?.errors.map((error, idx) => /* @__PURE__ */ React3__namespace.createElement("li", { key: idx, className: styles.validationErrorItem }, /* @__PURE__ */ React3__namespace.createElement("strong", null, error.fieldLabel, ":"), " ", error.message)))), /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.apiValidationSection }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { weight: "semibold", className: styles.validationSectionTitle }, "Dynamics 365 API Test"), !validationResult?.apiValidation?.available ? /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.apiUnavailable }, "API validation unavailable \u2014 not running in Dynamics 365 environment.") : !validationResult?.isValid ? /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.apiUnavailable }, "Fix query structure errors before testing against the API.") : apiValidating ? /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.validationApiRow }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Spinner, { size: "tiny" }), /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, null, "Testing query against Dynamics 365...")) : validationResult?.apiValidation?.tested ? validationResult.apiValidation.success ? /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.validationSuccess }, /* @__PURE__ */ React3__namespace.createElement(reactIcons.CheckmarkCircleRegular, { className: styles.validationIcon }), "Query executed successfully. ", validationResult.apiValidation.recordCount ?? 0, " record(s) would match.") : /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.validationError }, /* @__PURE__ */ React3__namespace.createElement(reactIcons.WarningRegular, { className: styles.validationIcon }), "API Error: ", validationResult.apiValidation.errorMessage) : /* @__PURE__ */ React3__namespace.createElement(reactComponents.Text, { className: styles.apiUnavailable }, "Waiting for validation..."))), /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogActions, null, /* @__PURE__ */ React3__namespace.createElement(reactComponents.DialogTrigger, { disableButtonEnhancement: true }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Button, { appearance: "primary" }, "OK")))))), props.showDeleteAllFiltersButton !== false && /* @__PURE__ */ React3__namespace.createElement(
|
|
3654
3733
|
reactComponents.Button,
|
|
3655
3734
|
{
|
|
3656
3735
|
size: "small",
|
|
@@ -3852,17 +3931,27 @@ var QueryBuilder = (props) => {
|
|
|
3852
3931
|
onClick: () => addNestedCondition(group.id, condition.id, nestedFields)
|
|
3853
3932
|
},
|
|
3854
3933
|
"Add condition"
|
|
3855
|
-
))), selectedRelated && nestedFields.length === 0 && nestedConditions.length === 0 && /* @__PURE__ */ React3__namespace.createElement("div", {
|
|
3934
|
+
))), selectedRelated && nestedFields.length === 0 && nestedConditions.length === 0 && /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.emptyRelatedEntity }, /* @__PURE__ */ React3__namespace.createElement(reactComponents.Spinner, { size: "tiny", label: "Loading fields..." })), !selectedRelated && /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.emptyRelatedEntity })));
|
|
3856
3935
|
}
|
|
3936
|
+
const isFieldUnknown = condition.isUnknownField && !availableFields.some((f) => f.id === condition.fieldId);
|
|
3857
3937
|
const selectedField = availableFields.find((field) => field.id === condition.fieldId) || defaultField;
|
|
3858
3938
|
const operators = getOperatorsForType(selectedField.dataType);
|
|
3859
3939
|
const isNullOperator = condition.operator === "null" || condition.operator === "notnull";
|
|
3860
3940
|
const isBetween = condition.operator === "between";
|
|
3861
3941
|
const conditionRowClass = reactComponents.mergeClasses(
|
|
3862
3942
|
styles.conditionTreeRow,
|
|
3863
|
-
invalidConditionIds.has(condition.id) && styles.conditionInvalid
|
|
3943
|
+
(invalidConditionIds.has(condition.id) || isFieldUnknown) && styles.conditionInvalid
|
|
3864
3944
|
);
|
|
3865
|
-
return /* @__PURE__ */ React3__namespace.createElement("div", { className: conditionRowClass, key: condition.id }, /* @__PURE__ */ React3__namespace.createElement("div", { className: connectorClass }, /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.conditionConnectorLine }), /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.conditionConnectorBranch })), /* @__PURE__ */ React3__namespace.createElement("div", { className:
|
|
3945
|
+
return /* @__PURE__ */ React3__namespace.createElement("div", { className: conditionRowClass, key: condition.id }, /* @__PURE__ */ React3__namespace.createElement("div", { className: connectorClass }, /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.conditionConnectorLine }), /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.conditionConnectorBranch })), isFieldUnknown ? /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.conditionUnknownBanner }, /* @__PURE__ */ React3__namespace.createElement("span", null, "\u26A0"), /* @__PURE__ */ React3__namespace.createElement("span", { className: styles.conditionUnknownText }, "Unknown field ", /* @__PURE__ */ React3__namespace.createElement("strong", null, condition.fieldId), " \u2014 not found in entity metadata. Update or remove this condition before saving."), /* @__PURE__ */ React3__namespace.createElement(
|
|
3946
|
+
reactComponents.Button,
|
|
3947
|
+
{
|
|
3948
|
+
appearance: "subtle",
|
|
3949
|
+
size: "small",
|
|
3950
|
+
className: styles.conditionUnknownButton,
|
|
3951
|
+
onClick: () => removeCondition(group.id, condition.id)
|
|
3952
|
+
},
|
|
3953
|
+
"Remove"
|
|
3954
|
+
)) : /* @__PURE__ */ React3__namespace.createElement("div", { className: rowGridClass, role: "row" }, /* @__PURE__ */ React3__namespace.createElement("div", { className: styles.fieldCell, role: "gridcell" }, /* @__PURE__ */ React3__namespace.createElement(
|
|
3866
3955
|
Lookup,
|
|
3867
3956
|
{
|
|
3868
3957
|
className: styles.compactControl,
|