From 76d8528c10bfa55a89a8dbee1372790ffd1ed534 Mon Sep 17 00:00:00 2001 From: James Tattersall <10601770+jamerst@users.noreply.github.com> Date: Sat, 7 May 2022 17:30:09 +0100 Subject: [PATCH] Add support Fix single quotes not being escaped in strings --- README.md | 11 ++- .../FilterBuilder/components/FilterRoot.tsx | 29 +++--- packages/base/FilterBuilder/hooks.ts | 90 +++++++++++-------- packages/base/FilterBuilder/translation.ts | 42 +++++++++ packages/base/FilterBuilder/types.ts | 30 ++++++- packages/base/FilterBuilder/utils.ts | 4 +- packages/base/components/ODataGridBase.tsx | 34 +++++-- packages/o-data-grid-pro/README.md | 13 +-- packages/o-data-grid-pro/dev/App.tsx | 20 ++--- packages/o-data-grid-pro/package.json | 6 +- packages/o-data-grid-pro/pnpm-lock.yaml | 44 +++++++-- packages/o-data-grid-pro/src/index.ts | 7 +- packages/o-data-grid/README.md | 11 ++- packages/o-data-grid/dev/App.tsx | 20 ++--- packages/o-data-grid/package.json | 6 +- packages/o-data-grid/pnpm-lock.yaml | 44 +++++++-- packages/o-data-grid/src/index.ts | 7 +- 17 files changed, 296 insertions(+), 122 deletions(-) create mode 100644 packages/base/FilterBuilder/translation.ts diff --git a/README.md b/README.md index 8af74ed..320beec 100644 --- a/README.md +++ b/README.md @@ -104,14 +104,17 @@ _* = not applicable to collection fields_ | `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. | | `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` | | `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` | +| `expand` | `Expand` | | Include related entities using the `$expand` clause. | | `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. | | `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. | +| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. | | `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. | | `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. | -| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. | -| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. | +| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.

Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. | +| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. | | `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. | | `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. | +| `select` | `string` | | Additional fields to add to the `$select` clause. | | `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. | | `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. | | `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. | @@ -129,8 +132,8 @@ _* = not applicable to collection fields_ | `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.

Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. | | `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) | | `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components | -| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | -| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | +| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | +| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | | `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` | ## Localization diff --git a/packages/base/FilterBuilder/components/FilterRoot.tsx b/packages/base/FilterBuilder/components/FilterRoot.tsx index df1de1b..85984f5 100644 --- a/packages/base/FilterBuilder/components/FilterRoot.tsx +++ b/packages/base/FilterBuilder/components/FilterRoot.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useCallback, useEffect, useRef, useState } from "react" +import React, { Fragment, useCallback, useEffect, useState } from "react" import { useSetRecoilState } from "recoil"; import { ArrowDropDown } from "@mui/icons-material"; import { Button, ButtonGroup, Grid, MenuItem, MenuList, Paper, Popover } from "@mui/material"; @@ -26,7 +26,6 @@ const FilterRoot = ({ props }: FilterRootProps) => { const odataFilter = UseODataFilter(); const odataFilterWithState = UseODataFilterWithState(); - const currentFilter = useRef(""); const [anchor, setAnchor] = useState(null); @@ -37,9 +36,7 @@ const FilterRoot = ({ props }: FilterRootProps) => { const result = odataFilter(); if (result.filter) { - currentFilter.current = result.filter; - - const returned = onSubmit(result.filter, result.serialised, result.queryString); + const returned = onSubmit({ ...result, filter: result.filter }); if (disableHistory !== true) { window.history.pushState( @@ -48,6 +45,8 @@ const FilterRoot = ({ props }: FilterRootProps) => { ...returned, filterBuilder: { filter: result.filter, + compute: result.compute, + select: result.select, serialised: result.serialised, queryString: result.queryString } @@ -60,13 +59,11 @@ const FilterRoot = ({ props }: FilterRootProps) => { }, [onSubmit, odataFilter, disableHistory]); const reset = useCallback(() => { - currentFilter.current = ""; - setClauses(initialClauses.update(rootConditionUuid, (c) => ({ ...c as ConditionClause, field: props.schema[0].field }))); setTree(initialTree); if (onSubmit) { - onSubmit("", undefined, undefined); + onSubmit({ filter: "" }); } if (disableHistory !== true) { @@ -91,31 +88,31 @@ const FilterRoot = ({ props }: FilterRootProps) => { }, [props.schema, setClauses, setTree]); const restoreState = useCallback((state: any, isPopstate: boolean) => { - let filter = "", obj, queryString; + let filter = "", serialised, queryString, compute, select; if (state?.filterBuilder) { if (state.filterBuilder.reset === true && isPopstate === true) { restoreDefault(); } + compute = state.filterBuilder.compute as string; filter = state.filterBuilder.filter as string; - obj = state.filterBuilder.serialised as SerialisedGroup; + select = state.filterBuilder.select as string[]; + serialised = state.filterBuilder.serialised as SerialisedGroup; queryString = state.filterBuilder.queryString as QueryStringCollection; } else { restoreDefault(); } - if (filter && obj) { - currentFilter.current = filter; - - const [tree, clauses] = deserialise(obj); + if (filter && serialised) { + const [tree, clauses] = deserialise(serialised); setClauses(clauses); setTree(tree); } if (onRestoreState) { - onRestoreState(filter, obj, queryString, state); + onRestoreState({ compute, filter, queryString, select, serialised}, state); } }, [onRestoreState, restoreDefault, setClauses, setTree]); @@ -128,7 +125,7 @@ const FilterRoot = ({ props }: FilterRootProps) => { if (onRestoreState) { const result = odataFilterWithState(clauses, tree); - onRestoreState(result.filter ?? "", result.serialised, result.queryString); + onRestoreState({ ...result, filter: result.filter ?? "" }); } }, [setClauses, setTree, onRestoreState, odataFilterWithState]); diff --git a/packages/base/FilterBuilder/hooks.ts b/packages/base/FilterBuilder/hooks.ts index fab4970..d5eb392 100644 --- a/packages/base/FilterBuilder/hooks.ts +++ b/packages/base/FilterBuilder/hooks.ts @@ -2,7 +2,8 @@ import { useCallback } from "react"; import { useRecoilValue, waitForAll } from "recoil" import { rootGroupUuid } from "./constants"; import { clauseState, schemaState, treeState } from "./state" -import { BaseFieldDef, SerialisedCondition, ConditionClause, FieldDef, SerialisedGroup, GroupClause, Operation, QueryStringCollection, StateClause, StateTree, TreeGroup } from "./types"; +import { defaultTranslators } from "./translation"; +import { BaseFieldDef, SerialisedCondition, ConditionClause, FieldDef, SerialisedGroup, GroupClause, Operation, QueryStringCollection, StateClause, StateTree, TreeGroup, FilterCompute, FilterTranslator } from "./types"; export const UseODataFilter = () => { const schema = useRecoilValue(schemaState); @@ -23,6 +24,8 @@ export const UseODataFilterWithState = () => { type BuiltInnerQuery = { filter?: string, + compute?: string, + select?: string[], queryString?: QueryStringCollection } @@ -53,12 +56,16 @@ const buildGroup = (schema: FieldDef[], clauses: StateClause, tree: StateTree, i if (childClauses.length > 1) { return { filter: `(${childClauses.filter(c => c.filter).map(c => c.filter).join(` ${clause.connective} `)})`, + compute: `${childClauses.filter(c => c.compute).map(c => c.compute).join(",")}`, + select: childClauses.filter(c => c.select).flatMap(c => c.select!), serialised: { connective: clause.connective, children: childClauses.map(c => c.serialised) }, queryString: childClauses.reduce((x, c) => ({ ...x, ...c.queryString }), {}) }; } else if (childClauses.length === 1) { return { filter: childClauses[0].filter, + compute: childClauses[0].compute, + select: childClauses[0].select, serialised: { connective: clause.connective, children: [childClauses[0].serialised] }, queryString: childClauses[0].queryString } @@ -109,60 +116,71 @@ const buildCondition = (schema: FieldDef[], clauses: StateClause, id: string): ( } if (typeof innerResult !== "boolean") { - if (typeof innerResult === "string") { + if (innerResult.filter) { return { - filter: innerResult, + filter: innerResult.filter, + compute: innerResult.compute, + select: innerResult.select, serialised: condition - }; + } } else { return { serialised: condition, - queryString: innerResult - } + queryString: innerResult.queryString + }; } } else { return false; } } -const buildInnerCondition = (schema: BaseFieldDef, field: string, op: Operation, value: any): string | QueryStringCollection | boolean => { +const buildInnerCondition = (schema: BaseFieldDef, field: string, op: Operation, value: any): BuiltInnerQuery | boolean => { if (schema.getCustomQueryString) { - return schema.getCustomQueryString(op, value); + return { + queryString: schema.getCustomQueryString(op, value) + }; } if (schema.getCustomFilterString) { - return schema.getCustomFilterString(op, value) - } + const result = schema.getCustomFilterString(op, value); - if (op === "contains") { - if ((schema.type && schema.type !== "string") || typeof value !== "string") { - console.warn(`Warning: operation "contains" is only supported for fields of type "string"`); - return false; - } - if (schema.caseSensitive === true) { - return `contains(${field}, '${value}')`; - } else { - return `contains(tolower(${field}), tolower('${value}'))`; - } - } else if (op === "null") { - return `${field} eq null`; - } else if (op === "notnull") { - return `${field} ne null`; - } else { - if (schema.type === "date") { - return `date(${field}) ${op} ${value}`; - } else if (schema.type === "datetime") { - return `${field} ${op} ${value}`; - } else if (schema.type === "boolean") { - return `${field} ${op} ${value}`; - } else if (!schema.type || schema.type === "string" || typeof value === "string") { - if (schema.caseSensitive === true) { - return `${field} ${op} '${value}'`; + if (typeof result === "string") { + return { + filter: result + } + } else if (typeof result !== "boolean") { + const compute = result.compute; + if (typeof compute === "string") { + return { + filter: result.filter, + compute: compute + }; } else { - return `tolower(${field}) ${op} tolower('${value}')`; + return { + filter: result.filter, + compute: compute.compute, + select: compute.select + }; } } else { - return `${field} ${op} ${value}`; + return result; } } + + let translator: FilterTranslator; + if (op in defaultTranslators) { + translator = defaultTranslators[op]!; + } else { + translator = defaultTranslators["default"]!; + } + + const result = translator(schema, field, op, value); + + if (typeof result === "string") { + return { + filter: result + }; + } else { + return result; + } } \ No newline at end of file diff --git a/packages/base/FilterBuilder/translation.ts b/packages/base/FilterBuilder/translation.ts new file mode 100644 index 0000000..99bc3b9 --- /dev/null +++ b/packages/base/FilterBuilder/translation.ts @@ -0,0 +1,42 @@ +import { FilterTranslatorCollection } from "./types"; +import { escapeODataString } from "./utils"; + +export const defaultTranslators: FilterTranslatorCollection = { + "contains": (schema, field, op, value) => { + if ((schema.type && schema.type !== "string") || typeof value !== "string") { + console.warn(`Warning: operation "contains" is only supported for fields of type "string"`); + return false; + } + if (schema.caseSensitive === true) { + return `contains(${field}, '${escapeODataString(value)}')`; + } else { + return `contains(tolower(${field}), tolower('${escapeODataString(value)}'))`; + } + }, + + "null": (schema, field, op, value) => { + return `${field} eq null`; + }, + + "notnull": (schema, field, op, value) => { + return `${field} ne null`; + }, + + "default": (schema, field, op, value) => { + if (schema.type === "date") { + return `date(${field}) ${op} ${value}`; + } else if (schema.type === "datetime") { + return `${field} ${op} ${value}`; + } else if (schema.type === "boolean") { + return `${field} ${op} ${value}`; + } else if (!schema.type || schema.type === "string" || typeof value === "string") { + if (schema.caseSensitive === true) { + return `${field} ${op} '${escapeODataString(value)}'`; + } else { + return `tolower(${field}) ${op} tolower('${escapeODataString(value)}')`; + } + } else { + return `${field} ${op} ${value}`; + } + } +} \ No newline at end of file diff --git a/packages/base/FilterBuilder/types.ts b/packages/base/FilterBuilder/types.ts index 83ad5ec..925d920 100644 --- a/packages/base/FilterBuilder/types.ts +++ b/packages/base/FilterBuilder/types.ts @@ -6,8 +6,8 @@ import { ValueOption } from "../types"; export type ExternalBuilderProps = { searchMenuItems?: ({ label: string, onClick: () => void })[], - onSubmit?: (filter: string, serialised: SerialisedGroup | undefined, queryString: QueryStringCollection | undefined) => (void | any), - onRestoreState?: (filter: string, serialised: SerialisedGroup | undefined, queryString: QueryStringCollection | undefined, state?: any) => void, + onSubmit?: (params: FilterParameters) => (void | any), + onRestoreState?: (params: FilterParameters, state?: any) => void, localeText?: FilterBuilderLocaleText, autocompleteGroups?: string[], @@ -24,6 +24,14 @@ export type ExternalBuilderProps = { filter?: SerialisedGroup } +export type FilterParameters = { + compute?: string, + filter: string, + queryString?: QueryStringCollection, + select?: string[], + serialised?: SerialisedGroup, +} + export type FilterBuilderLocaleText = { and?: string, or?: string, @@ -68,7 +76,7 @@ export type BaseFieldDef = { filterOperators?: Operation[], filterType?: string, - getCustomFilterString?: (op: Operation, value: any) => string, + getCustomFilterString?: (op: Operation, value: any) => string | FilterCompute | boolean, getCustomQueryString?: (op: Operation, value: any) => QueryStringCollection, label?: string, @@ -93,10 +101,26 @@ export type FieldDef = BaseFieldDef & { export type CollectionFieldDef = BaseFieldDef; +export type FilterCompute = { + filter: string, + compute: string | ComputeSelect +} + +export type ComputeSelect = { + compute: string, + select: string[] +} + export type QueryStringCollection = { [key: string]: string } +export type FilterTranslatorCollection = { + [key in Operation | "default"]?: FilterTranslator +} + +export type FilterTranslator = (schema: BaseFieldDef, field: string, op: Operation, value: any) => string | boolean; + export type Connective = "and" | "or" export type Operation = "eq" | "ne" | "gt" | "lt" | "ge" | "le" | "contains" | "null" | "notnull" diff --git a/packages/base/FilterBuilder/utils.ts b/packages/base/FilterBuilder/utils.ts index 75e1731..51578e5 100644 --- a/packages/base/FilterBuilder/utils.ts +++ b/packages/base/FilterBuilder/utils.ts @@ -73,4 +73,6 @@ const groupObjToMap = (obj: SerialisedGroup, id: string, clauses?: StateClause): }); return [{ id: id, children: children }, clauses] -} \ No newline at end of file +} + +export const escapeODataString = (val: string) => val.replace("'", "''"); \ No newline at end of file diff --git a/packages/base/components/ODataGridBase.tsx b/packages/base/components/ODataGridBase.tsx index f44bbc0..d77434f 100644 --- a/packages/base/components/ODataGridBase.tsx +++ b/packages/base/components/ODataGridBase.tsx @@ -10,7 +10,7 @@ import { Expand, ODataResponse, ODataGridBaseProps, IGridSortModel, IGridProps, import { ExpandToQuery, Flatten, GroupArrayBy, GetPageNumber, GetPageSizeOrDefault } from "../utils"; import { defaultPageSize } from "../constants"; -import { SerialisedGroup, QueryStringCollection } from "../FilterBuilder/types"; +import { SerialisedGroup, QueryStringCollection, FilterParameters } from "../FilterBuilder/types"; const ODataGridBase = (props.defaultSortModel); const [filter, setFilter] = useState(""); + const [filterSelects, setFilterSelects] = useState(); + const [compute, setCompute] = useState(); const [queryString, setQueryString] = useState(); const [visibleColumns, setVisibleColumns] = useState(props.columns @@ -65,6 +67,10 @@ const ODataGridBase = fields.add(c)); } + if (filterSelects) { + filterSelects.forEach((s) => fields.add(s)); + } + // group all expands by the navigation field const groupedExpands = GroupArrayBy( props.columns @@ -107,6 +113,10 @@ const ODataGridBase = 0) { query.append("$orderby", sortModel.map(s => { const sortCol = props.columns.find(c => c.field === s.field); @@ -141,6 +151,8 @@ const ODataGridBase = { + const handleBuilderSubmit = useCallback((params: FilterParameters) => { pendingFilter.current = true; fetchCount.current = true; if (props.filterBuilderProps?.onSubmit) { - props.filterBuilderProps.onSubmit(f, s, q); + props.filterBuilderProps.onSubmit(params); } - setFilter(f); - setQueryString(q); + setCompute(params.compute); + setFilter(params.filter); + setFilterSelects(params.select); + setQueryString(params.queryString); setPageNumber(0); return { oDataGrid: { sortModel: sortModel } }; }, [props.filterBuilderProps, sortModel]); - const handleBuilderRestore = useCallback((f: string, s: SerialisedGroup | undefined, q: QueryStringCollection | undefined, state: any) => { + const handleBuilderRestore = useCallback((params: FilterParameters, state: any) => { fetchCount.current = true; if (props.filterBuilderProps?.onRestoreState) { - props.filterBuilderProps.onRestoreState(f, s, q, state); + props.filterBuilderProps.onRestoreState(params, state); } if (props.disableHistory !== true) { @@ -183,8 +197,10 @@ const ODataGridBase = { diff --git a/packages/o-data-grid-pro/README.md b/packages/o-data-grid-pro/README.md index 0d8bfdd..320beec 100644 --- a/packages/o-data-grid-pro/README.md +++ b/packages/o-data-grid-pro/README.md @@ -1,4 +1,4 @@ -# ODataGridPro +# ODataGrid ODataGrid is an extension to the [MUI DataGrid](https://github.com/mui-org/material-ui-x) React component which implements features such as sorting, pagination, column selection, and filtering using the [OData Standard](https://www.odata.org/). This allows you to quickly create a powerful interface for browsing data with minimal back-end code. ![ODataGrid in action](https://raw.githubusercontent.com/jamerst/o-data-grid/main/images/o-data-grid.png) @@ -104,14 +104,17 @@ _* = not applicable to collection fields_ | `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. | | `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` | | `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` | +| `expand` | `Expand` | | Include related entities using the `$expand` clause. | | `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. | | `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. | +| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. | | `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. | | `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. | -| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. | -| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. | +| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.

Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. | +| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. | | `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. | | `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. | +| `select` | `string` | | Additional fields to add to the `$select` clause. | | `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. | | `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. | | `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. | @@ -129,8 +132,8 @@ _* = not applicable to collection fields_ | `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.

Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. | | `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) | | `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components | -| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | -| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | +| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | +| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | | `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` | ## Localization diff --git a/packages/o-data-grid-pro/dev/App.tsx b/packages/o-data-grid-pro/dev/App.tsx index 3d558a5..93bcd3e 100644 --- a/packages/o-data-grid-pro/dev/App.tsx +++ b/packages/o-data-grid-pro/dev/App.tsx @@ -2,7 +2,7 @@ import React from "react" import { CssBaseline, Typography, Grid, TextField, Slider, Chip } from "@mui/material"; import { createTheme, ThemeProvider } from "@mui/material/styles"; import { GridSortModel } from "@mui/x-data-grid" -import { ODataGridColDef, QueryStringCollection, ODataColumnVisibilityModel } from "../src/index"; +import { ODataGridColDef, ODataColumnVisibilityModel, escapeODataString } from "../src/index"; import ODataGridPro from "../src/ODataGridPro"; import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; @@ -30,7 +30,6 @@ const App = () => { getRowId={(row) => row.Id} defaultSortModel={defaultSort} filterBuilderProps={filterBuilderProps} - defaultPageSize={15} alwaysSelect={alwaysFetch} /> @@ -85,16 +84,17 @@ const columns: ODataGridColDef[] = [ ), - getCustomQueryString: (_, v) => { + getCustomFilterString: (_, v) => { const filter = v as LocationFilter; - const result: QueryStringCollection = {}; - if (filter.location) { - result["location"] = filter.location!; - result["distance"] = (filter.distance ?? 15).toString(); - } - - return result; + return { + filter: `Latitude ne null and Longitude ne null and Distance le ${filter.distance ?? 15}`, + compute: { + compute: `geocode('${escapeODataString(filter.location ?? "")}', Latitude, Longitude) as Distance`, + select: ["Distance"] + } + }; }, + valueGetter: (params) => `${params.row.Location}${params.row.Distance ? ` (${params.row.Distance.toFixed(1)} mi away)` : ""}`, autocompleteGroup: "Job" }, { diff --git a/packages/o-data-grid-pro/package.json b/packages/o-data-grid-pro/package.json index d7e9f80..8f366a6 100644 --- a/packages/o-data-grid-pro/package.json +++ b/packages/o-data-grid-pro/package.json @@ -1,6 +1,6 @@ { "name": "o-data-grid-pro", - "version": "1.0.1", + "version": "1.1.0", "description": "A React Data Grid and Query Builder for OData APIs. Based on the Material-UI DataGridPro.", "main": "build/o-data-grid-pro-cjs.js", "module": "build/o-data-grid-pro-esm.js", @@ -46,8 +46,8 @@ }, "dependencies": { "immutable": "^4.0.0", - "recoil": "^0.5.2", - "tss-react": "^3.2.4", + "recoil": "0.7.2", + "tss-react": "3.6.2", "uuid": "^8.3.2" } } diff --git a/packages/o-data-grid-pro/pnpm-lock.yaml b/packages/o-data-grid-pro/pnpm-lock.yaml index ea37d00..1f47847 100644 --- a/packages/o-data-grid-pro/pnpm-lock.yaml +++ b/packages/o-data-grid-pro/pnpm-lock.yaml @@ -2,18 +2,28 @@ lockfileVersion: 5.3 specifiers: immutable: ^4.0.0 - recoil: ^0.5.2 - tss-react: ^3.2.4 + recoil: 0.7.2 + tss-react: 3.6.2 uuid: ^8.3.2 dependencies: immutable: 4.0.0 - recoil: 0.5.2 - tss-react: 3.4.1 + recoil: 0.7.2 + tss-react: 3.6.2 uuid: 8.3.2 packages: + /@emotion/cache/11.7.1: + resolution: {integrity: sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==} + dependencies: + '@emotion/memoize': 0.7.5 + '@emotion/sheet': 1.1.0 + '@emotion/utils': 1.0.0 + '@emotion/weak-memoize': 0.2.5 + stylis: 4.0.13 + dev: false + /@emotion/hash/0.8.0: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} dev: false @@ -32,6 +42,10 @@ packages: csstype: 3.0.10 dev: false + /@emotion/sheet/1.1.0: + resolution: {integrity: sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==} + dev: false + /@emotion/unitless/0.7.5: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} dev: false @@ -40,6 +54,10 @@ packages: resolution: {integrity: sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==} dev: false + /@emotion/weak-memoize/0.2.5: + resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} + dev: false + /csstype/3.0.10: resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==} dev: false @@ -52,8 +70,8 @@ packages: resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==} dev: false - /recoil/0.5.2: - resolution: {integrity: sha512-Edibzpu3dbUMLy6QRg73WL8dvMl9Xqhp+kU+f2sJtXxsaXvAlxU/GcnDE8HXPkprXrhHF2e6SZozptNvjNF5fw==} + /recoil/0.7.2: + resolution: {integrity: sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==} peerDependencies: react: '>=16.13.1' react-dom: '*' @@ -67,11 +85,21 @@ packages: hamt_plus: 1.0.2 dev: false - /tss-react/3.4.1: - resolution: {integrity: sha512-S/OA9+8wBatj5B7JoWaz3K07dVY7UjPDwong0IZwOtqLUjTgJuT4GTrs7hxurq/cvF6DzPLv9HI754Aw1p3lqA==} + /stylis/4.0.13: + resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==} + dev: false + + /tss-react/3.6.2: + resolution: {integrity: sha512-+ecQIqCNFVlVJVk3NiCWZY2+5DhVKMVUVxIbEwv9nYsTRQA/KxyKCU8g+KMj5ByqFv9LW76+TUYzbWck/IHkfA==} peerDependencies: '@emotion/react': ^11.4.1 + '@emotion/server': ^11.4.0 + react: ^16.8.0 || ^17.0.2 || ^18.0.0 + peerDependenciesMeta: + '@emotion/server': + optional: true dependencies: + '@emotion/cache': 11.7.1 '@emotion/serialize': 1.0.2 '@emotion/utils': 1.0.0 dev: false diff --git a/packages/o-data-grid-pro/src/index.ts b/packages/o-data-grid-pro/src/index.ts index 49604f1..ba49602 100644 --- a/packages/o-data-grid-pro/src/index.ts +++ b/packages/o-data-grid-pro/src/index.ts @@ -19,11 +19,16 @@ export type { SelectOption, ValueOption, ODataColumnVisibilityModel } from "../. export type { CollectionFieldDef, CollectionOperation, + ComputeSelect, Connective, FilterBuilderLocaleText, + FilterCompute, + FilterParameters, FieldDef, QueryStringCollection, SerialisedGroup, SerialisedCondition, } from "../../base/FilterBuilder/types"; -export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder"; \ No newline at end of file +export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder"; + +export { escapeODataString } from "../../base/FilterBuilder/utils" \ No newline at end of file diff --git a/packages/o-data-grid/README.md b/packages/o-data-grid/README.md index 8af74ed..320beec 100644 --- a/packages/o-data-grid/README.md +++ b/packages/o-data-grid/README.md @@ -104,14 +104,17 @@ _* = not applicable to collection fields_ | `collectionFields` | `ODataGridColDef` | | Column definitions for the subfields of the collection. Any properties marked with * are not supported. | | `datePickerProps` | [`DatePickerProps`](https://mui.com/api/date-picker/) | | Props to pass to the `DatePicker` component for columns with type `date` | | `dateTimePickerProps` | [`DateTimePickerProps`](https://mui.com/api/date-time-picker/) | | Props to pass to the `DateTimePicker` component for columns with type `datetime` | +| `expand` | `Expand` | | Include related entities using the `$expand` clause. | | `filterable` | `boolean` | | Hides the field and does not allow filtering in the FilterBuilder when set to `false`. | | `filterField` | `string` | | If the field name is different to the field which should be used for filtering, provide the field for filtering here. See also: `filterType`. | +| `filterOnly` | `boolean` | `false` | Set to true if the field is for filtering only and cannot be displayed as a column in the datagrid. | | `filterOperators` | `Operation[]` | `["eq", "ne", "gt", "lt", "ge", "le", "contains", "null", "notnull"]` | Array of supported filter operations for the field. | | `filterType` | `string` | | If the type of the field to be filtered is different to that of the displayed field, provide the type here. See also: `filterField`. | -| `getCustomFilterString` | `(op: Operation, value: any) => string` | | Function to generate a custom filter string for use in the `$filter` clause. | -| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. Allows custom filtering to be performed which is not supported by OData. | +| `getCustomFilterString` | `(op: Operation, value: any) => string \| FilterCompute \| boolean` | | Function to generate a custom filter string for use in the `$filter` clause. Return `false` to skip and not add it to the `$filter` clause.

Also supports the use of the `$compute` clause by returning a `FilterCompute`. The computed property/properties can also be added to `$select` by returning a `ComputeSelect`. | +| `getCustomQueryString` | `(op: Operation, value: any) => ({ [key: string]: string })` | | Function to generate a custom set of query string values to add to the OData request. | | `label` | `string` | Defaults to the same value as `headerName` or `field` | Text to be displayed in the field selection dropdown. | | `nullable` | `boolean` | | Adds an "Unknown" option to the value dropdown for columns with type `boolean` if set to `true`. | +| `select` | `string` | | Additional fields to add to the `$select` clause. | | `selectProps` | `{ selectProps?: SelectProps, formControlProps?: FormControlProps, label?: string }` | | Props to pass to the `Select`, `FormControl` and `Label` components for this column in the filter. See also: `textFieldProps`. | | `sortField` | `string` | | If the name of the field to sort by is different to that of the displayed field, provide the name for sorting by here. | | `textFieldProps` | [`TextFieldProps`](https://mui.com/api/text-field/) | | Props to pass to the `TextField` component in the filter for this column. See also: `selectProps`. | @@ -129,8 +132,8 @@ _* = not applicable to collection fields_ | `filter` | `SerialisedGroup` | | Allows setting the state of the FilterBuilder using a `SerialisedGroup`. You could use this to implement filter saving and restoring.

Changing the value of this property will cause `restoreState` to be called, but with the `state` property undefined. | | `localeText` | [`FilterBuilderLocaleText`](#FilterBuilderLocaleText) | | Localization strings for `FilterBuilder` (see [Localization](#localization) section) | | `localizationProviderProps` | [`LocalizationProviderProps`](https://mui.com/components/date-picker/#localization) | | Props to pass to the `LocalizationProvider` component for the `DatePicker` and `DateTimePicker` components | -| `onSubmit` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`filter` is the OData filter string, `serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | -| `onRestoreState` | `(filter: string, serialised: SerialisedGroup \| undefined, queryString: QueryStringCollection \| undefined, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | +| `onSubmit` | `(params: FilterParameters) => (void \| any)` | | Function called when FilterBuilder is submitted (e.g. when the search button is clicked). You should use this to trigger the OData request.

`params.filter` is the OData filter string, `params.serialised` is a serialised form of the query which can be used to load the query back into the filter builder. | +| `onRestoreState` | `(params: FilterParameters, state?: any) => void` | | Function called when the state of the FilterBuilder is restored (e.g. from history navigation). You should also use this to trigger the OData request alongside the `onSubmit` callback.

`state` is the the value of `history.state` that the query was restored from. `state` will be undefined if the call is as a result of the `filter` property changing. | | `searchMenuItems` | `({ label: string, onClick: () => void })[]` | | Array of entries to add to the dropdown menu next to the Search button of the `FilterBuilder` | ## Localization diff --git a/packages/o-data-grid/dev/App.tsx b/packages/o-data-grid/dev/App.tsx index a7f0f03..3a2844e 100644 --- a/packages/o-data-grid/dev/App.tsx +++ b/packages/o-data-grid/dev/App.tsx @@ -2,7 +2,7 @@ import React from "react" import { CssBaseline, Typography, Grid, TextField, Slider, Chip } from "@mui/material"; import { createTheme, ThemeProvider } from "@mui/material/styles"; import { GridSortModel } from "@mui/x-data-grid" -import { ODataGridColDef, QueryStringCollection, ODataColumnVisibilityModel } from "../src/index"; +import { ODataGridColDef, ODataColumnVisibilityModel, escapeODataString } from "../src/index"; import ODataGrid from "../src/ODataGrid"; import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; @@ -30,7 +30,6 @@ const App = () => { getRowId={(row) => row.Id} defaultSortModel={defaultSort} filterBuilderProps={filterBuilderProps} - defaultPageSize={15} alwaysSelect={alwaysFetch} /> @@ -85,16 +84,17 @@ const columns: ODataGridColDef[] = [ ), - getCustomQueryString: (_, v) => { + getCustomFilterString: (_, v) => { const filter = v as LocationFilter; - const result: QueryStringCollection = {}; - if (filter.location) { - result["location"] = filter.location!; - result["distance"] = (filter.distance ?? 15).toString(); - } - - return result; + return { + filter: `Latitude ne null and Longitude ne null and Distance le ${filter.distance ?? 15}`, + compute: { + compute: `geocode('${escapeODataString(filter.location ?? "")}', Latitude, Longitude) as Distance`, + select: ["Distance"] + } + }; }, + valueGetter: (params) => `${params.row.Location}${params.row.Distance ? ` (${params.row.Distance.toFixed(1)} mi away)` : ""}`, autocompleteGroup: "Job" }, { diff --git a/packages/o-data-grid/package.json b/packages/o-data-grid/package.json index f314245..195e4d4 100644 --- a/packages/o-data-grid/package.json +++ b/packages/o-data-grid/package.json @@ -1,6 +1,6 @@ { "name": "o-data-grid", - "version": "1.0.1", + "version": "1.1.0", "description": "A React Data Grid and Query Builder for OData APIs. Based on the Material-UI DataGrid.", "main": "build/o-data-grid-cjs.js", "module": "build/o-data-grid-esm.js", @@ -46,8 +46,8 @@ }, "dependencies": { "immutable": "^4.0.0", - "recoil": "^0.5.2", - "tss-react": "^3.2.4", + "recoil": "0.7.2", + "tss-react": "3.6.2", "uuid": "^8.3.2" } } diff --git a/packages/o-data-grid/pnpm-lock.yaml b/packages/o-data-grid/pnpm-lock.yaml index ea37d00..1f47847 100644 --- a/packages/o-data-grid/pnpm-lock.yaml +++ b/packages/o-data-grid/pnpm-lock.yaml @@ -2,18 +2,28 @@ lockfileVersion: 5.3 specifiers: immutable: ^4.0.0 - recoil: ^0.5.2 - tss-react: ^3.2.4 + recoil: 0.7.2 + tss-react: 3.6.2 uuid: ^8.3.2 dependencies: immutable: 4.0.0 - recoil: 0.5.2 - tss-react: 3.4.1 + recoil: 0.7.2 + tss-react: 3.6.2 uuid: 8.3.2 packages: + /@emotion/cache/11.7.1: + resolution: {integrity: sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==} + dependencies: + '@emotion/memoize': 0.7.5 + '@emotion/sheet': 1.1.0 + '@emotion/utils': 1.0.0 + '@emotion/weak-memoize': 0.2.5 + stylis: 4.0.13 + dev: false + /@emotion/hash/0.8.0: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} dev: false @@ -32,6 +42,10 @@ packages: csstype: 3.0.10 dev: false + /@emotion/sheet/1.1.0: + resolution: {integrity: sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==} + dev: false + /@emotion/unitless/0.7.5: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} dev: false @@ -40,6 +54,10 @@ packages: resolution: {integrity: sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==} dev: false + /@emotion/weak-memoize/0.2.5: + resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} + dev: false + /csstype/3.0.10: resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==} dev: false @@ -52,8 +70,8 @@ packages: resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==} dev: false - /recoil/0.5.2: - resolution: {integrity: sha512-Edibzpu3dbUMLy6QRg73WL8dvMl9Xqhp+kU+f2sJtXxsaXvAlxU/GcnDE8HXPkprXrhHF2e6SZozptNvjNF5fw==} + /recoil/0.7.2: + resolution: {integrity: sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==} peerDependencies: react: '>=16.13.1' react-dom: '*' @@ -67,11 +85,21 @@ packages: hamt_plus: 1.0.2 dev: false - /tss-react/3.4.1: - resolution: {integrity: sha512-S/OA9+8wBatj5B7JoWaz3K07dVY7UjPDwong0IZwOtqLUjTgJuT4GTrs7hxurq/cvF6DzPLv9HI754Aw1p3lqA==} + /stylis/4.0.13: + resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==} + dev: false + + /tss-react/3.6.2: + resolution: {integrity: sha512-+ecQIqCNFVlVJVk3NiCWZY2+5DhVKMVUVxIbEwv9nYsTRQA/KxyKCU8g+KMj5ByqFv9LW76+TUYzbWck/IHkfA==} peerDependencies: '@emotion/react': ^11.4.1 + '@emotion/server': ^11.4.0 + react: ^16.8.0 || ^17.0.2 || ^18.0.0 + peerDependenciesMeta: + '@emotion/server': + optional: true dependencies: + '@emotion/cache': 11.7.1 '@emotion/serialize': 1.0.2 '@emotion/utils': 1.0.0 dev: false diff --git a/packages/o-data-grid/src/index.ts b/packages/o-data-grid/src/index.ts index c2ad922..ccf6c8a 100644 --- a/packages/o-data-grid/src/index.ts +++ b/packages/o-data-grid/src/index.ts @@ -19,11 +19,16 @@ export type { SelectOption, ValueOption, ODataColumnVisibilityModel } from "../. export type { CollectionFieldDef, CollectionOperation, + ComputeSelect, Connective, FilterBuilderLocaleText, + FilterCompute, + FilterParameters, FieldDef, QueryStringCollection, SerialisedGroup, SerialisedCondition, } from "../../base/FilterBuilder/types"; -export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder"; \ No newline at end of file +export type { FilterBuilderProps } from "../../base/FilterBuilder/components/FilterBuilder"; + +export { escapeODataString } from "../../base/FilterBuilder/utils"; \ No newline at end of file