UNPKG

@etsoo/materialui

Version:

TypeScript Material-UI Implementation

279 lines (278 loc) 11.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchBar = SearchBar; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importDefault(require("react")); const MoreHoriz_1 = __importDefault(require("@mui/icons-material/MoreHoriz")); const shared_1 = require("@etsoo/shared"); const react_2 = require("@etsoo/react"); const Labels_1 = require("./app/Labels"); const Stack_1 = __importDefault(require("@mui/material/Stack")); const IconButton_1 = __importDefault(require("@mui/material/IconButton")); const Button_1 = __importDefault(require("@mui/material/Button")); const Drawer_1 = __importDefault(require("@mui/material/Drawer")); const Toolbar_1 = __importDefault(require("@mui/material/Toolbar")); // Cached width attribute name const cachedWidthName = "data-cached-width"; // Reset form const resetForm = (form) => { for (const input of form.elements) { // Ignore disabled inputs if ("disabled" in input && input.disabled) continue; // All non hidden inputs if (input instanceof HTMLInputElement) { // Ignore hidden and data-reset=false input var reset = input.dataset.reset; if (input.type === "hidden" || reset === "false") continue; // Ignore readOnly without data-reset=true inputs if (!input.readOnly || reset === "true") { react_2.ReactUtils.triggerChange(input, "", true); } continue; } // All selects if (input instanceof HTMLSelectElement) { if (input.options.length > 0 && input.options[0].value === "") { input.selectedIndex = 0; } else { input.selectedIndex = -1; } continue; } } // Trigger reset event const resetEvent = new Event("reset"); form.dispatchEvent(resetEvent); }; // Disable inputs avoid auto trigger change events for them const setChildState = (child, enabled) => { const inputs = child.getElementsByTagName("input"); for (const input of inputs) { input.disabled = !enabled; } }; function checkFormEvent(event) { if (event.nativeEvent.cancelable && !event.nativeEvent.composed) return true; if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) { const minChars = shared_1.NumberUtils.parse(event.target.dataset.minChars); if (minChars != null && minChars > 0) { const len = event.target.value.length; if (len > 0 && len < minChars) { return true; } } } return false; } /** * Search bar * Make sure its container's width is fixed, not fluid, like "<Container fixed><SearchBar ../></Container>" * @param props Props * @returns Component */ function SearchBar(props) { // Destruct const { className, fields, onSubmit, itemGap = 6, itemWidth = 160, top, width } = props; // Labels const labels = Labels_1.Labels.CommonPage; // Menu index const [index, updateIndex] = react_1.default.useState(); // Drawer open / close const [open, updateOpen] = react_1.default.useState(false); // State const state = react_1.default.useRef({ hasMore: true, lastMaxWidth: 9999 }).current; // Watch container const { dimensions } = (0, react_2.useDimensions)(1, (target, rect) => { // Same logic from resetButtonRefe); if (rect.width === state.lastMaxWidth || (!state.hasMore && rect.width > state.lastMaxWidth)) return false; // Len const len = target.children.length; for (let i = 0; i < len; i++) { var classList = target.children[i].classList; classList.remove("showChild"); } }, 0); // Show or hide element const setElementVisible = (element, visible) => { element.classList.remove(visible ? "hiddenChild" : "showChild"); element.classList.add(visible ? "showChild" : "hiddenChild"); }; // Reset button ref const resetButtonRef = (instance) => { // Reset button const resetButton = instance; if (resetButton == null) return; // First const [_, container, containerRect] = dimensions[0]; if (container == null || containerRect == null || containerRect.width < 10) return; // Container width let maxWidth = containerRect.width; if (maxWidth === state.lastMaxWidth || (!state.hasMore && maxWidth > state.lastMaxWidth)) { return; } state.lastMaxWidth = maxWidth; // More button const buttonMore = resetButton.previousElementSibling; // Cached button width const cachedButtonWidth = container.getAttribute(cachedWidthName); if (cachedButtonWidth) { maxWidth -= Number.parseFloat(cachedButtonWidth); } else { // Reset button rect const resetButtonRect = resetButton.getBoundingClientRect(); // More button rect const buttonMoreRect = buttonMore.getBoundingClientRect(); // Total const totalButtonWidth = resetButtonRect.width + buttonMoreRect.width + 3 * itemGap; // Cache container.setAttribute(cachedWidthName, totalButtonWidth.toString()); maxWidth -= totalButtonWidth; } // Children const children = container.children; // Len const len = children.length; // Other elements const others = len - 2; let hasMore = false; let newIndex = others; for (let c = 0; c < others; c++) { const child = children[c]; const cachedWidth = child.getAttribute(cachedWidthName); let childWidth; if (cachedWidth) { childWidth = Number.parseFloat(cachedWidth); } else { const childD = child.getBoundingClientRect(); childWidth = childD.width + itemGap; child.setAttribute(cachedWidthName, childWidth.toString()); } // No gap here, child width includes the gap if (childWidth <= maxWidth) { maxWidth -= childWidth; setChildState(child, true); setElementVisible(child, true); } else { setChildState(child, false); setElementVisible(child, false); if (!hasMore) { // Make sure coming logic to the block maxWidth = 0; // Keep the current index newIndex = c; // Indicates more hasMore = true; } } } // Show or hide more button state.hasMore = hasMore; setElementVisible(buttonMore, hasMore); setElementVisible(resetButton, true); // Update menu start index updateIndex(newIndex); }; // More items creator const moreItems = []; if (index != null) { for (let i = index; i < fields.length; i++) { moreItems.push((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: fields[i] }, i)); } } const hasMoreItems = moreItems.length > 0; // Handle main form const handleForm = (event) => { if (checkFormEvent(event)) return; if (state.form == null) state.form = event.currentTarget; delayed.call(); }; // Handle more button click const handleMore = () => { updateOpen(!open); }; // More form change const moreFormChange = (event) => { if (checkFormEvent(event)) return; if (state.moreForm == null) state.moreForm = event.currentTarget; delayed.call(); }; // Submit at once const handleSubmitInstant = (reset = false) => { // Prepare data const data = new FormData(state.form); if (state.moreForm != null) { shared_1.DomUtils.mergeFormData(data, new FormData(state.moreForm)); } onSubmit(data, reset); }; const delayed = (0, react_2.useDelayedExecutor)(handleSubmitInstant, 480); // Reset const handleReset = () => { // Clear form values if (state.form != null) resetForm(state.form); if (state.moreForm != null) resetForm(state.moreForm); // Resubmit handleSubmitInstant(true); }; react_1.default.useEffect(() => { // Delayed way delayed.call(100); return () => { delayed.clear(); }; }, [className]); // Layout return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)("form", { id: "SearchBarForm", className: className, onChange: handleForm, ref: (form) => { if (form) state.form = form; }, children: (0, jsx_runtime_1.jsxs)(Stack_1.default, { ref: dimensions[0][0], className: "SearchBarContainer", justifyContent: "center", alignItems: "center", direction: "row", spacing: `${itemGap}px`, width: width, overflow: "hidden", paddingTop: "6px", sx: { "& > :not(style)": { flexBasis: "auto", flexGrow: 0, flexShrink: 0, maxWidth: `${itemWidth}px`, visibility: "hidden" }, "& > .hiddenChild": { display: "none" }, "& > .showChild": { display: "block", visibility: "visible" } }, children: [fields.map((item, index) => ((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: item }, index))), (0, jsx_runtime_1.jsx)(IconButton_1.default, { title: labels.more, size: "medium", sx: { height: "40px" }, onClick: handleMore, children: (0, jsx_runtime_1.jsx)(MoreHoriz_1.default, {}) }), (0, jsx_runtime_1.jsx)(Button_1.default, { variant: "contained", size: "medium", ref: resetButtonRef, onClick: handleReset, children: labels.reset })] }) }), hasMoreItems && ((0, jsx_runtime_1.jsxs)(Drawer_1.default, { anchor: "right", sx: { minWidth: "180px", paddingTop: typeof top === "number" ? `${top}px` : undefined }, ModalProps: { keepMounted: true }, open: open, onClose: () => updateOpen(false), children: [top === true && (0, jsx_runtime_1.jsx)(Toolbar_1.default, {}), (0, jsx_runtime_1.jsx)("form", { onChange: moreFormChange, ref: (form) => { if (form) state.moreForm = form; }, children: (0, jsx_runtime_1.jsx)(Stack_1.default, { direction: "column", alignItems: "stretch", spacing: 2, padding: 2, sx: { "& > :not(style)": { minWidth: "100px" } }, children: moreItems }) })] }))] })); }