diff --git a/docs/examples/auto-adjust-dropdown.tsx b/docs/examples/auto-adjust-dropdown.tsx index 3921b032b..911f86657 100644 --- a/docs/examples/auto-adjust-dropdown.tsx +++ b/docs/examples/auto-adjust-dropdown.tsx @@ -8,7 +8,8 @@ class Test extends React.Component { value: '3', }; - onChange = e => { + onChange = (e) => { + console.log(`e`, e); let value; if (e && e.target) { ({ value } = e.target); @@ -35,48 +36,29 @@ class Test extends React.Component {
- + + {' '}
- + +
@@ -88,32 +70,16 @@ class Test extends React.Component { }} >
- + +
- + +
diff --git a/docs/examples/combobox.tsx b/docs/examples/combobox.tsx index eab533466..669133ee4 100644 --- a/docs/examples/combobox.tsx +++ b/docs/examples/combobox.tsx @@ -25,7 +25,7 @@ class Combobox extends React.Component { }); }; - onKeyDown = e => { + onKeyDown = (e) => { const { value } = this.state; if (e.keyCode === 13) { console.log('onEnter', value); @@ -40,7 +40,7 @@ class Combobox extends React.Component { console.log('onSearch:', text); }; - onAsyncChange = value => { + onAsyncChange = (value) => { window.clearTimeout(this.timeoutId); this.setState({ @@ -81,6 +81,7 @@ class Combobox extends React.Component {

{ - {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ( + {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => ( diff --git a/docs/examples/custom-icon.tsx b/docs/examples/custom-icon.tsx index 294844551..af0057cc6 100644 --- a/docs/examples/custom-icon.tsx +++ b/docs/examples/custom-icon.tsx @@ -90,6 +90,7 @@ class CustomIconComponent extends React.Component {

{
console.log('focus')} onBlur={() => console.log('blur')} - dropdownRender={menu => ( + dropdownRender={(menu) => (
{ diff --git a/docs/examples/email.tsx b/docs/examples/email.tsx index e57599e6a..df064bd76 100644 --- a/docs/examples/email.tsx +++ b/docs/examples/email.tsx @@ -8,18 +8,18 @@ class Test extends React.Component { options: [], }; - onSelect = value => { + onSelect = (value) => { console.log('onSelect', value); }; - onChange = value => { + onChange = (value) => { console.log('onChange', value); let options: React.ReactNode = []; if (value) { if (value.indexOf('@') >= 0) { options = ; } else { - options = ['gmail.com', 'yahoo.com', 'outlook.com'].map(domain => { + options = ['gmail.com', 'yahoo.com', 'outlook.com'].map((domain) => { const email = `${value}@${domain}`; return ; }); @@ -34,6 +34,7 @@ class Test extends React.Component { const { options } = this.state; return ( ( >

without filter sort

node.parentNode; + getPopupContainer = (node) => node.parentNode; - setVisible = open => { + setVisible = (open) => { this.setState({ open, }); @@ -50,6 +50,7 @@ class Test extends React.Component {
console.log('focus')} - onBlur={v => console.log('blur', v)} + onBlur={(v) => console.log('blur', v)} tokenSeparators={[' ', ',']} > {children} diff --git a/docs/examples/mul-suggest.tsx b/docs/examples/mul-suggest.tsx index b9e1e9461..0f9945557 100644 --- a/docs/examples/mul-suggest.tsx +++ b/docs/examples/mul-suggest.tsx @@ -11,15 +11,15 @@ class Test extends React.Component { value: [], }; - onChange = value => { + onChange = (value) => { console.log('onChange ', value); this.setState({ value, }); }; - fetchData = value => { - fetch(value, data => { + fetchData = (value) => { + fetch(value, (data) => { this.setState({ data, }); @@ -28,7 +28,7 @@ class Test extends React.Component { render() { const { data, value } = this.state; - const options = data.map(d => ( + const options = data.map((d) => ( @@ -39,6 +39,7 @@ class Test extends React.Component {
{ + onChange = (value) => { console.log('onChange', value); this.setState({ value }); }; @@ -30,6 +30,7 @@ class Test extends React.Component {

multiple readonly default selected item

console.log('focus')} - onBlur={v => console.log('blur', v)} + onBlur={(v) => console.log('blur', v)} tokenSeparators={[' ', ',']} > {children} diff --git a/docs/examples/optgroup.tsx b/docs/examples/optgroup.tsx index cc32968a1..0a80ef3ef 100644 --- a/docs/examples/optgroup.tsx +++ b/docs/examples/optgroup.tsx @@ -12,6 +12,7 @@ const Test = () => (

Select OptGroup

Select optionLabelProp

{ + onChange = (e) => { let value; if (e && e.target) { ({ value } = e.target); @@ -29,7 +29,7 @@ class Test extends React.Component { }); }; - onBlur = v => { + onBlur = (v) => { console.log('onBlur', v); }; @@ -37,7 +37,7 @@ class Test extends React.Component { console.log('onFocus'); }; - onSearch = val => { + onSearch = (val) => { console.log('Search:', val); }; @@ -51,7 +51,7 @@ class Test extends React.Component {
{ + onMouseDown={(e) => { e.preventDefault(); }} > @@ -62,6 +62,7 @@ class Test extends React.Component {
{ return ( {
{ + updateOptions = (value) => { const options = [value, value + value, value + value + value]; this.setState({ options, @@ -43,6 +43,7 @@ class Test extends React.Component { label: {label}
- {options.map(opt => ( + {options.map((opt) => ( ))} diff --git a/jest.config.js b/jest.config.js index 66a2c8753..8d019a98f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ module.exports = { snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], + testEnvironment: 'jest-environment-jsdom-fourteen' // https://stackoverflow.com/a/62439088/2971795 }; diff --git a/package.json b/package.json index 810e90ed3..430ebf066 100644 --- a/package.json +++ b/package.json @@ -54,16 +54,17 @@ }, "devDependencies": { "@testing-library/react": "12", - "@types/enzyme": "^3.10.9", + "@types/enzyme": "^3.10.12", "@types/jest": "^26.0.24", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.3", "cross-env": "^7.0.0", "dumi": "^1.1.32", - "enzyme": "^3.3.0", + "enzyme": "^3.11.0", "enzyme-to-json": "^3.4.0", "eslint": "^7.1.0", "father": "^2.13.2", + "jest-environment-jsdom-fourteen": "^1.0.1", "jsonp": "^0.2.1", "np": "^7.5.0", "prettier": "^2.7.1", diff --git a/src/BaseSelect.tsx b/src/BaseSelect.tsx index 8907176be..5649e33fa 100644 --- a/src/BaseSelect.tsx +++ b/src/BaseSelect.tsx @@ -75,6 +75,7 @@ export interface BaseSelectPrivateProps { id: string; prefixCls: string; omitDomProps?: string[]; + // name?: string; // >>> Value displayValues: DisplayValueType[]; @@ -121,6 +122,7 @@ export type BaseSelectPropsWithoutPrivate = Omit React.ReactElement; @@ -205,6 +207,7 @@ export function isMultiple(mode: Mode) { const BaseSelect = React.forwardRef((props: BaseSelectProps, ref: React.Ref) => { const { id, + name, prefixCls, className, showSearch, @@ -753,6 +756,7 @@ const BaseSelect = React.forwardRef((props: BaseSelectProps, ref: React.Ref void; @@ -159,6 +161,7 @@ const Select = React.forwardRef( const { id, mode, + name, prefixCls = 'rc-select', backfill, fieldNames, @@ -195,7 +198,7 @@ const Select = React.forwardRef( ...restProps } = props; - + const selectNameProp = name; const mergedId = useId(id); const multiple = isMultiple(mode); const childrenAsData = !!(!options && children); @@ -417,6 +420,10 @@ const Select = React.forwardRef( const labeledValues = convert2LabelValues(values); setInternalValue(labeledValues); + if (selectNameProp) { + generateHiddenInputs(selectNameProp, values); + } + if ( onChange && // Trigger event only when value changed @@ -481,6 +488,12 @@ const Select = React.forwardRef( // Used for OptionList selection const onInternalSelect = useRefFunc((val, info) => { + console.log(`-----------calling onInternalSelect -------------`); + console.log(val); + if (selectNameProp) { + generateHiddenInputs(selectNameProp, val); + } + let cloneValues: (RawValueType | DisplayValueType)[]; // Single mode always trigger select only with option list @@ -508,6 +521,10 @@ const Select = React.forwardRef( // ======================= Display Change ======================= // BaseSelect display values change const onDisplayValuesChange: BaseSelectProps['onDisplayValuesChange'] = (nextValues, info) => { + console.log(`-------onDisplayValuesChange------`, nextValues); + if (selectNameProp) { + generateHiddenInputs(selectNameProp, nextValues); + } triggerChange(nextValues); if (info.type === 'remove' || info.type === 'clear') { @@ -546,6 +563,7 @@ const Select = React.forwardRef( }; const onInternalSearchSplit: BaseSelectProps['onSearchSplit'] = (words) => { + console.log(`---------onInternalSearchSplit--------`); let patchValues: RawValueType[] = words; if (mode !== 'tags') { @@ -558,6 +576,7 @@ const Select = React.forwardRef( } const newRawValues = Array.from(new Set([...rawValues, ...patchValues])); + console.log(`newRawValues`, newRawValues); triggerChange(newRawValues); newRawValues.forEach((newRawValue) => { triggerSelect(newRawValue, true); @@ -612,6 +631,7 @@ const Select = React.forwardRef( {...restProps} // >>> MISC id={mergedId} + name={selectNameProp} prefixCls={prefixCls} ref={ref} omitDomProps={OMIT_DOM_PROPS} diff --git a/src/Selector/Input.tsx b/src/Selector/Input.tsx index 498608c84..7f1502f61 100644 --- a/src/Selector/Input.tsx +++ b/src/Selector/Input.tsx @@ -8,6 +8,7 @@ type InputRef = HTMLInputElement | HTMLTextAreaElement; interface InputProps { prefixCls: string; id: string; + name?: string; inputElement: React.ReactElement; disabled: boolean; autoFocus: boolean; @@ -36,6 +37,7 @@ interface InputProps { const Input: React.RefForwardingComponent = ( { prefixCls, + name, id, inputElement, disabled, @@ -78,7 +80,7 @@ const Input: React.RefForwardingComponent = ( inputNode = React.cloneElement(inputNode, { type: 'search', ...originProps, - + name, // Override over origin props id, ref: composeRef(ref, originRef as any), @@ -117,6 +119,7 @@ const Input: React.RefForwardingComponent = ( } }, onChange: (event: React.ChangeEvent) => { + console.log(`2222-----------`); onChange(event); if (onOriginChange) { onOriginChange(event); diff --git a/src/Selector/SingleSelector.tsx b/src/Selector/SingleSelector.tsx index 1be8ccddb..8dbca121d 100644 --- a/src/Selector/SingleSelector.tsx +++ b/src/Selector/SingleSelector.tsx @@ -12,6 +12,7 @@ const SingleSelector: React.FC = (props) => { const { inputElement, prefixCls, + name, id, inputRef, disabled, @@ -78,6 +79,7 @@ const SingleSelector: React.FC = (props) => { <> ; placeholder?: React.ReactNode; disabled?: boolean; @@ -55,6 +55,7 @@ export interface SelectorProps { prefixCls: string; showSearch?: boolean; open: boolean; + name?: string; /** Display in the Selector value, it's not same as `value` prop */ values: DisplayValueType[]; mode: Mode; @@ -106,6 +107,7 @@ const Selector: React.RefForwardingComponent = mode, showSearch, tokenWithEnter, + name, onSearch, onSearchSubmit, @@ -238,6 +240,7 @@ const Selector: React.RefForwardingComponent = // ================= Inner Selector ================== const sharedProps = { inputRef, + name, onInputKeyDown: onInternalInputKeyDown, onInputMouseDown: onInternalInputMouseDown, onInputChange, diff --git a/src/utils/generateHiddenInputs.ts b/src/utils/generateHiddenInputs.ts new file mode 100644 index 000000000..cabb99c30 --- /dev/null +++ b/src/utils/generateHiddenInputs.ts @@ -0,0 +1,62 @@ +import type { DraftValueType } from '@/Select'; + +/** + * @param inputNameAttributeVal - name value passed from component + * @param prefixCls + * + * @description + * This function generates hidden `` element(s) after a value is Selected from the component. For morebackground context & history, see discussions below: + * - https://github.com/tailwindlabs/headlessui/pull/1214 + * - https://github.com/react-component/select/issues/799 + */ +export const generateHiddenInputs = ( + inputNameAttributeVal: string, + value: string | string[] | DraftValueType[] | DraftValueType | any, + prefixCls = 'rc-select', +) => { + // We will append the hidden inputs below this element. The hidden inputs act as value container(s) for the selected option values. + const rcSelectInputElement = document.querySelector( + `.${prefixCls} input[class^="${prefixCls}"]`, + ) as HTMLInputElement; + if (!rcSelectInputElement) return; + + /** + * Part 1: + * Before creating any new hidden inputElements, first check if we have any existing inputs that were generated + * from previously calling `onSelect`, `onDeselect`, `onChange` etc. + */ + const hiddenInputCSSSelector = `.${prefixCls} input.hidden-input`; + const hiddenInputElements = Array.from(document.querySelectorAll(hiddenInputCSSSelector)); + hiddenInputElements.forEach((hiddenInputElement) => hiddenInputElement.remove()); + + // debugger; + /** + * Part 2: + * Generate new hidden Elements and append them in the .rc-select container. + */ + if (Array.isArray(value)) { + value.forEach((val) => { + const _val = val?.value ? val.value : val; + const hiddenInputElement = document.createElement('input'); + hiddenInputElement.setAttribute('type', 'hidden'); + hiddenInputElement.setAttribute('name', inputNameAttributeVal); + hiddenInputElement.setAttribute('value', _val); + hiddenInputElement.setAttribute('class', 'hidden-input'); + rcSelectInputElement.insertAdjacentElement(`afterend`, hiddenInputElement); + }); + } else { + // Select value is single value + const _val = value?.value ? value.value : value; + const hiddenInputElement = document.createElement('input'); + hiddenInputElement.setAttribute('type', 'hidden'); + hiddenInputElement.setAttribute('name', inputNameAttributeVal); + hiddenInputElement.setAttribute('value', _val); + hiddenInputElement.setAttribute('class', 'hidden-input'); + rcSelectInputElement.insertAdjacentElement(`afterend`, hiddenInputElement); + } +}; diff --git a/tests/Combobox.test.tsx b/tests/Combobox.test.tsx index 954c4765b..9892a751e 100644 --- a/tests/Combobox.test.tsx +++ b/tests/Combobox.test.tsx @@ -527,4 +527,33 @@ describe('Select.Combobox', () => { selectItem(wrapper); expect(wrapper.find('.rc-select-item-option-selected').length).toBe(0); }); + + // describe('HTML5 Compatability', () => { + // it('should be possible to submit a
with a value', () => { + // const submits = jest.fn(); + + // const wrapper = mount( + // { + // event.preventDefault(); + // submits([...new FormData(event.currentTarget).entries()]); + // }} + // > + // + // + //
, + // ); + // toggleOpen(wrapper); + // selectItem(wrapper); + // expect(wrapper.find('input').props().value).toEqual('1'); + + // const form = wrapper.find('form').first(); + // form.simulate('submit'); + // expect(submits).lastCalledWith([['my-test-select', '1']]); + // }); + // }); }); diff --git a/tests/Multiple.test.tsx b/tests/Multiple.test.tsx index ea143a831..00ef68652 100644 --- a/tests/Multiple.test.tsx +++ b/tests/Multiple.test.tsx @@ -601,4 +601,128 @@ describe('Select.Multiple', () => { errSpy.mockRestore(); }); }); + + // describe('HTML5 Compatability', () => { + // it('should be possible to submit a
with a value', () => { + // const submits = jest.fn(); + + // const wrapper = mount( + // { + // event.preventDefault(); + // submits([...new FormData(event.currentTarget).entries()]); + // }} + // > + // + // + //
, + // ); + // console.log(wrapper.debug()); + // toggleOpen(wrapper); + // selectItem(wrapper, 0); + + // const form = wrapper.find('form').first(); + // form.simulate('submit'); + // expect(submits).lastCalledWith([['my-select', 'bamboo']]); + // }); + // it('should be possible to submit a
with a multiple values selected', () => { + // const submits = jest.fn(); + + // const wrapper = mount( + // { + // event.preventDefault(); + // submits([...new FormData(event.currentTarget).entries()]); + // }} + // > + // + // + //
, + // ); + // console.log(wrapper.debug()); + // toggleOpen(wrapper); + // selectItem(wrapper, 0); + // selectItem(wrapper, 1); + + // const form = wrapper.find('form').first(); + // form.simulate('submit'); + // expect(submits).lastCalledWith([ + // ['my-select', 'bamboo'], + // ['my-select', 'little'], + // ]); + // }); + // }); + describe('HTML5 Compatability', () => { + it(`should be possible to submit a
with a value (mode='multiple')`, () => { + const submits = jest.fn(); + + const wrapper = mount( + { + event.preventDefault(); + console.log(`-caling submot------------------------`); + const value = [...new FormData(event.currentTarget).entries()]; + console.log(`value???`, value); + submits([...new FormData(event.currentTarget).entries()]); + }} + > + + +
, + ); + toggleOpen(wrapper); + selectItem(wrapper, 0); + console.log(wrapper.debug()); + + const form = wrapper.find('form').first(); + form.simulate('submit'); + expect(submits).lastCalledWith([['my-test-select', '1']]); + }); + + // it('should be possible to submit a
with multiple values', () => { + // const submits = jest.fn(); + + // const wrapper = mount( + // { + // event.preventDefault(); + // console.log(`-caling submot------------------------`); + // const value = [...new FormData(event.currentTarget).entries()]; + // console.log(`value???`, value); + // submits([...new FormData(event.currentTarget).entries()]); + // }} + // > + // + // + //
, + // ); + // toggleOpen(wrapper); + // selectItem(wrapper, 0); + // selectItem(wrapper, 1); + // console.log(wrapper.debug()); + + // const form = wrapper.find('form').first(); + // form.simulate('submit'); + // expect(submits).lastCalledWith([ + // ['my-test-select', '1'], + // ['my-test-select', '2'], + // ]); + // }); + }); }); diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 2c8ca434b..9a56ec806 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1914,4 +1914,34 @@ describe('Select.Basic', () => { expect(wrapper.find('div.rc-select-item').prop('data-test')).toEqual('good'); expect(wrapper.find('div.rc-select-item').prop('aria-label')).toEqual('well'); }); + + describe('HTML5 Compatability', () => { + it('should be possible to submit a
with a value', () => { + const submits = jest.fn(); + + const wrapper = mount( + { + event.preventDefault(); + submits([...new FormData(event.currentTarget).entries()]); + }} + > + + +
, + ); + console.log(wrapper.debug()); + toggleOpen(wrapper); + selectItem(wrapper); + expect(wrapper.find('input').props().value).toEqual('1'); + + const form = wrapper.find('form').first(); + form.simulate('submit'); + expect(submits).lastCalledWith([['my-select', '1']]); + }); + }); });