@etsoo/react 1.4.73 → 1.4.77

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.
Files changed (48) hide show
  1. package/lib/app/Utils.d.ts +7 -0
  2. package/lib/app/Utils.js +26 -0
  3. package/lib/components/GridColumn.d.ts +1 -1
  4. package/lib/components/ScrollerGrid.d.ts +1 -1
  5. package/lib/index.d.ts +1 -0
  6. package/lib/index.js +1 -0
  7. package/lib/mu/AutocompleteExtendedProps.d.ts +1 -1
  8. package/lib/mu/ComboBox.d.ts +1 -1
  9. package/lib/mu/DataGridEx.d.ts +9 -0
  10. package/lib/mu/DataGridEx.js +2 -2
  11. package/lib/mu/DnDList.d.ts +1 -1
  12. package/lib/mu/ItemList.d.ts +2 -2
  13. package/lib/mu/MUGlobal.d.ts +8 -0
  14. package/lib/mu/MUGlobal.js +3 -0
  15. package/lib/mu/MobileListItemRenderer.js +0 -1
  16. package/lib/mu/OptionGroup.d.ts +2 -2
  17. package/lib/mu/ResponsibleContainer.d.ts +4 -0
  18. package/lib/mu/ResponsibleContainer.js +6 -3
  19. package/lib/mu/ScrollerListEx.d.ts +10 -1
  20. package/lib/mu/ScrollerListEx.js +5 -3
  21. package/lib/mu/SelectBool.d.ts +14 -0
  22. package/lib/mu/SelectBool.js +21 -0
  23. package/lib/mu/SelectEx.d.ts +2 -2
  24. package/lib/mu/TableEx.d.ts +1 -1
  25. package/lib/mu/Tiplist.js +3 -1
  26. package/lib/mu/pages/ResponsivePageProps.d.ts +4 -0
  27. package/lib/mu/pages/ViewPage.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/src/app/Utils.ts +32 -0
  30. package/src/components/GridColumn.ts +1 -1
  31. package/src/components/ScrollerGrid.tsx +1 -1
  32. package/src/index.ts +1 -0
  33. package/src/mu/AutocompleteExtendedProps.ts +1 -1
  34. package/src/mu/ComboBox.tsx +1 -1
  35. package/src/mu/DataGridEx.tsx +19 -0
  36. package/src/mu/DnDList.tsx +1 -1
  37. package/src/mu/ItemList.tsx +2 -2
  38. package/src/mu/MUGlobal.ts +11 -0
  39. package/src/mu/MobileListItemRenderer.tsx +0 -2
  40. package/src/mu/OptionGroup.tsx +2 -2
  41. package/src/mu/ResponsibleContainer.tsx +15 -0
  42. package/src/mu/ScrollerListEx.tsx +32 -2
  43. package/src/mu/SelectBool.tsx +31 -0
  44. package/src/mu/SelectEx.tsx +2 -2
  45. package/src/mu/TableEx.tsx +2 -2
  46. package/src/mu/Tiplist.tsx +7 -1
  47. package/src/mu/pages/ResponsivePageProps.ts +5 -0
  48. package/src/mu/pages/ViewPage.tsx +1 -1
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  /**
2
3
  * React utils
3
4
  */
@@ -8,6 +9,12 @@ export declare namespace Utils {
8
9
  * @returns Formatted value
9
10
  */
10
11
  function formatInputValue(value: unknown): string | number | any[] | undefined;
12
+ /**
13
+ * Is safe click
14
+ * @param event Mouse event
15
+ * @returns Result
16
+ */
17
+ function isSafeClick(event: React.MouseEvent<HTMLElement>): boolean;
11
18
  /**
12
19
  * Trigger input change event
13
20
  * @param input Form input
package/lib/app/Utils.js CHANGED
@@ -20,6 +20,32 @@ export var Utils;
20
20
  return String(value);
21
21
  }
22
22
  Utils.formatInputValue = formatInputValue;
23
+ /**
24
+ * Is safe click
25
+ * @param event Mouse event
26
+ * @returns Result
27
+ */
28
+ function isSafeClick(event) {
29
+ // No target
30
+ // HTMLElement <= Element, SVGElement <= Element
31
+ if (!(event.target instanceof Element))
32
+ return true;
33
+ // Outside of the currentTarget
34
+ let target = event.target;
35
+ if (!event.currentTarget.contains(target))
36
+ return false;
37
+ while (target != null && target != event.currentTarget) {
38
+ const nodeName = target.nodeName.toUpperCase();
39
+ if (nodeName === 'INPUT' ||
40
+ nodeName === 'BUTTON' ||
41
+ nodeName === 'A' ||
42
+ target.hasAttribute('onClick'))
43
+ return false;
44
+ target = target.parentElement;
45
+ }
46
+ return true;
47
+ }
48
+ Utils.isSafeClick = isSafeClick;
23
49
  /**
24
50
  * Trigger input change event
25
51
  * @param input Form input
@@ -123,7 +123,7 @@ export interface GridColumn<T> {
123
123
  /**
124
124
  * The column identifier. It's used to map with row data
125
125
  */
126
- field?: string;
126
+ field?: string & keyof T;
127
127
  /**
128
128
  * The title of the column rendered in the column header cell
129
129
  */
@@ -33,7 +33,7 @@ export interface ScrollerGridProps<T> extends GridLoader<T>, Omit<VariableSizeGr
33
33
  * Id field
34
34
  * @default id
35
35
  */
36
- idField?: keyof T;
36
+ idField?: string & keyof T;
37
37
  /**
38
38
  * Item renderer
39
39
  */
package/lib/index.d.ts CHANGED
@@ -64,6 +64,7 @@ export * from './mu/ScrollTopFab';
64
64
  export * from './mu/SearchBar';
65
65
  export * from './mu/SearchField';
66
66
  export * from './mu/SearchOptionGroup';
67
+ export * from './mu/SelectBool';
67
68
  export * from './mu/SelectEx';
68
69
  export * from './mu/Switch';
69
70
  export * from './mu/SwitchAnt';
package/lib/index.js CHANGED
@@ -67,6 +67,7 @@ export * from './mu/ScrollTopFab';
67
67
  export * from './mu/SearchBar';
68
68
  export * from './mu/SearchField';
69
69
  export * from './mu/SearchOptionGroup';
70
+ export * from './mu/SelectBool';
70
71
  export * from './mu/SelectEx';
71
72
  export * from './mu/Switch';
72
73
  export * from './mu/SwitchAnt';
@@ -8,7 +8,7 @@ export interface AutocompleteExtendedProps<T extends Record<string, unknown>> ex
8
8
  /**
9
9
  * Id field, default is id
10
10
  */
11
- idField?: keyof T;
11
+ idField?: string & keyof T;
12
12
  /**
13
13
  * Id value
14
14
  */
@@ -12,7 +12,7 @@ export interface ComboBoxProps<T extends {}> extends AutocompleteExtendedProps<T
12
12
  /**
13
13
  * Label field
14
14
  */
15
- labelField?: keyof T;
15
+ labelField?: string & keyof T;
16
16
  /**
17
17
  * Load data callback
18
18
  */
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { GridColumn } from '../components/GridColumn';
3
3
  import { GridLoaderStates } from '../components/GridLoader';
4
4
  import { ScrollerGridProps } from '../components/ScrollerGrid';
5
+ import { MouseEventWithDataHandler } from './MUGlobal';
5
6
  /**
6
7
  * Footer item renderer props
7
8
  */
@@ -55,6 +56,14 @@ export interface DataGridExProps<T extends Record<string, any>> extends Omit<Scr
55
56
  * Hover color
56
57
  */
57
58
  hoverColor?: string;
59
+ /**
60
+ * Double click handler
61
+ */
62
+ onDoubleClick?: MouseEventWithDataHandler<T>;
63
+ /**
64
+ * Click handler
65
+ */
66
+ onClick?: MouseEventWithDataHandler<T>;
58
67
  /**
59
68
  * Selectable to support hover over and out effect and row clickable
60
69
  * @default true
@@ -126,7 +126,7 @@ export function DataGridEx(props) {
126
126
  })));
127
127
  }
128
128
  // Destruct
129
- const { alternatingColors = [theme.palette.grey[100], undefined], borderRowsCount, bottomHeight = 53, checkable = false, className, columns, defaultOrderBy, height, headerHeight = 56, headerRenderer = defaultHeaderRenderer, footerRenderer = defaultFooterRenderer, footerItemRenderer = DataGridRenderers.defaultFooterItemRenderer, hideFooter = false, hoverColor = '#f6f9fb', idField = 'id', mRef = React.createRef(), selectable = true, selectedColor = '#edf4fb', width, ...rest } = props;
129
+ const { alternatingColors = [theme.palette.grey[100], undefined], borderRowsCount, bottomHeight = 53, checkable = false, className, columns, defaultOrderBy, height, headerHeight = 56, headerRenderer = defaultHeaderRenderer, footerRenderer = defaultFooterRenderer, footerItemRenderer = DataGridRenderers.defaultFooterItemRenderer, hideFooter = false, hoverColor = '#f6f9fb', idField = 'id', mRef = React.createRef(), onClick, onDoubleClick, selectable = true, selectedColor = '#edf4fb', width, ...rest } = props;
130
130
  if (checkable) {
131
131
  const cbColumn = {
132
132
  field: 'selected',
@@ -261,7 +261,7 @@ export function DataGridEx(props) {
261
261
  cellProps,
262
262
  renderProps
263
263
  });
264
- return (React.createElement("div", { className: rowClass, style: style, "data-row": rowIndex, "data-column": columnIndex, onMouseDown: selectable && !checkable ? handleMouseDown : undefined, onMouseOver: selectable ? handleMouseOver : undefined, onMouseOut: selectable ? handleMouseOut : undefined },
264
+ return (React.createElement("div", { className: rowClass, style: style, "data-row": rowIndex, "data-column": columnIndex, onMouseDown: selectable && !checkable ? handleMouseDown : undefined, onMouseOver: selectable ? handleMouseOver : undefined, onMouseOut: selectable ? handleMouseOut : undefined, onClick: (event) => onClick && data != null && onClick(event, data), onDoubleClick: (event) => onDoubleClick && data != null && onDoubleClick(event, data) },
265
265
  React.createElement(Box, { ...cellProps, onMouseEnter: handleMouseEnter }, child)));
266
266
  };
267
267
  // Column width calculator
@@ -31,7 +31,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
31
31
  /**
32
32
  * Label field
33
33
  */
34
- labelField: keyof D;
34
+ labelField: string & keyof D;
35
35
  /**
36
36
  * Load data
37
37
  */
@@ -10,11 +10,11 @@ export interface ItemListProps<T extends Record<string, unknown>> {
10
10
  /**
11
11
  * Id field name
12
12
  */
13
- idField?: keyof T;
13
+ idField?: string & keyof T;
14
14
  /**
15
15
  * Label field name or callback
16
16
  */
17
- labelField?: keyof T | ((item: T) => string);
17
+ labelField?: (string & keyof T) | ((item: T) => string);
18
18
  /**
19
19
  * Button icon
20
20
  */
@@ -1,4 +1,12 @@
1
+ /// <reference types="react" />
1
2
  import { Theme } from '@mui/material';
3
+ /**
4
+ * Mouse event handler with data
5
+ */
6
+ export declare type MouseEventWithDataHandler<T> = (event: React.MouseEvent<HTMLDivElement>, data: T) => void;
7
+ /**
8
+ * MUGlobal for global configurations
9
+ */
2
10
  export declare class MUGlobal {
3
11
  /**
4
12
  * Search field shrink
@@ -1,4 +1,7 @@
1
1
  import { NumberUtils } from '@etsoo/shared';
2
+ /**
3
+ * MUGlobal for global configurations
4
+ */
2
5
  export class MUGlobal {
3
6
  /**
4
7
  * Update object number properties with half of it
@@ -22,7 +22,6 @@ export function MobileListItemRenderer({ data, itemHeight, margins }, renderer)
22
22
  // Loading
23
23
  if (data == null)
24
24
  return React.createElement(LinearProgress, null);
25
- console.log('margins', margins);
26
25
  // Elements
27
26
  const [title, subheader, actions, children, cardActions] = renderer(data);
28
27
  return (React.createElement(Card, { sx: {
@@ -17,7 +17,7 @@ export interface OptionGroupProps<T extends Record<string, any> = IdLabelDto, D
17
17
  /**
18
18
  * Id field, default is id
19
19
  */
20
- idField?: keyof T;
20
+ idField?: string & keyof T;
21
21
  /**
22
22
  * Label
23
23
  */
@@ -25,7 +25,7 @@ export interface OptionGroupProps<T extends Record<string, any> = IdLabelDto, D
25
25
  /**
26
26
  * Label field, default is label
27
27
  */
28
- labelField?: keyof T;
28
+ labelField?: string & keyof T;
29
29
  /**
30
30
  * Multiple choose item
31
31
  */
@@ -72,6 +72,10 @@ export interface ResponsibleContainerProps<T extends {}, F extends DataTypes.Bas
72
72
  * Pull to refresh data
73
73
  */
74
74
  pullToRefresh?: boolean;
75
+ /**
76
+ * Quick action for double click or click under mobile
77
+ */
78
+ quickAction?: (data: T) => void;
75
79
  /**
76
80
  * Size ready to read miliseconds span
77
81
  */
@@ -1,6 +1,7 @@
1
1
  import { Box, Stack } from '@mui/material';
2
2
  import React from 'react';
3
3
  import { Labels } from '../app/Labels';
4
+ import { Utils } from '../app/Utils';
4
5
  import { GridDataGet } from '../components/GridLoader';
5
6
  import useCombinedRefs from '../uses/useCombinedRefs';
6
7
  import { useDimensions } from '../uses/useDimensions';
@@ -24,7 +25,7 @@ function defaultContainerBoxSx(paddings, hasField, _dataGrid) {
24
25
  */
25
26
  export function ResponsibleContainer(props) {
26
27
  // Destruct
27
- const { adjustHeight, columns, containerBoxSx = defaultContainerBoxSx, dataGridMinWidth = Math.max(576, DataGridExCalColumns(columns).total), elementReady, fields, fieldTemplate, height, loadData, mRef, paddings = MUGlobal.pagePaddings, pullToRefresh = true, sizeReadyMiliseconds = 0, ...rest } = props;
28
+ const { adjustHeight, columns, containerBoxSx = defaultContainerBoxSx, dataGridMinWidth = Math.max(576, DataGridExCalColumns(columns).total), elementReady, fields, fieldTemplate, height, loadData, mRef, paddings = MUGlobal.pagePaddings, pullToRefresh = true, quickAction, sizeReadyMiliseconds = 0, ...rest } = props;
28
29
  // Labels
29
30
  const labels = Labels.CommonPage;
30
31
  // Refs
@@ -106,7 +107,7 @@ export function ResponsibleContainer(props) {
106
107
  delete rest.itemRenderer;
107
108
  return [
108
109
  React.createElement(Box, { className: "DataGridBox" },
109
- React.createElement(DataGridEx, { autoLoad: !hasFields, height: heightLocal, width: rect.width, loadData: localLoadData, mRef: mRefs, outerRef: (element) => {
110
+ React.createElement(DataGridEx, { autoLoad: !hasFields, height: heightLocal, width: rect.width, loadData: localLoadData, mRef: mRefs, onDoubleClick: (_, data) => quickAction && quickAction(data), outerRef: (element) => {
110
111
  if (element != null && elementReady)
111
112
  elementReady(element, true);
112
113
  }, columns: columns, ...rest })),
@@ -124,7 +125,9 @@ export function ResponsibleContainer(props) {
124
125
  delete rest.selectable;
125
126
  return [
126
127
  React.createElement(Box, { className: "ListBox", sx: { height: heightLocal } },
127
- React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, oRef: (element) => {
128
+ React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, onClick: (event, data) => quickAction &&
129
+ Utils.isSafeClick(event) &&
130
+ quickAction(data), oRef: (element) => {
128
131
  if (element != null && elementReady)
129
132
  elementReady(element, false);
130
133
  }, ...rest })),
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ListChildComponentProps } from 'react-window';
3
3
  import { ScrollerListProps } from '../components/ScrollerList';
4
+ import { MouseEventWithDataHandler } from './MUGlobal';
4
5
  /**
5
6
  * Extended ScrollerList inner item renderer props
6
7
  */
@@ -40,7 +41,7 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
40
41
  /**
41
42
  * Id field
42
43
  */
43
- idField?: keyof T;
44
+ idField?: string & keyof T;
44
45
  /**
45
46
  * Inner item renderer
46
47
  */
@@ -53,6 +54,14 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
53
54
  * Item size, a function indicates its a variable size list
54
55
  */
55
56
  itemSize: ScrollerListExItemSize;
57
+ /**
58
+ * Double click handler
59
+ */
60
+ onDoubleClick?: MouseEventWithDataHandler<T>;
61
+ /**
62
+ * Click handler
63
+ */
64
+ onClick?: MouseEventWithDataHandler<T>;
56
65
  /**
57
66
  * On items select change
58
67
  */
@@ -66,7 +66,7 @@ const defaultMargin = (margin, isNarrow) => {
66
66
  };
67
67
  };
68
68
  // Default itemRenderer
69
- function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, space, margins }) {
69
+ function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, onClick, onDoubleClick, space, margins }) {
70
70
  // Child
71
71
  const child = innerItemRenderer({
72
72
  index,
@@ -81,7 +81,7 @@ function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, sele
81
81
  if (selected)
82
82
  rowClass += ` ${selectedClassName}`;
83
83
  // Layout
84
- return (React.createElement("div", { className: rowClass, style: style, onMouseDown: (event) => onMouseDown(event.currentTarget, data) }, child));
84
+ return (React.createElement("div", { className: rowClass, style: style, onMouseDown: (event) => onMouseDown(event.currentTarget, data), onClick: (event) => onClick && onClick(event, data), onDoubleClick: (event) => onDoubleClick && onDoubleClick(event, data) }, child));
85
85
  }
86
86
  /**
87
87
  * Extended ScrollerList
@@ -118,12 +118,14 @@ export function ScrollerListEx(props) {
118
118
  itemHeight,
119
119
  innerItemRenderer,
120
120
  onMouseDown,
121
+ onClick,
122
+ onDoubleClick,
121
123
  space,
122
124
  margins,
123
125
  selected: isSelected(itemProps.data),
124
126
  ...itemProps
125
127
  });
126
- }, onSelectChange, selectedColor = '#edf4fb', ...rest } = props;
128
+ }, onClick, onDoubleClick, onSelectChange, selectedColor = '#edf4fb', ...rest } = props;
127
129
  // Theme
128
130
  const theme = useTheme();
129
131
  // Cache calculation
@@ -0,0 +1,14 @@
1
+ /// <reference types="react" />
2
+ import { IdLabelDto } from '@etsoo/appscript';
3
+ import { SelectExProps } from './SelectEx';
4
+ /**
5
+ * SelectBool props
6
+ */
7
+ export interface SelectBoolProps extends Omit<SelectExProps<IdLabelDto<string>>, 'options' | 'loadData'> {
8
+ }
9
+ /**
10
+ * SelectBool (yes/no)
11
+ * @param props Props
12
+ * @returns Component
13
+ */
14
+ export declare function SelectBool(props: SelectBoolProps): JSX.Element;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { globalApp } from '..';
3
+ import { SelectEx } from './SelectEx';
4
+ /**
5
+ * SelectBool (yes/no)
6
+ * @param props Props
7
+ * @returns Component
8
+ */
9
+ export function SelectBool(props) {
10
+ // Destruct
11
+ const { search = true, ...rest } = props;
12
+ // Options
13
+ const options = [
14
+ { id: 'false', label: globalApp.get('no') },
15
+ { id: 'true', label: globalApp.get('yes') }
16
+ ];
17
+ if (search)
18
+ options.unshift({ id: '', label: '---' });
19
+ // Layout
20
+ return React.createElement(SelectEx, { options: options, search: search, ...rest });
21
+ }
@@ -8,7 +8,7 @@ export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId'
8
8
  /**
9
9
  * Id field, default is id
10
10
  */
11
- idField?: keyof T;
11
+ idField?: string & keyof T;
12
12
  /**
13
13
  * Item icon renderer
14
14
  */
@@ -16,7 +16,7 @@ export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId'
16
16
  /**
17
17
  * Label field, default is label
18
18
  */
19
- labelField?: ((option: T) => string) | keyof T;
19
+ labelField?: ((option: T) => string) | (string & keyof T);
20
20
  /**
21
21
  * Load data callback
22
22
  */
@@ -35,7 +35,7 @@ export interface TableExProps<T extends Record<string, any>> extends TableProps,
35
35
  /**
36
36
  * Id field
37
37
  */
38
- idField?: keyof T;
38
+ idField?: string & keyof T;
39
39
  /**
40
40
  * Max height
41
41
  */
package/lib/mu/Tiplist.js CHANGED
@@ -29,6 +29,8 @@ export function Tiplist(props) {
29
29
  options: [],
30
30
  value: null
31
31
  });
32
+ // Input value
33
+ const inputValue = React.useMemo(() => states.value && Reflect.get(states.value, idField), [states.value]);
32
34
  React.useEffect(() => {
33
35
  if (localValue != null)
34
36
  stateUpdate({ value: localValue });
@@ -133,7 +135,7 @@ export function Tiplist(props) {
133
135
  }, []);
134
136
  // Layout
135
137
  return (React.createElement("div", null,
136
- React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: localIdValue !== null && localIdValue !== void 0 ? localIdValue : '', readOnly: true, onChange: inputOnChange }),
138
+ React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: inputValue !== null && inputValue !== void 0 ? inputValue : '', readOnly: true, onChange: inputOnChange }),
137
139
  React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, onChange: (event, value, reason, details) => {
138
140
  // Set value
139
141
  setInputValue(value);
@@ -32,4 +32,8 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate> extends
32
32
  * Pull to refresh data
33
33
  */
34
34
  pullToRefresh?: boolean;
35
+ /**
36
+ * Quick action for double click or click under mobile
37
+ */
38
+ quickAction?: (data: T) => void;
35
39
  }
@@ -9,7 +9,7 @@ export interface ViewPageField<T extends {}> extends GridProps {
9
9
  /**
10
10
  * Data field
11
11
  */
12
- data: keyof T | ((item: T) => React.ReactNode);
12
+ data: (string & keyof T) | ((item: T) => React.ReactNode);
13
13
  /**
14
14
  * Data type
15
15
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.4.73",
3
+ "version": "1.4.77",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/src/app/Utils.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  /**
2
4
  * React utils
3
5
  */
@@ -19,6 +21,36 @@ export namespace Utils {
19
21
  return String(value);
20
22
  }
21
23
 
24
+ /**
25
+ * Is safe click
26
+ * @param event Mouse event
27
+ * @returns Result
28
+ */
29
+ export function isSafeClick(event: React.MouseEvent<HTMLElement>) {
30
+ // No target
31
+ // HTMLElement <= Element, SVGElement <= Element
32
+ if (!(event.target instanceof Element)) return true;
33
+
34
+ // Outside of the currentTarget
35
+ let target: Element | null = event.target;
36
+ if (!event.currentTarget.contains(target)) return false;
37
+
38
+ while (target != null && target != event.currentTarget) {
39
+ const nodeName = target.nodeName.toUpperCase();
40
+ if (
41
+ nodeName === 'INPUT' ||
42
+ nodeName === 'BUTTON' ||
43
+ nodeName === 'A' ||
44
+ target.hasAttribute('onClick')
45
+ )
46
+ return false;
47
+
48
+ target = target.parentElement;
49
+ }
50
+
51
+ return true;
52
+ }
53
+
22
54
  /**
23
55
  * Trigger input change event
24
56
  * @param input Form input
@@ -160,7 +160,7 @@ export interface GridColumn<T> {
160
160
  /**
161
161
  * The column identifier. It's used to map with row data
162
162
  */
163
- field?: string;
163
+ field?: string & keyof T;
164
164
 
165
165
  /**
166
166
  * The title of the column rendered in the column header cell
@@ -54,7 +54,7 @@ export interface ScrollerGridProps<T>
54
54
  * Id field
55
55
  * @default id
56
56
  */
57
- idField?: keyof T;
57
+ idField?: string & keyof T;
58
58
 
59
59
  /**
60
60
  * Item renderer
package/src/index.ts CHANGED
@@ -71,6 +71,7 @@ export * from './mu/ScrollTopFab';
71
71
  export * from './mu/SearchBar';
72
72
  export * from './mu/SearchField';
73
73
  export * from './mu/SearchOptionGroup';
74
+ export * from './mu/SelectBool';
74
75
  export * from './mu/SelectEx';
75
76
  export * from './mu/Switch';
76
77
  export * from './mu/SwitchAnt';
@@ -13,7 +13,7 @@ export interface AutocompleteExtendedProps<T extends Record<string, unknown>>
13
13
  /**
14
14
  * Id field, default is id
15
15
  */
16
- idField?: keyof T;
16
+ idField?: string & keyof T;
17
17
 
18
18
  /**
19
19
  * Id value
@@ -20,7 +20,7 @@ export interface ComboBoxProps<T extends {}>
20
20
  /**
21
21
  * Label field
22
22
  */
23
- labelField?: keyof T;
23
+ labelField?: string & keyof T;
24
24
 
25
25
  /**
26
26
  * Load data callback
@@ -25,6 +25,7 @@ import {
25
25
  } from '../components/ScrollerGrid';
26
26
  import useCombinedRefs from '../uses/useCombinedRefs';
27
27
  import { DataGridRenderers } from './DataGridRenderers';
28
+ import { MouseEventWithDataHandler } from './MUGlobal';
28
29
 
29
30
  /**
30
31
  * Footer item renderer props
@@ -98,6 +99,16 @@ export interface DataGridExProps<T extends Record<string, any>>
98
99
  */
99
100
  hoverColor?: string;
100
101
 
102
+ /**
103
+ * Double click handler
104
+ */
105
+ onDoubleClick?: MouseEventWithDataHandler<T>;
106
+
107
+ /**
108
+ * Click handler
109
+ */
110
+ onClick?: MouseEventWithDataHandler<T>;
111
+
101
112
  /**
102
113
  * Selectable to support hover over and out effect and row clickable
103
114
  * @default true
@@ -351,6 +362,8 @@ export function DataGridEx<T extends Record<string, any>>(
351
362
  hoverColor = '#f6f9fb',
352
363
  idField = 'id',
353
364
  mRef = React.createRef(),
365
+ onClick,
366
+ onDoubleClick,
354
367
  selectable = true,
355
368
  selectedColor = '#edf4fb',
356
369
  width,
@@ -567,6 +580,12 @@ export function DataGridEx<T extends Record<string, any>>(
567
580
  }
568
581
  onMouseOver={selectable ? handleMouseOver : undefined}
569
582
  onMouseOut={selectable ? handleMouseOut : undefined}
583
+ onClick={(event) =>
584
+ onClick && data != null && onClick(event, data)
585
+ }
586
+ onDoubleClick={(event) =>
587
+ onDoubleClick && data != null && onDoubleClick(event, data)
588
+ }
570
589
  >
571
590
  <Box {...cellProps} onMouseEnter={handleMouseEnter}>
572
591
  {child}
@@ -50,7 +50,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
50
50
  /**
51
51
  * Label field
52
52
  */
53
- labelField: keyof D;
53
+ labelField: string & keyof D;
54
54
 
55
55
  /**
56
56
  * Load data
@@ -22,12 +22,12 @@ export interface ItemListProps<T extends Record<string, unknown>> {
22
22
  /**
23
23
  * Id field name
24
24
  */
25
- idField?: keyof T;
25
+ idField?: string & keyof T;
26
26
 
27
27
  /**
28
28
  * Label field name or callback
29
29
  */
30
- labelField?: keyof T | ((item: T) => string);
30
+ labelField?: (string & keyof T) | ((item: T) => string);
31
31
 
32
32
  /**
33
33
  * Button icon
@@ -1,6 +1,17 @@
1
1
  import { NumberUtils } from '@etsoo/shared';
2
2
  import { Breakpoint, Theme } from '@mui/material';
3
3
 
4
+ /**
5
+ * Mouse event handler with data
6
+ */
7
+ export type MouseEventWithDataHandler<T> = (
8
+ event: React.MouseEvent<HTMLDivElement>,
9
+ data: T
10
+ ) => void;
11
+
12
+ /**
13
+ * MUGlobal for global configurations
14
+ */
4
15
  export class MUGlobal {
5
16
  /**
6
17
  * Search field shrink
@@ -36,8 +36,6 @@ export function MobileListItemRenderer<T>(
36
36
  // Loading
37
37
  if (data == null) return <LinearProgress />;
38
38
 
39
- console.log('margins', margins);
40
-
41
39
  // Elements
42
40
  const [title, subheader, actions, children, cardActions] = renderer(data);
43
41
 
@@ -32,7 +32,7 @@ export interface OptionGroupProps<
32
32
  /**
33
33
  * Id field, default is id
34
34
  */
35
- idField?: keyof T;
35
+ idField?: string & keyof T;
36
36
 
37
37
  /**
38
38
  * Label
@@ -42,7 +42,7 @@ export interface OptionGroupProps<
42
42
  /**
43
43
  * Label field, default is label
44
44
  */
45
- labelField?: keyof T;
45
+ labelField?: string & keyof T;
46
46
 
47
47
  /**
48
48
  * Multiple choose item
@@ -3,6 +3,7 @@ import { Box, Stack, SxProps, Theme } from '@mui/material';
3
3
  import React from 'react';
4
4
  import { ListChildComponentProps } from 'react-window';
5
5
  import { Labels } from '../app/Labels';
6
+ import { Utils } from '../app/Utils';
6
7
  import { GridColumn } from '../components/GridColumn';
7
8
  import {
8
9
  GridDataGet,
@@ -125,6 +126,11 @@ export interface ResponsibleContainerProps<
125
126
  */
126
127
  pullToRefresh?: boolean;
127
128
 
129
+ /**
130
+ * Quick action for double click or click under mobile
131
+ */
132
+ quickAction?: (data: T) => void;
133
+
128
134
  /**
129
135
  * Size ready to read miliseconds span
130
136
  */
@@ -173,6 +179,7 @@ export function ResponsibleContainer<
173
179
  mRef,
174
180
  paddings = MUGlobal.pagePaddings,
175
181
  pullToRefresh = true,
182
+ quickAction,
176
183
  sizeReadyMiliseconds = 0,
177
184
  ...rest
178
185
  } = props;
@@ -285,6 +292,9 @@ export function ResponsibleContainer<
285
292
  width={rect.width}
286
293
  loadData={localLoadData}
287
294
  mRef={mRefs}
295
+ onDoubleClick={(_, data) =>
296
+ quickAction && quickAction(data)
297
+ }
288
298
  outerRef={(element?: HTMLDivElement) => {
289
299
  if (element != null && elementReady)
290
300
  elementReady(element, true);
@@ -314,6 +324,11 @@ export function ResponsibleContainer<
314
324
  height={heightLocal}
315
325
  loadData={localLoadData}
316
326
  mRef={mRefs}
327
+ onClick={(event, data) =>
328
+ quickAction &&
329
+ Utils.isSafeClick(event) &&
330
+ quickAction(data)
331
+ }
317
332
  oRef={(element) => {
318
333
  if (element != null && elementReady)
319
334
  elementReady(element, false);
@@ -4,7 +4,7 @@ import { useTheme } from '@mui/material';
4
4
  import React from 'react';
5
5
  import { ListChildComponentProps } from 'react-window';
6
6
  import { ScrollerList, ScrollerListProps } from '../components/ScrollerList';
7
- import { MUGlobal } from './MUGlobal';
7
+ import { MouseEventWithDataHandler, MUGlobal } from './MUGlobal';
8
8
 
9
9
  // Scroll bar size
10
10
  const scrollbarSize = 16;
@@ -127,7 +127,7 @@ export interface ScrollerListExProps<T>
127
127
  /**
128
128
  * Id field
129
129
  */
130
- idField?: keyof T;
130
+ idField?: string & keyof T;
131
131
 
132
132
  /**
133
133
  * Inner item renderer
@@ -146,6 +146,16 @@ export interface ScrollerListExProps<T>
146
146
  */
147
147
  itemSize: ScrollerListExItemSize;
148
148
 
149
+ /**
150
+ * Double click handler
151
+ */
152
+ onDoubleClick?: MouseEventWithDataHandler<T>;
153
+
154
+ /**
155
+ * Click handler
156
+ */
157
+ onClick?: MouseEventWithDataHandler<T>;
158
+
149
159
  /**
150
160
  * On items select change
151
161
  */
@@ -175,6 +185,16 @@ interface defaultItemRendererProps<T> extends ListChildComponentProps<T> {
175
185
  */
176
186
  itemHeight: number;
177
187
 
188
+ /**
189
+ * Double click handler
190
+ */
191
+ onDoubleClick?: MouseEventWithDataHandler<T>;
192
+
193
+ /**
194
+ * Click handler
195
+ */
196
+ onClick?: MouseEventWithDataHandler<T>;
197
+
178
198
  /**
179
199
  * Item space
180
200
  */
@@ -200,6 +220,8 @@ function defaultItemRenderer<T>({
200
220
  selected,
201
221
  style,
202
222
  itemHeight,
223
+ onClick,
224
+ onDoubleClick,
203
225
  space,
204
226
  margins
205
227
  }: defaultItemRendererProps<T>) {
@@ -223,6 +245,10 @@ function defaultItemRenderer<T>({
223
245
  className={rowClass}
224
246
  style={style}
225
247
  onMouseDown={(event) => onMouseDown(event.currentTarget, data)}
248
+ onClick={(event) => onClick && onClick(event, data)}
249
+ onDoubleClick={(event) =>
250
+ onDoubleClick && onDoubleClick(event, data)
251
+ }
226
252
  >
227
253
  {child}
228
254
  </div>
@@ -282,12 +308,16 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
282
308
  itemHeight,
283
309
  innerItemRenderer,
284
310
  onMouseDown,
311
+ onClick,
312
+ onDoubleClick,
285
313
  space,
286
314
  margins,
287
315
  selected: isSelected(itemProps.data),
288
316
  ...itemProps
289
317
  });
290
318
  },
319
+ onClick,
320
+ onDoubleClick,
291
321
  onSelectChange,
292
322
  selectedColor = '#edf4fb',
293
323
  ...rest
@@ -0,0 +1,31 @@
1
+ import { IdLabelDto } from '@etsoo/appscript';
2
+ import React from 'react';
3
+ import { globalApp } from '..';
4
+ import { SelectEx, SelectExProps } from './SelectEx';
5
+
6
+ /**
7
+ * SelectBool props
8
+ */
9
+ export interface SelectBoolProps
10
+ extends Omit<SelectExProps<IdLabelDto<string>>, 'options' | 'loadData'> {}
11
+
12
+ /**
13
+ * SelectBool (yes/no)
14
+ * @param props Props
15
+ * @returns Component
16
+ */
17
+ export function SelectBool(props: SelectBoolProps) {
18
+ // Destruct
19
+ const { search = true, ...rest } = props;
20
+
21
+ // Options
22
+ const options: IdLabelDto<string>[] = [
23
+ { id: 'false', label: globalApp.get('no')! },
24
+ { id: 'true', label: globalApp.get('yes')! }
25
+ ];
26
+
27
+ if (search) options.unshift({ id: '', label: '---' });
28
+
29
+ // Layout
30
+ return <SelectEx options={options} search={search} {...rest} />;
31
+ }
@@ -23,7 +23,7 @@ export interface SelectExProps<T extends {}>
23
23
  /**
24
24
  * Id field, default is id
25
25
  */
26
- idField?: keyof T;
26
+ idField?: string & keyof T;
27
27
 
28
28
  /**
29
29
  * Item icon renderer
@@ -33,7 +33,7 @@ export interface SelectExProps<T extends {}>
33
33
  /**
34
34
  * Label field, default is label
35
35
  */
36
- labelField?: ((option: T) => string) | keyof T;
36
+ labelField?: ((option: T) => string) | (string & keyof T);
37
37
 
38
38
  /**
39
39
  * Load data callback
@@ -68,7 +68,7 @@ export interface TableExProps<T extends Record<string, any>>
68
68
  /**
69
69
  * Id field
70
70
  */
71
- idField?: keyof T;
71
+ idField?: string & keyof T;
72
72
 
73
73
  /**
74
74
  * Max height
@@ -107,7 +107,7 @@ export function TableEx<T extends Record<string, unknown>>(
107
107
  // Theme
108
108
  const theme = useTheme();
109
109
 
110
- type keyType = keyof T;
110
+ type keyType = string & keyof T;
111
111
 
112
112
  // Destruct
113
113
  const {
@@ -83,6 +83,12 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
83
83
  }
84
84
  );
85
85
 
86
+ // Input value
87
+ const inputValue = React.useMemo(
88
+ () => states.value && Reflect.get(states.value, idField),
89
+ [states.value]
90
+ );
91
+
86
92
  React.useEffect(() => {
87
93
  if (localValue != null) stateUpdate({ value: localValue });
88
94
  }, [localValue]);
@@ -219,7 +225,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
219
225
  type="text"
220
226
  style={{ display: 'none' }}
221
227
  name={name}
222
- value={localIdValue ?? ''}
228
+ value={inputValue ?? ''}
223
229
  readOnly
224
230
  onChange={inputOnChange}
225
231
  />
@@ -46,4 +46,9 @@ export interface ResponsePageProps<T, F extends DataTypes.BasicTemplate>
46
46
  * Pull to refresh data
47
47
  */
48
48
  pullToRefresh?: boolean;
49
+
50
+ /**
51
+ * Quick action for double click or click under mobile
52
+ */
53
+ quickAction?: (data: T) => void;
49
54
  }
@@ -24,7 +24,7 @@ export interface ViewPageField<T extends {}> extends GridProps {
24
24
  /**
25
25
  * Data field
26
26
  */
27
- data: keyof T | ((item: T) => React.ReactNode);
27
+ data: (string & keyof T) | ((item: T) => React.ReactNode);
28
28
 
29
29
  /**
30
30
  * Data type