Files
enviPy-bayer/static/js/ketcher2/script/ui/component/input.jsx
2025-06-23 20:13:54 +02:00

233 lines
6.3 KiB
JavaScript

/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
import { h, Component } from 'preact';
/** @jsx h */
function GenericInput({ value, onChange, type = "text", ...props }) {
return (
<input type={type} value={value} onInput={onChange} {...props} />
);
}
GenericInput.val = function (ev, schema) {
const input = ev.target;
const isNumber = (input.type === 'number' || input.type === 'range') ||
(schema && (schema.type === 'number' || schema.type === 'integer'));
const value = isNumber ? input.value.replace(/,/g, '.') : input.value;
return (isNumber && !isNaN(value - 0)) ? value - 0 : value;
};
function TextArea({ value, onChange, ...props }) {
return (
<textarea value={value} onInput={onChange} {...props}/>
);
}
TextArea.val = (ev) => ev.target.value;
function CheckBox({ value, onChange, ...props }) {
return (
<input type="checkbox" checked={value} onClick={onChange} {...props} />
);
}
CheckBox.val = function (ev) {
ev.stopPropagation();
return !!ev.target.checked;
};
function Select({ schema, value, selected, onSelect, ...props }) {
return (
<select onChange={onSelect} {...props}>
{
enumSchema(schema, (title, val) => (
<option selected={selected(val, value)}
value={typeof val !== 'object' && val}>
{title}
</option>
))
}
</select>
);
}
Select.val = function (ev, schema) {
const select = ev.target;
if (!select.multiple)
return enumSchema(schema, select.selectedIndex);
return [].reduce.call(select.options, function (res, o, i) {
return !o.selected ? res :
[enumSchema(schema, i), ...res];
}, []);
};
function FieldSet({ schema, value, selected, onSelect, type = "radio", ...props }) {
return (
<fieldset onClick={onSelect} className="radio">
{
enumSchema(schema, (title, val) => (
<label>
<input type={type} checked={selected(val, value)}
value={typeof val !== 'object' && val}
{...props}/>
{title}
</label>
))
}
</fieldset>
);
}
FieldSet.val = function (ev, schema) {
const input = ev.target;
if (ev.target.tagName !== 'INPUT') {
ev.stopPropagation();
return undefined;
}
// Hm.. looks like premature optimization
// should we inline this?
const fieldset = input.parentNode.parentNode;
const res = [].reduce.call(fieldset.querySelectorAll('input'),
function (res, inp, i) {
return !inp.checked ? res :
[enumSchema(schema, i), ...res];
}, []);
return input.type === 'radio' ? res[0] : res;
};
function enumSchema(schema, cbOrIndex) {
const isTypeValue = Array.isArray(schema);
if (!isTypeValue && schema.items)
schema = schema.items;
if (typeof cbOrIndex === 'function') {
return (isTypeValue ? schema : schema.enum).map((item, i) => {
const title = isTypeValue ? item.title :
schema.enumNames && schema.enumNames[i];
return cbOrIndex(title !== undefined ? title : item,
item.value !== undefined ? item.value : item);
});
}
if (!isTypeValue)
return schema.enum[cbOrIndex];
const res = schema[cbOrIndex];
return res.value !== undefined ? res.value : res;
}
function inputCtrl(component, schema, onChange) {
let props = {};
if (schema) {
// TODO: infer maxLength, min, max, step, etc
if (schema.type === 'number' || schema.type === 'integer')
props = { type: 'text' };
}
return {
onChange: function (ev) {
const val = !component.val ? ev :
component.val(ev, schema);
onChange(val);
},
...props
};
}
function singleSelectCtrl(component, schema, onChange) {
return {
selected: (testVal, value) => (value === testVal),
onSelect: function (ev, value) {
const val = !component.val ? ev :
component.val(ev, schema);
if (val !== undefined)
onChange(val);
}
};
}
function multipleSelectCtrl(component, schema, onChange) {
return {
multiple: true,
selected: (testVal, values) =>
(values && values.indexOf(testVal) >= 0),
onSelect: function (ev, values) {
if (component.val) {
let val = component.val(ev, schema);
if (val !== undefined)
onChange(val);
} else {
const i = values ? values.indexOf(ev) : -1;
if (i < 0)
onChange(values ? [ev, ...values] : [ev]);
else
onChange([...values.slice(0, i),
...values.slice(i + 1)]);
}
}
};
}
function ctrlMap(component, { schema, multiple, onChange }) {
if (!schema || !schema.enum && !schema.items && !Array.isArray(schema) || schema.type === 'string')
return inputCtrl(component, schema, onChange);
if (multiple || schema.type === 'array')
return multipleSelectCtrl(component, schema, onChange);
return singleSelectCtrl(component, schema, onChange);
}
function componentMap({ schema, type, multiple }) {
if (!schema || !schema.enum && !schema.items && !Array.isArray(schema)) {
if (type === 'checkbox' || schema && schema.type === 'boolean')
return CheckBox;
return (type === 'textarea') ? TextArea : GenericInput;
}
if (multiple || schema.type === 'array')
return (type === 'checkbox') ? FieldSet : Select;
return (type === 'radio') ? FieldSet : Select;
}
function shallowCompare(a, b) {
for (let i in a) if (!(i in b)) return true;
for (let i in b) if (a[i] !== b[i]) { return true; }
return false;
}
export default class Input extends Component {
constructor({ component, ...props }) {
super(props);
this.component = component || componentMap(props);
this.ctrl = ctrlMap(this.component, props);
}
shouldComponentUpdate({ children, onChange, ...nextProps }) {
var { children, onChange, ...oldProps } = this.props;
return shallowCompare(oldProps, nextProps);
}
render() {
var { children, onChange, ...props } = this.props;
return h(this.component, { ...this.ctrl, ...props });
}
}