@etsoo/materialui
Version:
TypeScript Material-UI Implementation
279 lines (278 loc) • 11.6 kB
JavaScript
;
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 }) })] }))] }));
}