ish-ui 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +39 -0
- package/.gitattributes +3 -0
- package/.github/workflows/release-package.yml +33 -0
- package/.prettierrc +5 -0
- package/README.md +80 -0
- package/assets/fonts/inter/Inter-Italic.var.woff2 +0 -0
- package/assets/fonts/inter/Inter-Roman.var.woff2 +0 -0
- package/assets/fonts/inter/LICENSE.txt +92 -0
- package/assets/fonts/inter/index.d.ts +10 -0
- package/assets/fonts/inter/inter.scss +17 -0
- package/package.json +108 -0
- package/src/actions/FieldProcessing.ts +21 -0
- package/src/actions/index.ts +10 -0
- package/src/appBar/AppBarHelpMenu.tsx +139 -0
- package/src/appBar/index.ts +9 -0
- package/src/buttons/AddButton.tsx +30 -0
- package/src/buttons/TimetableButton.tsx +19 -0
- package/src/buttons/index.ts +10 -0
- package/src/colorPicker/ColorPicker.tsx +223 -0
- package/src/colorPicker/index.ts +9 -0
- package/src/dialog/BrowserWarning.tsx +24 -0
- package/src/dialog/confirm/ConfirmBase.tsx +59 -0
- package/src/dialog/index.ts +11 -0
- package/src/dialog/message/Message.tsx +140 -0
- package/src/documents/DocumentIconsChooser.tsx +69 -0
- package/src/documents/index.ts +10 -0
- package/src/documents/utils/index.tsx +71 -0
- package/src/dynamicSizeList/DynamicSizeList.tsx +208 -0
- package/src/dynamicSizeList/index.ts +9 -0
- package/src/fieldMessage/ErrorMessage.tsx +28 -0
- package/src/fieldMessage/WarningMessage.tsx +28 -0
- package/src/fieldMessage/index.ts +11 -0
- package/src/fieldMessage/styles.ts +21 -0
- package/src/fileUploader/FileUploaderDialog.tsx +158 -0
- package/src/fileUploader/index.ts +9 -0
- package/src/formFields/CheckboxField.tsx +90 -0
- package/src/formFields/CodeEditorField.tsx +103 -0
- package/src/formFields/ColoredCheckBox.tsx +74 -0
- package/src/formFields/DateTimeField.tsx +61 -0
- package/src/formFields/EditInPlaceDateTimeField.tsx +285 -0
- package/src/formFields/EditInPlaceDurationField.tsx +79 -0
- package/src/formFields/EditInPlaceField.tsx +131 -0
- package/src/formFields/EditInPlaceFieldBase.tsx +241 -0
- package/src/formFields/EditInPlaceFileField.tsx +67 -0
- package/src/formFields/EditInPlaceMoneyField.tsx +64 -0
- package/src/formFields/EditInPlacePhoneField.tsx +97 -0
- package/src/formFields/EditInPlaceSearchSelect.tsx +562 -0
- package/src/formFields/FieldAdornments.tsx +70 -0
- package/src/formFields/FilePreview.tsx +149 -0
- package/src/formFields/FormRadioButtons.tsx +62 -0
- package/src/formFields/SelectCustomComponents.tsx +228 -0
- package/src/formFields/Switch.tsx +146 -0
- package/src/formFields/TextField.tsx +60 -0
- package/src/formFields/UneditableBase.tsx +101 -0
- package/src/formFields/index.ts +27 -0
- package/src/index.ts +26 -0
- package/src/layout/InfoPill.tsx +36 -0
- package/src/layout/index.ts +10 -0
- package/src/layout/resizable/ResizableWrapper.tsx +87 -0
- package/src/markdownEditor/HtmlEditor.tsx +44 -0
- package/src/markdownEditor/WysiwygEditor.tsx +137 -0
- package/src/markdownEditor/index.ts +12 -0
- package/src/markdownEditor/style.ts +188 -0
- package/src/markdownEditor/utils/index.ts +48 -0
- package/src/model/AppBar.ts +20 -0
- package/src/model/CommomObjects.ts +8 -0
- package/src/model/CommonFunctions.ts +20 -0
- package/src/model/Confirm.ts +27 -0
- package/src/model/Fields.ts +254 -0
- package/src/model/NestedEntity.ts +14 -0
- package/src/model/Theme.ts +42 -0
- package/src/model/index.ts +16 -0
- package/src/styles/GlobalStylesProvider.tsx +520 -0
- package/src/styles/StylesProviderCustom.tsx +8 -0
- package/src/styles/animateStyles.ts +94 -0
- package/src/styles/bootstrap.ts +503 -0
- package/src/styles/hooks.ts +44 -0
- package/src/styles/index.ts +18 -0
- package/src/styles/makeStyles.ts +21 -0
- package/src/styles/mixins/common.ts +40 -0
- package/src/styles/mixins/keyframes.ts +16 -0
- package/src/styles/mixins/prefixer.ts +21 -0
- package/src/tagsInput/AddTagMenu.tsx +74 -0
- package/src/tagsInput/AddTagMenuItem.tsx +50 -0
- package/src/tagsInput/TagInputList.tsx +428 -0
- package/src/tagsInput/index.ts +4 -0
- package/src/themes/appTheme.ts +324 -0
- package/src/themes/index.ts +10 -0
- package/src/themes/ishTheme.ts +401 -0
- package/src/timetable/datePeriodFilter/DayPeriodIcons.tsx +95 -0
- package/src/timetable/datePeriodFilter/index.ts +10 -0
- package/src/timetable/index.ts +11 -0
- package/src/timetable/miniCalendar/CalendarDay.tsx +109 -0
- package/src/timetable/miniCalendar/CalendarHeader.tsx +37 -0
- package/src/timetable/miniCalendar/CalendarWeekPanel.tsx +35 -0
- package/src/timetable/miniCalendar/WeekDay.tsx +49 -0
- package/src/timetable/miniCalendar/index.ts +13 -0
- package/src/utils/DOM/Links.ts +17 -0
- package/src/utils/DOM/getCaretCoordinates.ts +122 -0
- package/src/utils/DOM/getOS.ts +32 -0
- package/src/utils/DOM/index.ts +34 -0
- package/src/utils/dates/datesNormalizing.ts +4 -0
- package/src/utils/dates/format.ts +52 -0
- package/src/utils/dates/formatRelative.ts +22 -0
- package/src/utils/dates/formatString.ts +233 -0
- package/src/utils/dates/formatTimezone.ts +37 -0
- package/src/utils/dates/index.ts +15 -0
- package/src/utils/dates/timetable.ts +46 -0
- package/src/utils/events/index.ts +8 -0
- package/src/utils/fields/index.ts +23 -0
- package/src/utils/formatting/PhoneMasks.ts +320 -0
- package/src/utils/formatting/index.tsx +20 -0
- package/src/utils/hooks/index.ts +19 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/numbers/decimalCalculation.ts +26 -0
- package/src/utils/numbers/index.ts +11 -0
- package/src/utils/numbers/numbersNormalizing.ts +52 -0
- package/src/utils/stubs/index.tsx +12 -0
- package/src/utils/tags/index.ts +53 -0
- package/tools/webpack/webpack.aliases.js +13 -0
- package/tools/webpack/webpack.config.dev.js +29 -0
- package/tools/webpack/webpack.config.prod.js +36 -0
- package/tools/webpack/webpack.helpers.js +26 -0
- package/tools/webpack/webpack.plugins.js +15 -0
- package/tools/webpack/webpack.rules.js +51 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import withStyles from '@mui/styles/withStyles';
|
|
4
|
+
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
|
|
5
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
6
|
+
import List from '@mui/material/List';
|
|
7
|
+
import AddTagMenuItem from './AddTagMenuItem';
|
|
8
|
+
import { MenuTag, TagLike } from '@src/model';
|
|
9
|
+
|
|
10
|
+
const styles = theme => ({
|
|
11
|
+
listItem: {
|
|
12
|
+
padding: theme.spacing(0, 0.5, 0, 2)
|
|
13
|
+
},
|
|
14
|
+
proceedIcon: {
|
|
15
|
+
marginTop: '2px'
|
|
16
|
+
},
|
|
17
|
+
backContainer: {
|
|
18
|
+
marginLeft: '-6px'
|
|
19
|
+
},
|
|
20
|
+
tagColorDotSmall: {
|
|
21
|
+
width: theme.spacing(2),
|
|
22
|
+
height: theme.spacing(2),
|
|
23
|
+
background: 'red',
|
|
24
|
+
borderRadius: '100%'
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
interface Props<T> {
|
|
29
|
+
classes?: any;
|
|
30
|
+
tags: MenuTag<T>[];
|
|
31
|
+
handleAdd: (tag: MenuTag<T>) => void;
|
|
32
|
+
activeTag: MenuTag<T>;
|
|
33
|
+
setActiveTag: (arg: (MenuTag<T> | ((prevState: MenuTag<T>) => MenuTag<T>))) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AddTagMenu<T extends TagLike>(
|
|
37
|
+
{
|
|
38
|
+
classes,
|
|
39
|
+
tags,
|
|
40
|
+
handleAdd,
|
|
41
|
+
activeTag,
|
|
42
|
+
setActiveTag
|
|
43
|
+
}: Props<T>
|
|
44
|
+
) {
|
|
45
|
+
const handleBack = () => {
|
|
46
|
+
setActiveTag(prev => prev.parent);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<List
|
|
51
|
+
className={classes.rootMenu}
|
|
52
|
+
>
|
|
53
|
+
{activeTag && (
|
|
54
|
+
<MenuItem onClick={handleBack} className={classes.listItem}>
|
|
55
|
+
<span className={clsx('d-flex textSecondaryColor', classes.backContainer)}>
|
|
56
|
+
<KeyboardArrowLeft color="inherit" />
|
|
57
|
+
Back
|
|
58
|
+
</span>
|
|
59
|
+
</MenuItem>
|
|
60
|
+
)}
|
|
61
|
+
{(activeTag ? activeTag.children : tags).map(t => (
|
|
62
|
+
<AddTagMenuItem
|
|
63
|
+
key={t.tagBody.id}
|
|
64
|
+
tag={t}
|
|
65
|
+
classes={classes}
|
|
66
|
+
setActiveTag={setActiveTag}
|
|
67
|
+
handleAdd={handleAdd}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</List>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default withStyles(styles)(AddTagMenu);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright ish group pty ltd 2023.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
|
|
5
|
+
*
|
|
6
|
+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import clsx from 'clsx';
|
|
11
|
+
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
|
12
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
13
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
14
|
+
import { MenuTag, TagLike } from '@src/model';
|
|
15
|
+
|
|
16
|
+
interface Props<T> {
|
|
17
|
+
classes: any;
|
|
18
|
+
setActiveTag: (tag: MenuTag<T>) => void;
|
|
19
|
+
handleAdd: (tag: MenuTag<T>) => void;
|
|
20
|
+
tag: MenuTag<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AddTagMenuItem<T extends TagLike>({ setActiveTag, tag, classes, handleAdd }: Props<T>) {
|
|
24
|
+
const openSubmenu = () => {
|
|
25
|
+
setActiveTag(tag);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const toggleChecked = () => {
|
|
29
|
+
handleAdd(tag);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const hasChildren = Boolean(tag.children.length);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<MenuItem
|
|
36
|
+
classes={{ root: classes.listItem }}
|
|
37
|
+
onClick={hasChildren ? openSubmenu : toggleChecked}
|
|
38
|
+
>
|
|
39
|
+
<div className={clsx(classes.tagColorDotSmall, 'mr-1')} style={{ background: '#' + tag.tagBody.color }} />
|
|
40
|
+
<span className="mr-2 flex-fill">{tag.tagBody.name}</span>
|
|
41
|
+
{hasChildren ? (
|
|
42
|
+
<KeyboardArrowRight className={clsx('d-flex textSecondaryColor', classes.proceedIcon)} />
|
|
43
|
+
) : (
|
|
44
|
+
<Checkbox classes={{ root: 'smallIconButton' }} checked={tag.active} />
|
|
45
|
+
)}
|
|
46
|
+
</MenuItem>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default AddTagMenuItem;
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright ish group pty ltd 2023.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
|
|
5
|
+
*
|
|
6
|
+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, {
|
|
10
|
+
useCallback, useEffect, useMemo, useRef, useState
|
|
11
|
+
} from 'react';
|
|
12
|
+
import {
|
|
13
|
+
InputAdornment, MenuItem, Select
|
|
14
|
+
} from '@mui/material';
|
|
15
|
+
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
|
16
|
+
import createStyles from '@mui/styles/createStyles';
|
|
17
|
+
import withStyles from '@mui/styles/withStyles';
|
|
18
|
+
import Autocomplete from '@mui/material/Autocomplete';
|
|
19
|
+
import clsx from 'clsx';
|
|
20
|
+
import { Edit } from '@mui/icons-material';
|
|
21
|
+
import { selectStyles } from '@src/formFields';
|
|
22
|
+
import AddTagMenu from './AddTagMenu';
|
|
23
|
+
import EditInPlaceFieldBase from '../formFields/EditInPlaceFieldBase';
|
|
24
|
+
import { FieldInputProps, FieldMetaProps, MenuTag, TagLike, TagsFieldProps } from '@src/model';
|
|
25
|
+
import { getAllMenuTags, getHighlightedPartLabel, getMenuTags } from '../utils';
|
|
26
|
+
import { stubComponent } from '@src/utils/stubs';
|
|
27
|
+
import getCaretCoordinates from '@src/utils/DOM/getCaretCoordinates';
|
|
28
|
+
|
|
29
|
+
const styles = theme =>
|
|
30
|
+
createStyles({
|
|
31
|
+
inputEndAdornment: {
|
|
32
|
+
visibility: 'hidden',
|
|
33
|
+
display: 'flex',
|
|
34
|
+
color: theme.palette.primary.main,
|
|
35
|
+
alignItems: 'flex-end',
|
|
36
|
+
alignSelf: 'flex-end',
|
|
37
|
+
marginBottom: '4px',
|
|
38
|
+
opacity: 0.5
|
|
39
|
+
},
|
|
40
|
+
tagColorDotSmall: {
|
|
41
|
+
width: theme.spacing(2),
|
|
42
|
+
minWidth: theme.spacing(2),
|
|
43
|
+
height: theme.spacing(2),
|
|
44
|
+
minHeight: theme.spacing(2),
|
|
45
|
+
background: 'red',
|
|
46
|
+
borderRadius: '100%'
|
|
47
|
+
},
|
|
48
|
+
listbox: {
|
|
49
|
+
whiteSpace: 'break-spaces'
|
|
50
|
+
},
|
|
51
|
+
invalid: {}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const endTagRegex = /#\s*[^\w\d]*$/;
|
|
55
|
+
|
|
56
|
+
const getCurrentInputString = (input, formTagIds: number[] = [], allMenuTags: MenuTag<TagLike>[] = []) => {
|
|
57
|
+
let substr = input;
|
|
58
|
+
|
|
59
|
+
formTagIds?.forEach(id => {
|
|
60
|
+
const tag = allMenuTags.find(t => t.tagBody.id === id);
|
|
61
|
+
if (tag) {
|
|
62
|
+
substr = substr.replace('#' + tag.tagBody.name, '').trim();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (substr) {
|
|
67
|
+
return substr.trim().replace(/#/g, '');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return substr;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getFullTag = (tagId: number, tags: TagLike[]) => {
|
|
74
|
+
let i = 0;
|
|
75
|
+
let result;
|
|
76
|
+
|
|
77
|
+
while (i < tags.length) {
|
|
78
|
+
if (tagId === tags[i].id) {
|
|
79
|
+
return tags[i];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (result) break;
|
|
83
|
+
|
|
84
|
+
if (tags[i].childTags.length) {
|
|
85
|
+
result = getFullTag(tagId, tags[i].childTags);
|
|
86
|
+
if (result) return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
++i;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getInputString = (tagIds: number[], allTags: TagLike[]) => (tagIds?.length && allTags?.length
|
|
94
|
+
? tagIds.reduce((acc, id) => {
|
|
95
|
+
const tag = getFullTag(id, allTags);
|
|
96
|
+
return (tag ? `${acc}#${tag.name} ` : acc);
|
|
97
|
+
}, '')
|
|
98
|
+
: '');
|
|
99
|
+
|
|
100
|
+
function TagInputList<T extends TagLike, IP extends FieldInputProps, MP extends FieldMetaProps>({
|
|
101
|
+
input,
|
|
102
|
+
tags,
|
|
103
|
+
classes,
|
|
104
|
+
placeholder,
|
|
105
|
+
labelAdornment,
|
|
106
|
+
meta,
|
|
107
|
+
label = 'Tags',
|
|
108
|
+
disabled,
|
|
109
|
+
className,
|
|
110
|
+
warning,
|
|
111
|
+
fieldClasses = {}
|
|
112
|
+
}: TagsFieldProps<T, IP, MP>) {
|
|
113
|
+
const [menuIsOpen, setMenuIsOpen] = useState(false);
|
|
114
|
+
const [activeTag, setActiveTag] = useState<MenuTag<TagLike>>(null);
|
|
115
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
116
|
+
const [inputValue, setInputValue] = useState('');
|
|
117
|
+
const [currentInputString, setCurrentInputString] = useState('');
|
|
118
|
+
|
|
119
|
+
const InputValueForRender = useMemo(() => {
|
|
120
|
+
if (!inputValue || !tags || !tags.length) return '';
|
|
121
|
+
|
|
122
|
+
const arrayOfTags = input?.value?.length
|
|
123
|
+
&& input.value.map(id => getFullTag(id, tags)).filter(t => t);
|
|
124
|
+
|
|
125
|
+
if (!arrayOfTags?.length) return '';
|
|
126
|
+
|
|
127
|
+
return arrayOfTags.map((tag: T, index) => (
|
|
128
|
+
<span key={tag.id} className={clsx('centeredFlex', index !== arrayOfTags.length - 1 ? 'pr-1' : '')}>
|
|
129
|
+
<div key={tag.id} className={clsx(classes.tagColorDotSmall, 'mr-0-5')} style={{ background: '#' + tag.color }} />
|
|
130
|
+
<span className="text-nowrap">
|
|
131
|
+
{`#${tag.name} `}
|
|
132
|
+
</span>
|
|
133
|
+
</span>
|
|
134
|
+
));
|
|
135
|
+
}, [tags, input.value, inputValue]);
|
|
136
|
+
|
|
137
|
+
const inputNode = useRef<any>();
|
|
138
|
+
const tagMenuNode = useRef<any>();
|
|
139
|
+
|
|
140
|
+
const menuTags = useMemo(
|
|
141
|
+
() => (tags && input.value ? getMenuTags(tags.filter(t => t.childrenCount > 0), input.value.map(id => getFullTag(id, tags)).filter(t => t)) : []),
|
|
142
|
+
[tags, input.value]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const allMenuTags = useMemo(
|
|
146
|
+
() => getAllMenuTags(menuTags),
|
|
147
|
+
[menuTags]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const synchronizeTags = () => {
|
|
151
|
+
const inputString = getInputString(input.value, tags);
|
|
152
|
+
|
|
153
|
+
if (inputString.trim() === inputValue.replace(endTagRegex, '').trim()) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const updated = [];
|
|
158
|
+
const current = inputValue.split('#').filter(i => i).map(i => i.trim());
|
|
159
|
+
|
|
160
|
+
allMenuTags.forEach(t => {
|
|
161
|
+
if (!t.children.length) {
|
|
162
|
+
const index = current.findIndex(c => c === t.tagBody.name);
|
|
163
|
+
if (index !== -1) {
|
|
164
|
+
const addedTagsMatch = input.value.find(id => getFullTag(id, tags)?.name === t.tagBody.name);
|
|
165
|
+
if (addedTagsMatch && addedTagsMatch !== t.tagBody.id) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
updated.push(t.tagBody.id);
|
|
169
|
+
current.splice(index, 1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
input.onChange(updated);
|
|
175
|
+
setInputValue(getInputString(updated, tags));
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const onTagAdd = (tag: MenuTag<T>) => {
|
|
179
|
+
const updated = [...input.value];
|
|
180
|
+
|
|
181
|
+
const index = updated.findIndex(id => id === tag.tagBody.id);
|
|
182
|
+
|
|
183
|
+
if (index !== -1) {
|
|
184
|
+
updated.splice(index, 1);
|
|
185
|
+
} else {
|
|
186
|
+
updated.push(tag.tagBody.id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
input.onChange(updated);
|
|
190
|
+
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
inputNode?.current?.focus();
|
|
193
|
+
}, 100);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const filterOptions = item => !item.children.length && !input.value.some(id => id === item.tagBody.id) && item.tagBody.name
|
|
197
|
+
.toLowerCase()
|
|
198
|
+
.trim()
|
|
199
|
+
.startsWith(currentInputString.toLowerCase().trim());
|
|
200
|
+
|
|
201
|
+
const filterOptionsInner = options => options.filter(filterOptions);
|
|
202
|
+
|
|
203
|
+
const filteredOptions = allMenuTags.filter(filterOptions);
|
|
204
|
+
|
|
205
|
+
const getOptionText = (option, optionProps, label) => (
|
|
206
|
+
<div {...optionProps}>
|
|
207
|
+
{`${option.path ? option.path + ' /' : ''}`}
|
|
208
|
+
{' '}
|
|
209
|
+
{label}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const getOptionLabel = op => op.path;
|
|
214
|
+
|
|
215
|
+
const renderOption = (optionProps, option) => {
|
|
216
|
+
const label = option?.tagBody?.name;
|
|
217
|
+
const highlightedLabel = getHighlightedPartLabel(label, currentInputString);
|
|
218
|
+
return getOptionText(option, optionProps, highlightedLabel);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const handleInputChange = e => {
|
|
222
|
+
setInputValue(e.target.value);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const edit = () => {
|
|
226
|
+
setIsEditing(true);
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
inputNode?.current?.focus();
|
|
229
|
+
}, 50);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const exit = () => {
|
|
233
|
+
setMenuIsOpen(false);
|
|
234
|
+
setIsEditing(false);
|
|
235
|
+
setActiveTag(null);
|
|
236
|
+
|
|
237
|
+
if (endTagRegex.test(inputValue)) {
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
setInputValue(inputValue.replace(endTagRegex, '').trim());
|
|
240
|
+
}, 100);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const onFocus = () => {
|
|
245
|
+
setMenuIsOpen(true);
|
|
246
|
+
if (!endTagRegex.test(inputValue)) {
|
|
247
|
+
setInputValue(inputValue + ' #');
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const onBlur = () => {
|
|
252
|
+
if (currentInputString) {
|
|
253
|
+
exit();
|
|
254
|
+
synchronizeTags();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const onTagListBlur = () => {
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
if (document.activeElement !== inputNode.current) {
|
|
261
|
+
exit();
|
|
262
|
+
synchronizeTags();
|
|
263
|
+
}
|
|
264
|
+
}, 60);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleChange = (e, value, action) => {
|
|
268
|
+
if (action === 'selectOption') {
|
|
269
|
+
onTagAdd(value);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
let inputString = getInputString(input.value, tags);
|
|
275
|
+
|
|
276
|
+
if (document.activeElement === inputNode.current && !endTagRegex.test(inputString)) {
|
|
277
|
+
inputString += ' #';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
setInputValue(inputString);
|
|
281
|
+
}, [input.value, tags]);
|
|
282
|
+
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
setCurrentInputString(getCurrentInputString(inputValue, input.value || [], allMenuTags));
|
|
285
|
+
}, [inputValue, input.value, allMenuTags]);
|
|
286
|
+
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (meta.invalid && !isEditing) {
|
|
289
|
+
setIsEditing(true);
|
|
290
|
+
}
|
|
291
|
+
}, [meta.invalid]);
|
|
292
|
+
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (activeTag) {
|
|
295
|
+
const activeUpdated = allMenuTags.find(t => t.tagBody.id === activeTag.tagBody.id);
|
|
296
|
+
if (activeUpdated) {
|
|
297
|
+
setActiveTag(activeUpdated);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}, [allMenuTags]);
|
|
301
|
+
|
|
302
|
+
const popperAdapter = useCallback(params => {
|
|
303
|
+
if (currentInputString || !inputNode.current) {
|
|
304
|
+
return <div {...params} />;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const coords = getCaretCoordinates(inputNode.current, inputValue.length);
|
|
308
|
+
const position = tagMenuNode.current
|
|
309
|
+
&& coords.left > inputNode.current.clientWidth - tagMenuNode.current.clientWidth
|
|
310
|
+
? { right: 0 }
|
|
311
|
+
: { left: coords.left };
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div id="popper" style={{ ...position, position: 'absolute', zIndex: 2 }}>
|
|
315
|
+
<ClickAwayListener onClickAway={onTagListBlur}>
|
|
316
|
+
{params.children}
|
|
317
|
+
</ClickAwayListener>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}, [allMenuTags, inputValue, currentInputString, inputNode.current, tagMenuNode.current, input.value]);
|
|
321
|
+
|
|
322
|
+
const listboxAdapter = useCallback(params => (
|
|
323
|
+
<div className={params.className} ref={tagMenuNode}>
|
|
324
|
+
<AddTagMenu
|
|
325
|
+
tags={menuTags}
|
|
326
|
+
handleAdd={onTagAdd}
|
|
327
|
+
activeTag={activeTag}
|
|
328
|
+
setActiveTag={setActiveTag}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
), [menuTags, activeTag, setActiveTag, onTagAdd]);
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<div className={className} id={input.name}>
|
|
335
|
+
<div
|
|
336
|
+
className={clsx('relative', {
|
|
337
|
+
'pointer-events-none': disabled
|
|
338
|
+
})}
|
|
339
|
+
>
|
|
340
|
+
<Autocomplete
|
|
341
|
+
value={null}
|
|
342
|
+
open={menuIsOpen}
|
|
343
|
+
options={filteredOptions}
|
|
344
|
+
onChange={handleChange}
|
|
345
|
+
renderOption={renderOption}
|
|
346
|
+
filterOptions={filterOptionsInner}
|
|
347
|
+
getOptionLabel={getOptionLabel}
|
|
348
|
+
PopperComponent={currentInputString ? undefined : popperAdapter}
|
|
349
|
+
ListboxComponent={currentInputString ? undefined : listboxAdapter}
|
|
350
|
+
classes={{
|
|
351
|
+
root: classes.root,
|
|
352
|
+
hasPopupIcon: classes.hasPopup,
|
|
353
|
+
hasClearIcon: classes.hasClear,
|
|
354
|
+
listbox: clsx(classes.listbox, fieldClasses.listbox),
|
|
355
|
+
inputRoot: classes.inputWrapper,
|
|
356
|
+
noOptions: 'd-none'
|
|
357
|
+
}}
|
|
358
|
+
renderInput={({
|
|
359
|
+
InputLabelProps, InputProps, inputProps, ...params
|
|
360
|
+
}) => (
|
|
361
|
+
<EditInPlaceFieldBase
|
|
362
|
+
{...params}
|
|
363
|
+
name={input.name}
|
|
364
|
+
value={input.value}
|
|
365
|
+
error={meta.error}
|
|
366
|
+
invalid={meta?.invalid}
|
|
367
|
+
label={label}
|
|
368
|
+
warning={warning}
|
|
369
|
+
disabled={disabled}
|
|
370
|
+
fieldClasses={fieldClasses}
|
|
371
|
+
shrink={Boolean(label || input.value)}
|
|
372
|
+
labelAdornment={labelAdornment}
|
|
373
|
+
placeholder={placeholder || 'No value'}
|
|
374
|
+
editIcon={<Edit fontSize="inherit" />}
|
|
375
|
+
InputProps={{
|
|
376
|
+
...InputProps,
|
|
377
|
+
onChange: handleInputChange,
|
|
378
|
+
onFocus,
|
|
379
|
+
onBlur,
|
|
380
|
+
inputProps: {
|
|
381
|
+
...inputProps,
|
|
382
|
+
ref: ref => {
|
|
383
|
+
(inputProps as any).ref.current = ref;
|
|
384
|
+
inputNode.current = ref;
|
|
385
|
+
},
|
|
386
|
+
value: inputValue
|
|
387
|
+
},
|
|
388
|
+
}}
|
|
389
|
+
CustomInput={!isEditing
|
|
390
|
+
? <Select
|
|
391
|
+
inputRef={ref => {
|
|
392
|
+
(inputProps as any).ref.current = ref?.node;
|
|
393
|
+
}}
|
|
394
|
+
onFocus={edit}
|
|
395
|
+
value="stub"
|
|
396
|
+
className={classes.inputWrapper}
|
|
397
|
+
classes={{ select: 'd-flex flex-wrap cursor-text' }}
|
|
398
|
+
endAdornment={
|
|
399
|
+
<InputAdornment
|
|
400
|
+
position="end"
|
|
401
|
+
className={classes.inputEndAdornment}>
|
|
402
|
+
<Edit />
|
|
403
|
+
</InputAdornment>
|
|
404
|
+
}
|
|
405
|
+
IconComponent={stubComponent}
|
|
406
|
+
>
|
|
407
|
+
<MenuItem value="stub">
|
|
408
|
+
{InputValueForRender || <span className="placeholderContent">No value</span>}
|
|
409
|
+
</MenuItem>
|
|
410
|
+
</Select>
|
|
411
|
+
: null}
|
|
412
|
+
/>)
|
|
413
|
+
}
|
|
414
|
+
popupIcon={stubComponent()}
|
|
415
|
+
disableListWrap
|
|
416
|
+
openOnFocus
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export default withStyles(theme => ({
|
|
424
|
+
...selectStyles(theme),
|
|
425
|
+
...styles(theme)
|
|
426
|
+
}))(
|
|
427
|
+
TagInputList
|
|
428
|
+
) as any;
|