@react-querybuilder/material
Version:
Custom MUI (Material Design) components for react-querybuilder
559 lines (558 loc) • 18.4 kB
JavaScript
import * as React from "react";
import { createContext, forwardRef, useContext, useMemo } from "react";
import { ActionElement, DragHandle, NotToggle, ShiftActions, ValueEditor, ValueSelector, defaultTranslations, getCompatContextProvider, getFirstOption, isOptionGroupArray, parseNumber, useValueEditor, useValueSelector } from "react-querybuilder";
import CloseIcon from "@mui/icons-material/Close";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import DragIndicator from "@mui/icons-material/DragIndicator";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
import Button from "@mui/material/Button";
import Checkbox from "@mui/material/Checkbox";
import FormControl from "@mui/material/FormControl";
import FormControlLabel from "@mui/material/FormControlLabel";
import InputLabel from "@mui/material/InputLabel";
import ListSubheader from "@mui/material/ListSubheader";
import MenuItem from "@mui/material/MenuItem";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import Select from "@mui/material/Select";
import Switch from "@mui/material/Switch";
import TextareaAutosize from "@mui/material/TextareaAutosize";
import TextField from "@mui/material/TextField";
//#region src/RQBMaterialContext.ts
/**
* @group Components
*/
const RQBMaterialContext = createContext(null);
//#endregion
//#region src/MaterialActionElement.tsx
/**
* @group Components
*/
const MaterialActionElement = ({ className, handleOnClick, label, title, disabled, disabledTranslation, testID, path, level, rules, context, validation, ruleOrGroup, schema, muiComponents: muiComponentsProp, ...otherProps }) => {
const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp;
const key = muiComponents ? "mui" : "no-mui";
if (!muiComponents) {
const AE = ActionElement;
return /* @__PURE__ */ React.createElement(AE, {
key,
className,
handleOnClick,
label,
title,
disabled,
disabledTranslation,
testID,
path,
level,
rules,
context,
validation,
ruleOrGroup,
schema
});
}
const { Button } = muiComponents;
return /* @__PURE__ */ React.createElement(Button, {
key,
variant: "contained",
color: "secondary",
className,
title: disabledTranslation && disabled ? disabledTranslation.title : title,
size: "small",
disabled: disabled && !disabledTranslation,
onClick: (e) => handleOnClick(e),
...otherProps
}, disabledTranslation && disabled ? disabledTranslation.label : label);
};
//#endregion
//#region src/MaterialDragHandle.tsx
/**
* @group Components
*/
const MaterialDragHandle = forwardRef(({ className, title, path, level, testID, label, disabled, context, validation, schema, ruleOrGroup, muiComponents: muiComponentsProp, ...otherProps }, dragRef) => {
const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp;
const key = muiComponents ? "mui" : "no-mui";
if (!muiComponents) return /* @__PURE__ */ React.createElement(DragHandle, {
key,
path,
level,
className,
title,
testID,
label,
disabled,
context,
validation,
schema,
ruleOrGroup
});
const { DragIndicator } = muiComponents;
return /* @__PURE__ */ React.createElement("span", {
key,
ref: dragRef,
className,
title
}, /* @__PURE__ */ React.createElement(DragIndicator, otherProps));
});
//#endregion
//#region src/MaterialNotToggle.tsx
/**
* @group Components
*/
const MaterialNotToggle = ({ className, handleOnChange, label, checked, title, disabled, level, path, context, validation, testID, schema, ruleGroup, muiComponents: muiComponentsProp, ...otherProps }) => {
const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp;
const { FormControlLabel, Switch } = muiComponents ?? {};
const switchControl = useMemo(() => Switch && /* @__PURE__ */ React.createElement(Switch, {
checked: !!checked,
onChange: (e) => handleOnChange(e.target.checked),
...otherProps
}), [
checked,
handleOnChange,
otherProps,
Switch
]);
const key = muiComponents ? "mui" : "no-mui";
if (!muiComponents) return /* @__PURE__ */ React.createElement(NotToggle, {
key,
className,
handleOnChange,
label,
checked,
title,
disabled,
path,
level,
context,
validation,
testID,
schema,
ruleGroup
});
return /* @__PURE__ */ React.createElement(FormControlLabel, {
key,
className,
title,
disabled,
control: switchControl,
label: label ?? ""
});
};
//#endregion
//#region src/MaterialShiftActions.tsx
/**
* @group Components
*/
const MaterialShiftActions = ({ path, shiftUp, shiftDown, shiftUpDisabled, shiftDownDisabled, disabled, className, labels, titles, testID, muiComponents: muiComponentsProp, ...otherProps }) => {
const muiComponents = React.useContext(RQBMaterialContext) ?? muiComponentsProp;
const key = muiComponents ? "mui" : "no-mui";
if (!muiComponents) return /* @__PURE__ */ React.createElement(ShiftActions, {
key,
path,
disabled,
className,
labels,
titles,
testID,
shiftUp,
shiftDown,
shiftUpDisabled,
shiftDownDisabled,
...otherProps
});
const { Button } = muiComponents;
return /* @__PURE__ */ React.createElement("div", {
key,
"data-testid": testID,
className
}, /* @__PURE__ */ React.createElement(Button, {
sx: { boxShadow: "none" },
variant: "contained",
color: "secondary",
className,
title: titles?.shiftUp,
size: "small",
disabled: disabled || shiftUpDisabled,
onClick: shiftUp
}, labels?.shiftUp), /* @__PURE__ */ React.createElement(Button, {
sx: { boxShadow: "none" },
variant: "contained",
color: "secondary",
className,
title: titles?.shiftDown,
size: "small",
disabled: disabled || shiftDownDisabled,
onClick: shiftDown
}, labels?.shiftDown));
};
//#endregion
//#region src/MaterialValueEditor.tsx
/**
* @group Components
*/
const MaterialValueEditor = (props) => {
const { muiComponents: muiComponentsProp, ...propsForValueEditor } = props;
const { field: _f, fieldData, operator, value, handleOnChange, title, className, type, inputType, path, level, values = [], listsAsArrays, separator, valueSource: _vs, disabled, testID, selectorComponent: SelectorComponent = props.schema.controls.valueSelector, showInputLabels: silProp, extraProps, parseNumbers: _parseNumbers, ...propsForValueSelector } = propsForValueEditor;
const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp;
const { valueAsArray, multiValueHandler, bigIntValueHandler, parseNumberMethod, valueListItemClassName, inputTypeCoerced } = useValueEditor(propsForValueEditor);
const masterKey = muiComponents ? "mui" : "no-mui";
const { Checkbox, FormControl, FormControlLabel, Radio, RadioGroup, Switch, TextareaAutosize, TextField, showInputLabels: silCtx } = useMemo(() => muiComponents ?? {}, [muiComponents]);
if (!muiComponents) return /* @__PURE__ */ React.createElement(ValueEditor, {
skipHook: true,
key: masterKey,
...propsForValueEditor
});
if (operator === "null" || operator === "notNull") return null;
const placeHolderText = fieldData?.placeholder ?? "";
const showInputLabels = silProp || silCtx;
if ((operator === "between" || operator === "notBetween") && (type === "select" || type === "text")) {
const editors = ["From", "To"].map((key, i) => {
if (type === "text") return /* @__PURE__ */ React.createElement(TextField, {
key,
variant: "standard",
type: inputTypeCoerced,
className: valueListItemClassName,
placeholder: placeHolderText,
value: valueAsArray[i] ?? "",
disabled,
label: showInputLabels ? key : void 0,
onChange: (e) => multiValueHandler(e.target.value, i),
...extraProps
});
return /* @__PURE__ */ React.createElement(SelectorComponent, {
key,
...propsForValueSelector,
title: showInputLabels ? key : void 0,
path,
level,
className: valueListItemClassName,
handleOnChange: (v) => multiValueHandler(v, i),
muiComponents,
disabled,
value: valueAsArray[i] ?? getFirstOption(values),
options: values,
listsAsArrays
});
});
return /* @__PURE__ */ React.createElement(FormControl, {
key: masterKey,
"data-testid": testID,
className,
title,
disabled
}, editors[0], separator, editors[1]);
}
switch (type) {
case "select":
case "multiselect": return /* @__PURE__ */ React.createElement(SelectorComponent, {
key: masterKey,
...propsForValueSelector,
muiComponents,
path,
level,
className,
handleOnChange,
options: values,
value,
disabled,
title,
multiple: type === "multiselect",
listsAsArrays
});
case "textarea": return /* @__PURE__ */ React.createElement(TextareaAutosize, {
key: masterKey,
value,
title,
disabled,
className,
placeholder: placeHolderText,
onChange: (e) => handleOnChange(e.target.value),
...extraProps
});
case "switch": return /* @__PURE__ */ React.createElement(Switch, {
key: masterKey,
checked: !!value,
title,
disabled,
className,
onChange: (e) => handleOnChange(e.target.checked),
...extraProps
});
case "checkbox": return /* @__PURE__ */ React.createElement(Checkbox, {
key: masterKey,
className,
title,
onChange: (e) => handleOnChange(e.target.checked),
checked: !!value,
disabled,
...extraProps
});
case "radio": return /* @__PURE__ */ React.createElement(FormControl, {
key: masterKey,
className,
title,
component: "fieldset",
disabled,
...extraProps
}, /* @__PURE__ */ React.createElement(RadioGroup, {
value,
onChange: (e) => handleOnChange(e.target.value)
}, values.map((v) => /* @__PURE__ */ React.createElement(FormControlLabel, {
key: v.name,
disabled,
value: v.name,
control: /* @__PURE__ */ React.createElement(Radio, null),
name: v.name,
label: v.label
}))));
}
/**
* TODO: Provide either (1) examples or (2) alternate exports that support `inputType`
* "date", "datetime-local", and "time", with components from `@mui/x-date-pickers`
* (`<DatePicker />`, `<DateTimePicker />`, and `<TimePicker />`, respecitively).
*/
if (inputType === "bigint") return /* @__PURE__ */ React.createElement(TextField, {
key: masterKey,
variant: "standard",
"data-testid": testID,
type: inputTypeCoerced,
placeholder: placeHolderText,
value: `${value}`,
title,
className,
disabled,
label: showInputLabels ? title : void 0,
onChange: (e) => bigIntValueHandler(e.target.value),
...extraProps
});
return /* @__PURE__ */ React.createElement(TextField, {
key: masterKey,
variant: "standard",
type: inputTypeCoerced,
value,
title,
disabled,
className,
placeholder: placeHolderText,
label: showInputLabels ? title : void 0,
onChange: (e) => handleOnChange(parseNumber(e.target.value, { parseNumbers: parseNumberMethod })),
...extraProps
});
};
//#endregion
//#region src/utils.tsx
// v8 ignore next
const defaultToOptionsOptions = {
ListSubheader: () => null,
MenuItem: () => /* @__PURE__ */ React.createElement(React.Fragment, null)
};
const toOptions = (arr = [], { ListSubheader, MenuItem } = defaultToOptionsOptions) => {
if (isOptionGroupArray(arr)) {
const optArray = [];
for (const og of arr) optArray.push(/* @__PURE__ */ React.createElement(ListSubheader, { key: og.label }, og.label), ...og.options.map((opt) => /* @__PURE__ */ React.createElement(MenuItem, {
key: opt.name,
value: opt.name
}, opt.label)));
return optArray;
}
/* v8 ignore else -- @preserve */
if (Array.isArray(arr)) return arr.map((opt) => /* @__PURE__ */ React.createElement(MenuItem, {
key: opt.name,
value: opt.name
}, opt.label));
/* v8 ignore next -- @preserve */
return null;
};
//#endregion
//#region src/MaterialValueSelector.tsx
/**
* @group Components
*/
const MaterialValueSelector = ({ className, handleOnChange, options, value, disabled, title, multiple, listsAsArrays, testID, rule, ruleGroup, rules, level, path, context, validation, operator, field, fieldData, schema, muiComponents: muiComponentsProp, showInputLabels: silProp, defaultValue: _defaultValue, ...otherProps }) => {
const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp;
const { onChange, val } = useValueSelector({
handleOnChange,
listsAsArrays,
multiple,
value
});
const muiSelectChangeHandler = React.useCallback(({ target: { value: v } }) => {
onChange(v);
}, [onChange]);
const key = muiComponents ? "mui" : "no-mui";
if (!muiComponents) {
const VS = ValueSelector;
return /* @__PURE__ */ React.createElement(VS, {
key,
className,
handleOnChange,
options,
value,
disabled,
title,
multiple,
listsAsArrays,
testID,
rule,
ruleGroup,
rules,
level,
path,
context,
validation,
operator,
field,
fieldData,
schema
});
}
const { FormControl, InputLabel, ListSubheader, MenuItem, Select, showInputLabels: silCtx } = muiComponents;
const showInputLabels = silProp || silCtx;
return /* @__PURE__ */ React.createElement(FormControl, {
key,
variant: "standard",
className,
title,
disabled
}, showInputLabels && /* @__PURE__ */ React.createElement(InputLabel, null, title), /* @__PURE__ */ React.createElement(Select, {
value: val,
onChange: muiSelectChangeHandler,
multiple,
disabled,
label: showInputLabels ? title : void 0,
...otherProps
}, toOptions(options, {
ListSubheader,
MenuItem
})));
};
//#endregion
//#region src/translations.tsx
const CloseIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.removeRule.label;
const { CloseIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(CloseIcon, null);
};
const ContentCopyIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.cloneRule.label;
const { ContentCopyIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(ContentCopyIcon, null);
};
const LockIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.lockRuleDisabled.label;
const { LockIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(LockIcon, null);
};
const LockOpenIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.lockRule.label;
const { LockOpenIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(LockOpenIcon, null);
};
const ShiftDownIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.shiftActionDown.label;
const { KeyboardArrowDownIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(KeyboardArrowDownIcon, null);
};
const ShiftUpIconWrapper = () => {
const muiComponents = React.useContext(RQBMaterialContext);
if (!muiComponents) return defaultTranslations.shiftActionUp.label;
const { KeyboardArrowUpIcon } = muiComponents;
return /* @__PURE__ */ React.createElement(KeyboardArrowUpIcon, null);
};
const materialTranslations = {
removeGroup: { label: /* @__PURE__ */ React.createElement(CloseIconWrapper, null) },
removeRule: { label: /* @__PURE__ */ React.createElement(CloseIconWrapper, null) },
cloneRule: { label: /* @__PURE__ */ React.createElement(ContentCopyIconWrapper, null) },
cloneRuleGroup: { label: /* @__PURE__ */ React.createElement(ContentCopyIconWrapper, null) },
lockGroup: { label: /* @__PURE__ */ React.createElement(LockOpenIconWrapper, null) },
lockRule: { label: /* @__PURE__ */ React.createElement(LockOpenIconWrapper, null) },
lockGroupDisabled: { label: /* @__PURE__ */ React.createElement(LockIconWrapper, null) },
lockRuleDisabled: { label: /* @__PURE__ */ React.createElement(LockIconWrapper, null) },
shiftActionDown: { label: /* @__PURE__ */ React.createElement(ShiftDownIconWrapper, null) },
shiftActionUp: { label: /* @__PURE__ */ React.createElement(ShiftUpIconWrapper, null) }
};
//#endregion
//#region src/useMuiComponents.ts
const defaultMuiComponents = {
DragIndicator,
Button,
Checkbox,
CloseIcon,
ContentCopyIcon,
FormControl,
FormControlLabel,
InputLabel,
KeyboardArrowDownIcon,
KeyboardArrowUpIcon,
ListSubheader,
LockIcon,
LockOpenIcon,
MenuItem,
Radio,
RadioGroup,
Select,
Switch,
TextareaAutosize,
TextField
};
/**
* @group Hooks
*/
const useMuiComponents = (preloadedComponents) => {
const muiComponentsFromContext = useContext(RQBMaterialContext);
return useMemo(() => preloadedComponents && muiComponentsFromContext ? {
...defaultMuiComponents,
...muiComponentsFromContext,
...preloadedComponents
} : preloadedComponents ? {
...defaultMuiComponents,
...preloadedComponents
} : muiComponentsFromContext ? {
...defaultMuiComponents,
...muiComponentsFromContext
} : defaultMuiComponents, [muiComponentsFromContext, preloadedComponents]);
};
//#endregion
//#region src/index.tsx
/**
* @group Props
*/
const materialControlElements = {
actionElement: MaterialActionElement,
dragHandle: MaterialDragHandle,
notToggle: MaterialNotToggle,
shiftActions: MaterialShiftActions,
valueEditor: MaterialValueEditor,
valueSelector: MaterialValueSelector
};
const MaterialContextProvider = getCompatContextProvider({
controlElements: materialControlElements,
translations: materialTranslations
});
/**
* @group Components
*/
const QueryBuilderMaterial = ({ muiComponents: muiComponentsProp, showInputLabels, ...props }) => {
const muiComponents = useMuiComponents(muiComponentsProp);
const ctxValue = useMemo(() => ({
...muiComponents,
...muiComponentsProp,
showInputLabels
}), [
muiComponents,
muiComponentsProp,
showInputLabels
]);
return /* @__PURE__ */ React.createElement(RQBMaterialContext.Provider, { value: ctxValue }, /* @__PURE__ */ React.createElement(MaterialContextProvider, props));
};
//#endregion
export { MaterialActionElement, MaterialDragHandle, MaterialNotToggle, MaterialShiftActions, MaterialValueEditor, MaterialValueSelector, QueryBuilderMaterial, RQBMaterialContext, defaultMuiComponents, materialControlElements, materialTranslations, useMuiComponents };
//# sourceMappingURL=react-querybuilder_material.mjs.map