UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

131 lines (130 loc) 6.44 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useComboboxInputConstrained = void 0; const react_1 = __importDefault(require("react")); const common_1 = require("@workday/canvas-kit-react/common"); const useComboboxModel_1 = require("./useComboboxModel"); function onlyDefined(input) { return !!input; } /** * A constrained combobox input can only offer values that are part of the provided list of `items`. * The default is an unconstrained. A constrained input should have both a form input that is hidden * from the user as well as a user input that will be visible to the user. This hook is in charge of * keeping the inputs and the model in sync with each other and working with a browser's * autocomplete, form libraries and the model. */ exports.useComboboxInputConstrained = (0, common_1.createElemPropsHook)(useComboboxModel_1.useComboboxModel)((model, ref, { disabled, value: reactValue, onChange, name, } = {}) => { // The user element is what the user sees const { elementRef: userElementRef, localRef: userLocalRef } = (0, common_1.useLocalRef)(model.state.targetRef); // The form element is what is seen in `FormData` during for submission to the server const { elementRef: formElementRef, localRef: formLocalRef } = (0, common_1.useLocalRef)(ref); // Create React refs so we can get the current value inside an Effect without using those values // as part of the dependency array. const modelNavigationRef = react_1.default.useRef(model.navigation); modelNavigationRef.current = model.navigation; const modelStateRef = react_1.default.useRef(model.state); modelStateRef.current = model.state; // Watch the `value` prop passed from React props and update the model accordingly react_1.default.useLayoutEffect(() => { if (formLocalRef.current && typeof reactValue === 'string') { if (reactValue !== formLocalRef.current.value) { model.events.setSelectedIds(reactValue ? reactValue.split(', ') : []); } } }, [reactValue, formLocalRef, model.events]); // useImperativeHandle allows us to modify the `ref` before it is sent to the application, // but after it is defined. We can add value watches, and redirect methods here. react_1.default.useImperativeHandle(formElementRef, () => { if (formLocalRef.current && userLocalRef.current) { const formElement = formLocalRef.current; const userElement = userLocalRef.current; // Hook into the DOM `value` property of the form input element and update the model // accordingly Object.defineProperty(formElement, 'value', { get() { var _a, _b; const value = (_b = (_a = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(formElement), 'value')) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(formElement); return value; }, set(value) { if (formElement && value !== (modelStateRef.current.selectedIds === 'all' ? [] : modelStateRef.current.selectedIds).join(', ')) { model.events.setSelectedIds(value ? value.split(', ') : []); } }, }); // forward calls to `.focus()` and `.blur()` to the user input // https://github.com/testing-library/user-event/pull/1252 doesn't allow writable, but // allows reconfiguration, so we use `Object.defineProperty`. Object.defineProperty(formElement, 'focus', { configurable: true, writable: true, value: (options) => userElement.focus(options), }); Object.defineProperty(formElement, 'blur', { configurable: true, writable: true, value: () => userElement.blur(), }); return formElement; } return formLocalRef.current; }, [formLocalRef, userLocalRef, model.events]); // sync model selection state with inputs react_1.default.useLayoutEffect(() => { if (userLocalRef.current) { const userValue = model.state.items.length === 0 ? '' : (model.state.selectedIds === 'all' ? [] : model.state.selectedIds .map(id => modelNavigationRef.current.getItem(id, { state: modelStateRef.current })) .filter(onlyDefined) .map(item => item.textValue)).join(', '); if (userValue !== userLocalRef.current.value) { (0, common_1.dispatchInputEvent)(userLocalRef.current, userValue); } } if (formLocalRef.current) { const formValue = (model.state.selectedIds === 'all' ? [] : model.state.selectedIds).join(', '); if (formValue !== formLocalRef.current.value) { (0, common_1.dispatchInputEvent)(formLocalRef.current, formValue); } } }, [model.state.selectedIds, model.state.items, formLocalRef, userLocalRef]); // The props here will go to the user input. return { ref: userElementRef, form: '', value: null, onChange: (event) => { var _a; (_a = model.onFilterChange) === null || _a === void 0 ? void 0 : _a.call(model, event); return null; // Prevent further `onChange` callbacks from firing }, name: null, disabled, /** * These props should be spread onto the form input. */ formInputProps: { disabled, tabIndex: -1, 'aria-hidden': true, ref: formElementRef, onChange: (event) => { var _a; onChange === null || onChange === void 0 ? void 0 : onChange(event); (_a = model.onChange) === null || _a === void 0 ? void 0 : _a.call(model, event); }, name, }, }; });