Repository: vazco/uniforms (ts)

Issue:
Ability to specify disabled items in SelectField
Currently, there's no way of disabling particular items in SelectField.

One should write a custom field in order to achieve something like this:

![image](https://user-images.githubusercontent.com/18295488/83311801-974ffe80-a210-11ea-91d1-5136e7508ba4.png)

It would be nice to have such an ability by default.

f.e.

```
<SelectField
   name="selectField"
   disableItem={(item) => ...}
/>
```
or

```
<SelectField
   name="selectField"
   disableItems={["Item 2", "Item 4"]}
/>
```

Thanks!

Pull request description:
Disable <SelectField /> items based on predicate (closes #736)
This PR adds the ability to disable certain items based on predicate (similar to `.filter(item => condition(item))`) that can be specified as a prop.

Example: _Disable all odd values_
```javascript
<SelectField
  name="x"
  disableItem={value => value % 2}
/>
```

Patch:
diff --git a/docs/api-fields.md b/docs/api-fields.md
index 011a3b40..041945e3 100644
--- a/docs/api-fields.md
+++ b/docs/api-fields.md
@@ -530,6 +530,7 @@ import { RadioField } from 'uniforms-unstyled';
 |  `allowedValues`  |                                               Array of allowed values. By default, those are extracted from your schema.                                                |                  All                   |
 |   `appearance`    |                 Field appearance. Set to "toggle" to appear as a Material Toggle or to "checkbox" (or leave it undefined) to use a Checkbox appearance.                 |                material                |
 |   `checkboxes`    |                                       Turn on checkbox/radio mode. It's always true in multiple (i.e. fieldType === Array) mode.                                        |                  All                   |
+|   `disableItem`   |                                                           Disable items (options) based on a given predicate.                                                           |                  All                   |
 |      `extra`      |                                Extra feedback text. In the antd theme, this renders additional help text below any validation messages.                                 |                  antd                  |
 |      `help`       |                                                           Help text. _Some description would be great, huh?_                                                            |      antd, bootstrap3, bootstrap4      |
 |  `helpClassName`  |                                                      Help block className. _Some description would be great, huh?_                                                      |         bootstrap3, bootstrap4         |
@@ -556,6 +557,7 @@ import { SelectField } from 'uniforms-unstyled';
 <SelectField
   allowedValues={[value1, value2 /* ... */]}
   checkboxes
+  disableItem={value => value % 2}
   extra="Extra Feedback or Help"
   help="Need help?"
   helpClassName="a b c"
diff --git a/packages/uniforms-antd/src/SelectField.tsx b/packages/uniforms-antd/src/SelectField.tsx
index f51c0174..2dc2f611 100644
--- a/packages/uniforms-antd/src/SelectField.tsx
+++ b/packages/uniforms-antd/src/SelectField.tsx
@@ -16,6 +16,7 @@ type CheckboxesProps = FieldProps<
   {
     allowedValues?: CheckboxValueType[];
     checkboxes: true;
+    disableItem?(value: CheckboxValueType): boolean;
     inputRef?: Ref<CheckboxGroup | typeof RadioGroup>;
     required?: boolean;
     transform?(value: CheckboxValueType): string;
@@ -28,6 +29,7 @@ type SelectProps = FieldProps<
   {
     allowedValues?: string[];
     checkboxes?: false;
+    disableItem?(value: CheckboxValueType): boolean;
     inputRef?: Ref<SelectAntD<string | string[]>>;
     required?: boolean;
     transform?(value: string): string;
@@ -51,6 +53,7 @@ function Select(props: SelectFieldProps) {
             : (event: any) => props.onChange(event.target.value)
         }
         options={props.allowedValues!.map(value => ({
+          disabled: props.disableItem?.(value),
           label: props.transform ? props.transform(value) : value,
           value,
         }))}
@@ -77,7 +80,11 @@ function Select(props: SelectFieldProps) {
         {...filterDOMProps(props)}
       >
         {props.allowedValues!.map(value => (
-          <SelectAntD.Option key={value} value={value}>
+          <SelectAntD.Option
+            disabled={props.disableItem?.(value)}
+            key={value}
+            value={value}
+          >
             {props.transform ? props.transform(value) : value}
           </SelectAntD.Option>
         ))}
diff --git a/packages/uniforms-antd/src/wrapField.tsx b/packages/uniforms-antd/src/wrapField.tsx
index 328dd473..d6f1d8df 100644
--- a/packages/uniforms-antd/src/wrapField.tsx
+++ b/packages/uniforms-antd/src/wrapField.tsx
@@ -70,11 +70,20 @@ export default function wrapField(
 
 declare module 'uniforms' {
   interface FilterDOMProps {
+    checkboxes: never;
     colon: never;
+    disableItem: never;
     labelCol: never;
     wrapperCol: never;
     wrapperStyle: never;
   }
 }
 
-filterDOMProps.register('colon', 'labelCol', 'wrapperCol', 'wrapperStyle');
+filterDOMProps.register(
+  'checkboxes',
+  'colon',
+  'disableItem',
+  'labelCol',
+  'wrapperCol',
+  'wrapperStyle',
+);
diff --git a/packages/uniforms-bootstrap3/src/SelectField.tsx b/packages/uniforms-bootstrap3/src/SelectField.tsx
index cab9dccc..9d547800 100644
--- a/packages/uniforms-bootstrap3/src/SelectField.tsx
+++ b/packages/uniforms-bootstrap3/src/SelectField.tsx
@@ -17,6 +17,7 @@ export type SelectFieldProps = HTMLFieldProps<
   {
     allowedValues?: string[];
     checkboxes?: boolean;
+    disableItem?: (value: string) => boolean;
     inline?: boolean;
     inputClassName?: string;
     inputRef?: Ref<HTMLSelectElement>;
@@ -28,6 +29,7 @@ function Select({
   allowedValues,
   checkboxes,
   className,
+  disableItem,
   disabled,
   error,
   errorMessage,
@@ -62,7 +64,7 @@ function Select({
               checked={
                 fieldType === Array ? value?.includes(item) : value === item
               }
-              disabled={disabled}
+              disabled={disableItem?.(item) || disabled}
               id={`${id}-${escape(item)}`}
               name={name}
               onChange={() =>
@@ -95,7 +97,7 @@ function Select({
         )}
 
         {allowedValues?.map(value => (
-          <option key={value} value={value}>
+          <option disabled={disableItem?.(value)} key={value} value={value}>
             {transform ? transform(value) : value}
           </option>
         ))}
diff --git a/packages/uniforms-bootstrap4/src/SelectField.tsx b/packages/uniforms-bootstrap4/src/SelectField.tsx
index e46871df..92f00dfc 100644
--- a/packages/uniforms-bootstrap4/src/SelectField.tsx
+++ b/packages/uniforms-bootstrap4/src/SelectField.tsx
@@ -17,6 +17,7 @@ export type SelectFieldProps = HTMLFieldProps<
   {
     allowedValues?: string[];
     checkboxes?: boolean;
+    disableItem?: (value: string) => boolean;
     inline?: boolean;
     inputClassName?: string;
     inputRef?: Ref<HTMLSelectElement>;
@@ -27,6 +28,7 @@ export type SelectFieldProps = HTMLFieldProps<
 function Select({
   allowedValues,
   checkboxes,
+  disableItem,
   disabled,
   error,
   fieldType,
@@ -66,7 +68,7 @@ function Select({
               checked={
                 fieldType === Array ? value?.includes(item) : value === item
               }
-              disabled={disabled}
+              disabled={disableItem?.(item) || disabled}
               id={`${id}-${escape(item)}`}
               name={name}
               onChange={() =>
@@ -99,7 +101,7 @@ function Select({
         )}
 
         {allowedValues?.map(value => (
-          <option key={value} value={value}>
+          <option disabled={disableItem?.(value)} key={value} value={value}>
             {transform ? transform(value) : value}
           </option>
         ))}
diff --git a/packages/uniforms-material/src/BoolField.tsx b/packages/uniforms-material/src/BoolField.tsx
index 14c9afd5..8eff18be 100644
--- a/packages/uniforms-material/src/BoolField.tsx
+++ b/packages/uniforms-material/src/BoolField.tsx
@@ -14,7 +14,6 @@ export type BoolFieldProps = FieldProps<
   {
     appearance?: 'checkbox' | 'switch';
     helperText?: string;
-    inputRef?: Ref<HTMLButtonElement>;
     legend?: string;
     transform?(label: string): string;
   }
@@ -34,7 +33,7 @@ function Bool(props: BoolFieldProps) {
   } = props;
   const SelectionControl =
     appearance === 'checkbox' || appearance === undefined ? Checkbox : Switch;
-  const filteredProps = wrapField._filterDOMProps(filterDOMProps(props));
+  const filteredProps = filterDOMProps(props);
 
   return wrapField(
     { fullWidth: true, margin: 'dense', ...props, component: 'fieldset' },
@@ -52,7 +51,7 @@ function Bool(props: BoolFieldProps) {
             onChange={event =>
               !disabled && onChange && onChange(event.target.checked)
             }
-            ref={inputRef}
+            ref={inputRef as Ref<HTMLButtonElement>}
             value={name}
             {...filteredProps}
           />
diff --git a/packages/uniforms-material/src/RadioField.tsx b/packages/uniforms-material/src/RadioField.tsx
index f3db1598..035fd40e 100644
--- a/packages/uniforms-material/src/RadioField.tsx
+++ b/packages/uniforms-material/src/RadioField.tsx
@@ -35,7 +35,7 @@ function Radio({
   value,
   ...props
 }: RadioFieldProps) {
-  const filteredProps = wrapField._filterDOMProps(filterDOMProps(props));
+  const filteredProps = filterDOMProps(props);
 
   return wrapField(
     { ...props, disabled, component: 'fieldset', fullWidth, margin },
diff --git a/packages/uniforms-material/src/SelectField.tsx b/packages/uniforms-material/src/SelectField.tsx
index 756c10b1..d4143783 100644
--- a/packages/uniforms-material/src/SelectField.tsx
+++ b/packages/uniforms-material/src/SelectField.tsx
@@ -15,17 +15,24 @@ import { FieldProps, connectField, filterDOMProps } from 'uniforms';
 
 import wrapField from './wrapField';
 
+type SelectFieldCommonProps = {
+  allowedValues?: string[];
+  appearance?: 'checkbox' | 'switch';
+  checkboxes?: boolean;
+  disableItem?(value: string): boolean;
+  inputRef?: Ref<HTMLButtonElement>;
+  native?: boolean;
+  required?: boolean;
+  transform?(value: string): string;
+};
+
 type CheckboxesProps = FieldProps<
   string | string[],
   CheckboxProps | SwitchProps,
   {
-    allowedValues?: string[];
-    appearance?: 'checkbox' | 'switch';
     checkboxes: true;
-    inputRef?: Ref<HTMLButtonElement>;
     legend?: string;
-    required?: boolean;
-    transform?(value: string): string;
+    native: false;
   }
 >;
 
@@ -33,18 +40,14 @@ type SelectProps = FieldProps<
   string | string[],
   TextFieldProps & MaterialSelectProps,
   {
-    allowedValues?: string[];
-    appearance?: 'checkbox' | 'switch';
     checkboxes?: false;
-    inputRef?: Ref<HTMLButtonElement>;
     labelProps?: object;
-    required?: boolean;
-    transform?(value: string): string;
     textFieldProps: TextFieldProps;
   }
 >;
 
-export type SelectFieldProps = CheckboxesProps | SelectProps;
+export type SelectFieldProps = (CheckboxesProps | SelectProps) &
+  SelectFieldCommonProps;
 
 const base64 =
   typeof btoa !== 'undefined'
@@ -68,11 +71,11 @@ function Select(props: SelectFieldProps) {
       onChange,
       transform,
     } = props;
+
     const appearance = props.appearance ?? 'checkbox';
-    const filteredProps = wrapField._filterDOMProps(
-      filterDOMProps(omit(props, ['id'])),
-    );
     const SelectionControl = appearance === 'checkbox' ? Checkbox : Switch;
+    const filteredProps = filterDOMProps(omit(props, ['id']));
+
     const children =
       fieldType !== Array ? (
         <RadioGroup
@@ -87,6 +90,7 @@ function Select(props: SelectFieldProps) {
               control={
                 <Radio id={`${id}-${escape(item)}`} {...filteredProps} />
               }
+              disabled={props.disableItem?.(item) || disabled}
               key={item}
               label={transform ? transform(item) : item}
               value={item}
@@ -108,6 +112,7 @@ function Select(props: SelectFieldProps) {
                   {...filteredProps}
                 />
               }
+              disabled={props.disableItem?.(item) || disabled}
               key={item}
               label={transform ? transform(item) : item}
             />
@@ -191,7 +196,7 @@ function Select(props: SelectFieldProps) {
       )}
 
       {allowedValues!.map(value => (
-        <Item key={value} value={value}>
+        <Item disabled={props.disableItem?.(value)} key={value} value={value}>
           {transform ? transform(value) : value}
         </Item>
       ))}
diff --git a/packages/uniforms-material/src/wrapField.tsx b/packages/uniforms-material/src/wrapField.tsx
index 7c752c71..52a80d73 100644
--- a/packages/uniforms-material/src/wrapField.tsx
+++ b/packages/uniforms-material/src/wrapField.tsx
@@ -1,13 +1,9 @@
 import FormControl from '@material-ui/core/FormControl';
 import FormHelperText from '@material-ui/core/FormHelperText';
-import omit from 'lodash/omit';
 import React, { ReactNode, createElement } from 'react';
+import { filterDOMProps } from 'uniforms';
 
-const _filterDOMPropsList = ['fullWidth', 'helperText', 'margin', 'variant'];
-const _filterDOMProps = (props: Record<string, any>) =>
-  omit(props, _filterDOMPropsList);
-
-function wrap(
+export default function wrap(
   {
     component,
     disabled,
@@ -41,4 +37,22 @@ function wrap(
   );
 }
 
-export default Object.assign(wrap, { _filterDOMProps, _filterDOMPropsList });
+declare module 'uniforms' {
+  interface FilterDOMProps {
+    checkboxes: never;
+    disableItem: never;
+    fullWidth: never;
+    helperText: never;
+    margin: never;
+    variant: never;
+  }
+}
+
+filterDOMProps.register(
+  'checkboxes',
+  'disableItem',
+  'fullWidth',
+  'helperText',
+  'margin',
+  'variant',
+);
diff --git a/packages/uniforms-semantic/src/SelectField.tsx b/packages/uniforms-semantic/src/SelectField.tsx
index 8ee3a473..af5a89cb 100644
--- a/packages/uniforms-semantic/src/SelectField.tsx
+++ b/packages/uniforms-semantic/src/SelectField.tsx
@@ -15,6 +15,7 @@ export type SelectFieldProps = HTMLFieldProps<
   {
     allowedValues?: string[];
     checkboxes?: boolean;
+    disableItem?: (value: string) => boolean;
     inputRef?: Ref<HTMLSelectElement>;
     transform?(value: string): string;
   }
@@ -24,6 +25,7 @@ function Select({
   allowedValues,
   checkboxes,
   className,
+  disableItem,
   disabled,
   error,
   errorMessage,
@@ -56,7 +58,7 @@ function Select({
                 checked={
                   fieldType === Array ? value?.includes(item) : value === item
                 }
-                disabled={disabled}
+                disabled={disableItem?.(item) || disabled}
                 id={`${id}-${escape(item)}`}
                 name={name}
                 onChange={() =>
@@ -90,7 +92,7 @@ function Select({
           )}
 
           {allowedValues?.map(value => (
-            <option key={value} value={value}>
+            <option disabled={disableItem?.(value)} key={value} value={value}>
               {transform ? transform(value) : value}
             </option>
           ))}
diff --git a/packages/uniforms-unstyled/src/SelectField.tsx b/packages/uniforms-unstyled/src/SelectField.tsx
index 55e0d415..b1bcd2c6 100644
--- a/packages/uniforms-unstyled/src/SelectField.tsx
+++ b/packages/uniforms-unstyled/src/SelectField.tsx
@@ -12,6 +12,7 @@ export type SelectFieldProps = HTMLFieldProps<
   {
     allowedValues?: string[];
     checkboxes?: boolean;
+    disableItem?(value: string): boolean;
     inputRef?: Ref<HTMLSelectElement>;
     transform?(value: string): string;
   }
@@ -29,6 +30,7 @@ function Select({
   onChange,
   placeholder,
   required,
+  disableItem,
   transform,
   value,
   ...props
@@ -43,7 +45,7 @@ function Select({
               checked={
                 fieldType === Array ? value!.includes(item) : value === item
               }
-              disabled={disabled}
+              disabled={disableItem?.(item) ?? disabled}
               id={`${id}-${escape(item)}`}
               name={name}
               onChange={() => {
@@ -77,7 +79,7 @@ function Select({
           )}
 
           {allowedValues!.map(value => (
-            <option key={value} value={value}>
+            <option disabled={disableItem?.(value)} key={value} value={value}>
               {transform ? transform(value) : value}
             </option>
           ))}


Test patch:
diff --git a/packages/uniforms-antd/__tests__/SelectField.tsx b/packages/uniforms-antd/__tests__/SelectField.tsx
index d71554a8..ba3303df 100644
--- a/packages/uniforms-antd/__tests__/SelectField.tsx
+++ b/packages/uniforms-antd/__tests__/SelectField.tsx
@@ -261,6 +261,25 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - disabled items (options) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField name="x" disableItem={value => value === allowedValues[0]} />
+  );
+  const wrapper = mount(
+    element,
+    createContext({
+      x: { type: Array },
+      'x.$': { type: String, allowedValues },
+    }),
+  );
+
+  expect(wrapper.find(Select).prop('children')).toHaveLength(2);
+  expect(wrapper.find(Select).prop('children')[0].props.disabled).toBe(true);
+  expect(wrapper.find(Select).prop('children')[1].props.disabled).toBe(false);
+});
+
 test('<SelectField checkboxes> - renders a set of checkboxes', () => {
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
@@ -491,3 +510,26 @@ test('<SelectField checkboxes> - works with special characters', () => {
     createContext({ x: { type: String, allowedValues: ['ă', 'ș'] } }),
   );
 });
+
+test('<SelectField checkboxes> - disabled items (checkboxes) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({
+      x: { type: Array },
+      'x.$': { type: String, allowedValues },
+    }),
+  );
+
+  expect(wrapper.find('input')).toHaveLength(2);
+  expect(wrapper.find('input').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('input').at(1).prop('disabled')).toBe(false);
+});
diff --git a/packages/uniforms-bootstrap3/__tests__/SelectField.tsx b/packages/uniforms-bootstrap3/__tests__/SelectField.tsx
index fca96b5d..052ba5c7 100644
--- a/packages/uniforms-bootstrap3/__tests__/SelectField.tsx
+++ b/packages/uniforms-bootstrap3/__tests__/SelectField.tsx
@@ -266,6 +266,22 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - disabled items (options) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField name="x" disableItem={value => value === allowedValues[0]} />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('select')).toHaveLength(1);
+  expect(wrapper.find('option[value="a"]').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('option[value="b"]').at(0).prop('disabled')).toBe(false);
+});
+
 test('<SelectField checkboxes> - renders a set of checkboxes', () => {
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
@@ -505,3 +521,23 @@ test('<SelectField checkboxes> - works with special characters', () => {
     createContext({ x: { type: String, allowedValues: ['ă', 'ș'] } }),
   );
 });
+
+test('<SelectField checkboxes> - disabled items (checkboxes) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('input')).toHaveLength(2);
+  expect(wrapper.find('input').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('input').at(1).prop('disabled')).toBe(false);
+});
diff --git a/packages/uniforms-bootstrap4/__tests__/SelectField.tsx b/packages/uniforms-bootstrap4/__tests__/SelectField.tsx
index 9e7bcbd6..047701a8 100644
--- a/packages/uniforms-bootstrap4/__tests__/SelectField.tsx
+++ b/packages/uniforms-bootstrap4/__tests__/SelectField.tsx
@@ -309,6 +309,22 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - disabled items (options) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField name="x" disableItem={value => value === allowedValues[0]} />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('select')).toHaveLength(1);
+  expect(wrapper.find('option[value="a"]').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('option[value="b"]').at(0).prop('disabled')).toBe(false);
+});
+
 test('<SelectField checkboxes> - renders a set of checkboxes', () => {
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
@@ -548,3 +564,23 @@ test('<SelectField checkboxes> - works with special characters', () => {
     createContext({ x: { type: String, allowedValues: ['ă', 'ș'] } }),
   );
 });
+
+test('<SelectField checkboxes> - disabled items (checkboxes) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('input')).toHaveLength(2);
+  expect(wrapper.find('input').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('input').at(1).prop('disabled')).toBe(false);
+});
diff --git a/packages/uniforms-material/__tests__/SelectField.tsx b/packages/uniforms-material/__tests__/SelectField.tsx
index 6ea0ca60..3a4cbf7d 100644
--- a/packages/uniforms-material/__tests__/SelectField.tsx
+++ b/packages/uniforms-material/__tests__/SelectField.tsx
@@ -287,6 +287,29 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - disabled items (options) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      native
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({
+      x: { type: Array },
+      'x.$': { type: String, allowedValues },
+    }),
+  );
+
+  expect(wrapper.find(Select)).toHaveLength(1);
+  expect(wrapper.find('option').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('option').at(1).prop('disabled')).toBe(false);
+});
+
 test('<SelectField> - renders with correct classnames', () => {
   const wrapper = mount(
     // @ts-ignore Fix SelectFieldProps.
@@ -622,3 +645,27 @@ test('<SelectField checkboxes> - works with special characters', () => {
     createContext({ x: { type: String, allowedValues: ['ă', 'ș'] } }),
   );
 });
+
+test('<SelectField checkboxes> - disabled items (checkboxes) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      appearance="checkbox"
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({
+      x: { type: Array },
+      'x.$': { type: String, allowedValues },
+    }),
+  );
+
+  expect(wrapper.find(Checkbox)).toHaveLength(2);
+  expect(wrapper.find(FormControlLabel).at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find(FormControlLabel).at(1).prop('disabled')).toBe(false);
+});
diff --git a/packages/uniforms-semantic/__tests__/SelectField.tsx b/packages/uniforms-semantic/__tests__/SelectField.tsx
index d436027c..5cde3df1 100644
--- a/packages/uniforms-semantic/__tests__/SelectField.tsx
+++ b/packages/uniforms-semantic/__tests__/SelectField.tsx
@@ -266,6 +266,22 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - disabled items (options) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField name="x" disableItem={value => value === allowedValues[0]} />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('select')).toHaveLength(1);
+  expect(wrapper.find('option[value="a"]').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('option[value="b"]').at(0).prop('disabled')).toBe(false);
+});
+
 test('<SelectField checkboxes> - renders a set of checkboxes', () => {
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
@@ -526,3 +542,23 @@ test('<SelectField> - renders correct error text (showInlineError=false)', () =>
 
   expect(wrapper.find('.ui.red.label').length).toBe(0);
 });
+
+test('<SelectField checkboxes> - disabled items (checkboxes) based on predicate', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('input')).toHaveLength(2);
+  expect(wrapper.find('input').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('input').at(1).prop('disabled')).toBe(false);
+});
diff --git a/packages/uniforms-unstyled/__tests__/SelectField.tsx b/packages/uniforms-unstyled/__tests__/SelectField.tsx
index d9dfbe6e..ec606de7 100644
--- a/packages/uniforms-unstyled/__tests__/SelectField.tsx
+++ b/packages/uniforms-unstyled/__tests__/SelectField.tsx
@@ -494,3 +494,23 @@ test('<SelectField checkboxes> - works with special characters', () => {
     createContext({ x: { type: String, allowedValues: ['ă', 'ș'] } }),
   );
 });
+
+test('<SelectField checkboxes> - renders a set of checkboxes with per-item props', () => {
+  const allowedValues = ['a', 'b'];
+
+  const element = (
+    <SelectField
+      checkboxes
+      name="x"
+      disableItem={value => value === allowedValues[0]}
+    />
+  );
+  const wrapper = mount(
+    element,
+    createContext({ x: { type: String, allowedValues } }),
+  );
+
+  expect(wrapper.find('input')).toHaveLength(2);
+  expect(wrapper.find('input').at(0).prop('disabled')).toBe(true);
+  expect(wrapper.find('input').at(1).prop('disabled')).toBe(false);
+});