@vuu-ui/vuu-utils 2.1.19-beta.1 → 2.1.19-beta.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/package.json +8 -9
- package/src/Clock.js +43 -0
- package/src/PageVisibilityObserver.js +42 -0
- package/src/ScaledDecimal.js +32 -0
- package/src/ShellContext.js +5 -0
- package/src/ThemeProvider.js +56 -0
- package/src/array-utils.js +51 -0
- package/src/box-utils.js +51 -0
- package/src/broadcast-channel.js +0 -0
- package/src/column-utils.js +790 -0
- package/src/common-types.js +11 -0
- package/src/component-registry.js +83 -0
- package/src/context-definitions/DataContext.js +15 -0
- package/src/context-definitions/DataProvider.js +13 -0
- package/src/context-definitions/DataSourceProvider.js +18 -0
- package/src/context-definitions/WorkspaceContext.js +14 -0
- package/src/cookie-utils.js +15 -0
- package/src/css-utils.js +5 -0
- package/src/data-editing/DataEditingProvider.js +13 -0
- package/src/data-editing/EditButtons.js +32 -0
- package/src/data-editing/EditModeProvider.js +18 -0
- package/src/data-editing/EditSession.js +149 -0
- package/src/data-editing/edit-utils.js +37 -0
- package/src/data-editing/useEditableTable.js +76 -0
- package/src/data-utils.js +55 -0
- package/src/datasource/BaseDataSource.js +233 -0
- package/src/datasource/datasource-action-utils.js +6 -0
- package/src/datasource/datasource-filter-utils.js +27 -0
- package/src/datasource/datasource-utils.js +148 -0
- package/src/date/date-utils.js +80 -0
- package/src/date/dateTimePattern.js +17 -0
- package/src/date/formatter.js +101 -0
- package/src/date/index.js +4 -0
- package/src/date/types.js +27 -0
- package/src/debug-utils.js +25 -0
- package/src/event-emitter.js +78 -0
- package/src/feature-utils.js +86 -0
- package/src/filters/filter-utils.js +136 -0
- package/src/filters/filterAsQuery.js +70 -0
- package/src/filters/index.js +2 -0
- package/src/form-utils.js +69 -0
- package/src/formatting-utils.js +42 -0
- package/src/getUniqueId.js +2 -0
- package/src/group-utils.js +16 -0
- package/src/html-utils.js +108 -0
- package/src/index.js +77 -0
- package/src/input-utils.js +3 -0
- package/src/invariant.js +9 -0
- package/src/itemToString.js +9 -0
- package/src/json-types.js +0 -0
- package/src/json-utils.js +108 -0
- package/src/keyboard-utils.js +14 -0
- package/src/keyset.js +57 -0
- package/src/layout-types.js +0 -0
- package/src/list-utils.js +5 -0
- package/src/local-storage-utils.js +23 -0
- package/src/logging-utils.js +52 -0
- package/src/module-utils.js +2 -0
- package/src/nanoid/index.js +13 -0
- package/src/perf-utils.js +28 -0
- package/src/promise-utils.js +26 -0
- package/src/protocol-message-utils.js +68 -0
- package/src/range-utils.js +109 -0
- package/src/react-utils.js +64 -0
- package/src/round-decimal.js +83 -0
- package/src/row-utils.js +43 -0
- package/src/selection-utils.js +25 -0
- package/src/shell-layout-types.js +9 -0
- package/src/sort-utils.js +75 -0
- package/src/table-schema-utils.js +11 -0
- package/src/text-utils.js +13 -0
- package/src/theme-utils.js +21 -0
- package/src/tree-types.js +0 -0
- package/src/tree-utils.js +96 -0
- package/src/ts-utils.js +6 -0
- package/src/typeahead-utils.js +4 -0
- package/src/url-utils.js +12 -0
- package/src/useId.js +6 -0
- package/src/useLayoutEffectSkipFirst.js +9 -0
- package/src/useResizeObserver.js +122 -0
- package/src/useStateRef.js +21 -0
- package/src/user-types.js +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { hasCustomRenderer, isColumnTypeRenderer, isTypeDescriptor } from "./column-utils.js";
|
|
2
|
+
const containersSet = new Set();
|
|
3
|
+
const viewsSet = new Set();
|
|
4
|
+
const layoutComponentsMap = new Map();
|
|
5
|
+
const cellRenderersMap = new Map();
|
|
6
|
+
const columnHeaderRenderersMap = new Map();
|
|
7
|
+
const configEditorsMap = new Map();
|
|
8
|
+
const cellConfigPanelsMap = new Map();
|
|
9
|
+
const editRuleValidatorsMap = new Map();
|
|
10
|
+
const optionsMap = new Map();
|
|
11
|
+
const rowClassGeneratorsMap = new Map();
|
|
12
|
+
const isTypeCompatible = (rendererType, serverDataType)=>{
|
|
13
|
+
if (void 0 === rendererType || "private" === rendererType) return true;
|
|
14
|
+
if (!Array.isArray(rendererType)) return rendererType === serverDataType;
|
|
15
|
+
if ("json" === serverDataType) return false;
|
|
16
|
+
return rendererType.includes(serverDataType);
|
|
17
|
+
};
|
|
18
|
+
const isContainer = (componentType)=>containersSet.has(componentType);
|
|
19
|
+
const isView = (componentType)=>viewsSet.has(componentType);
|
|
20
|
+
const isLayoutComponent = (componentType)=>isContainer(componentType) || isView(componentType);
|
|
21
|
+
const isCellRenderer = (type, component)=>void 0 !== component && "cell-renderer" === type;
|
|
22
|
+
const isColumnHeaderContentRenderer = (type, component)=>"column-header-content-renderer" === type;
|
|
23
|
+
const isColumnHeaderLabelRenderer = (type, component)=>"column-header-label-renderer" === type;
|
|
24
|
+
const isCellConfigPanel = (type, component)=>"cell-config-panel" === type;
|
|
25
|
+
const isEditRuleValidator = (type, component)=>"data-edit-validator" === type;
|
|
26
|
+
const isRowClassGenerator = (type, component)=>"row-class-generator" === type;
|
|
27
|
+
function registerComponent(componentName, component, componentType, options) {
|
|
28
|
+
if ("container" === componentType || "view" === componentType || "component" === componentType) {
|
|
29
|
+
layoutComponentsMap.set(componentName, component);
|
|
30
|
+
if ("container" === componentType) containersSet.add(componentName);
|
|
31
|
+
else if ("view" === componentType) viewsSet.add(componentName);
|
|
32
|
+
} else if (isCellRenderer(componentType, component)) cellRenderersMap.set(componentName, component);
|
|
33
|
+
else if (isColumnHeaderContentRenderer(componentType, component)) columnHeaderRenderersMap.set(componentName, component);
|
|
34
|
+
else if (isColumnHeaderLabelRenderer(componentType, component)) columnHeaderRenderersMap.set(componentName, component);
|
|
35
|
+
else if (isCellConfigPanel(componentType, component)) cellConfigPanelsMap.set(componentName, component);
|
|
36
|
+
else if (isEditRuleValidator(componentType, component)) editRuleValidatorsMap.set(componentName, component);
|
|
37
|
+
else if (isRowClassGenerator(componentType, component)) rowClassGeneratorsMap.set(componentName, component);
|
|
38
|
+
if (options) optionsMap.set(componentName, options);
|
|
39
|
+
}
|
|
40
|
+
const registerConfigurationEditor = (componentName, configurationEditor)=>{
|
|
41
|
+
configEditorsMap.set(componentName, configurationEditor);
|
|
42
|
+
};
|
|
43
|
+
const getRegisteredCellRenderers = (cellRendererDataType)=>{
|
|
44
|
+
const rendererNames = Array.from(cellRenderersMap.keys());
|
|
45
|
+
const allRenderers = rendererNames.map((name)=>({
|
|
46
|
+
name,
|
|
47
|
+
...optionsMap.get(name)
|
|
48
|
+
})).filter(({ userCanAssign })=>false !== userCanAssign);
|
|
49
|
+
if (cellRendererDataType) return allRenderers.filter((renderer)=>isTypeCompatible(renderer.serverDataType, cellRendererDataType));
|
|
50
|
+
return allRenderers;
|
|
51
|
+
};
|
|
52
|
+
const getLayoutComponent = (componentName)=>{
|
|
53
|
+
const layoutComponent = layoutComponentsMap.get(componentName);
|
|
54
|
+
if (layoutComponent) return layoutComponent;
|
|
55
|
+
throw Error(`layout component ${componentName} not found in ComponentRegistry`);
|
|
56
|
+
};
|
|
57
|
+
const getCellRendererOptions = (renderName)=>optionsMap.get(renderName);
|
|
58
|
+
function getCellRenderer(column) {
|
|
59
|
+
return dataCellRenderer(column);
|
|
60
|
+
}
|
|
61
|
+
function getColumnHeaderContentRenderer(column) {
|
|
62
|
+
if (column.colHeaderContentRenderer) return columnHeaderRenderersMap.get(column.colHeaderContentRenderer);
|
|
63
|
+
}
|
|
64
|
+
function getColumnHeaderLabelRenderer(column) {
|
|
65
|
+
if (column.colHeaderLabelRenderer) return columnHeaderRenderersMap.get(column.colHeaderLabelRenderer);
|
|
66
|
+
}
|
|
67
|
+
const getRowClassNameGenerator = (generatorId)=>rowClassGeneratorsMap.get(generatorId);
|
|
68
|
+
function getConfigurationEditor(configEditor = "") {
|
|
69
|
+
return configEditorsMap.get(configEditor);
|
|
70
|
+
}
|
|
71
|
+
function getCellConfigPanelRenderer(name) {
|
|
72
|
+
return cellConfigPanelsMap.get(name);
|
|
73
|
+
}
|
|
74
|
+
function getEditRuleValidator(name) {
|
|
75
|
+
return editRuleValidatorsMap.get(name);
|
|
76
|
+
}
|
|
77
|
+
function dataCellRenderer(column) {
|
|
78
|
+
if ("boolean" === column.serverDataType && !hasCustomRenderer(column.type)) return cellRenderersMap.get("checkbox-cell");
|
|
79
|
+
if (column.editable && !hasCustomRenderer(column.type)) return cellRenderersMap.get("input-cell");
|
|
80
|
+
if (isTypeDescriptor(column.type) && isColumnTypeRenderer(column.type.renderer)) return cellRenderersMap.get(column.type.renderer?.name);
|
|
81
|
+
if ("boolean" === column.serverDataType) return cellRenderersMap.get("checkbox-cell");
|
|
82
|
+
}
|
|
83
|
+
export { getCellConfigPanelRenderer, getCellRenderer, getCellRendererOptions, getColumnHeaderContentRenderer, getColumnHeaderLabelRenderer, getConfigurationEditor, getEditRuleValidator, getLayoutComponent, getRegisteredCellRenderers, getRowClassNameGenerator, isContainer, isLayoutComponent, isView, registerComponent, registerConfigurationEditor };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
const getServerAPI = ()=>{
|
|
3
|
+
throw Error("no DataProvider has been installed");
|
|
4
|
+
};
|
|
5
|
+
class NullDataSourceConstructor {
|
|
6
|
+
constructor(_){
|
|
7
|
+
throw Error("no DataProvider has been installed");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const DataContext = /*#__PURE__*/ createContext({
|
|
11
|
+
isLocalData: false,
|
|
12
|
+
getServerAPI: getServerAPI,
|
|
13
|
+
VuuDataSource: NullDataSourceConstructor
|
|
14
|
+
});
|
|
15
|
+
export { DataContext, NullDataSourceConstructor };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { DataContext } from "./DataContext.js";
|
|
4
|
+
const DataProvider = ({ children, getServerAPI, isLocalData = true, VuuDataSource })=>/*#__PURE__*/ jsx(DataContext.Provider, {
|
|
5
|
+
value: {
|
|
6
|
+
isLocalData,
|
|
7
|
+
VuuDataSource,
|
|
8
|
+
getServerAPI
|
|
9
|
+
},
|
|
10
|
+
children: children
|
|
11
|
+
});
|
|
12
|
+
const useData = ()=>useContext(DataContext);
|
|
13
|
+
export { DataProvider, useData };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const DataSourceContext = /*#__PURE__*/ createContext({
|
|
4
|
+
dataSource: void 0
|
|
5
|
+
});
|
|
6
|
+
const DataSourceProvider = ({ children, dataSource })=>/*#__PURE__*/ jsx(DataSourceContext.Provider, {
|
|
7
|
+
value: {
|
|
8
|
+
dataSource
|
|
9
|
+
},
|
|
10
|
+
children: children
|
|
11
|
+
});
|
|
12
|
+
function useDataSource(throwIfNoDataSource = false) {
|
|
13
|
+
const { dataSource } = useContext(DataSourceContext);
|
|
14
|
+
if (dataSource) return dataSource;
|
|
15
|
+
if (throwIfNoDataSource) throw Error("[DataSOurceProvider] useDataSource,no DataSourceProvider has been declared ");
|
|
16
|
+
console.warn("[DataSourceProvider] useDataSource: no DataSourceProvider has been declared");
|
|
17
|
+
}
|
|
18
|
+
export { DataSourceContext, DataSourceProvider, useDataSource };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import react, { useContext } from "react";
|
|
2
|
+
const WorkspaceContext = /*#__PURE__*/ react.createContext({
|
|
3
|
+
getApplicationSettings: ()=>void 0,
|
|
4
|
+
layoutMetadata: [],
|
|
5
|
+
saveLayout: ()=>void 0,
|
|
6
|
+
saveApplicationLayout: ()=>void 0,
|
|
7
|
+
saveApplicationSettings: ()=>void 0,
|
|
8
|
+
loadLayoutById: ()=>void 0
|
|
9
|
+
});
|
|
10
|
+
const usePlaceholderJSON = ()=>{
|
|
11
|
+
const { layoutPlaceholderJSON } = useContext(WorkspaceContext);
|
|
12
|
+
return layoutPlaceholderJSON;
|
|
13
|
+
};
|
|
14
|
+
export { WorkspaceContext, usePlaceholderJSON };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const getCookieValue = (name)=>{
|
|
2
|
+
if (globalThis.document?.cookie !== void 0) return globalThis.document.cookie.split("; ").find((row)=>row.startsWith(`${name}=`))?.split("=")[1];
|
|
3
|
+
};
|
|
4
|
+
function setCookieValue(name, value, days) {
|
|
5
|
+
let expires = "";
|
|
6
|
+
if ("number" == typeof days) {
|
|
7
|
+
const date = new Date();
|
|
8
|
+
date.setTime(date.getTime() + 24 * days * 3600000);
|
|
9
|
+
expires = "; expires=" + date.toUTCString();
|
|
10
|
+
}
|
|
11
|
+
const encodedName = encodeURIComponent(name);
|
|
12
|
+
const encodedValue = encodeURIComponent(value);
|
|
13
|
+
document.cookie = `${encodedName}=${encodedValue}${expires};`;
|
|
14
|
+
}
|
|
15
|
+
export { getCookieValue, setCookieValue };
|
package/src/css-utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const DataEditingContext = /*#__PURE__*/ createContext(void 0);
|
|
4
|
+
const DataEditingProvider = ({ children, editSession })=>/*#__PURE__*/ jsx(DataEditingContext.Provider, {
|
|
5
|
+
value: editSession,
|
|
6
|
+
children: children
|
|
7
|
+
});
|
|
8
|
+
function useEditSession(throwIfUnavailable = false) {
|
|
9
|
+
const editSession = useContext(DataEditingContext);
|
|
10
|
+
if (void 0 !== editSession) return editSession;
|
|
11
|
+
if (throwIfUnavailable) throw Error("[useEditSession] no DataEditingContext in scope. You need to enclose editable component(s) with DataEditingProvider");
|
|
12
|
+
}
|
|
13
|
+
export { DataEditingProvider, useEditSession };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@salt-ds/core";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
const EditButtons = ({ editSession, onCancel, onSave })=>{
|
|
5
|
+
const [editState, setEditState] = useState("clean");
|
|
6
|
+
const handleSave = useCallback(()=>{
|
|
7
|
+
onSave("stale" === editState);
|
|
8
|
+
}, [
|
|
9
|
+
editState,
|
|
10
|
+
onSave
|
|
11
|
+
]);
|
|
12
|
+
useMemo(()=>{
|
|
13
|
+
editSession?.on("editState", setEditState);
|
|
14
|
+
}, [
|
|
15
|
+
editSession
|
|
16
|
+
]);
|
|
17
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ jsx(Button, {
|
|
20
|
+
disabled: "clean" === editState || "invalid" === editState,
|
|
21
|
+
onClick: handleSave,
|
|
22
|
+
sentiment: "accented",
|
|
23
|
+
children: "stale" === editState ? "Save (force)" : "Save"
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ jsx(Button, {
|
|
26
|
+
onClick: onCancel,
|
|
27
|
+
children: "Cancel"
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
export { EditButtons };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
|
+
const EditModeContext = /*#__PURE__*/ createContext({
|
|
4
|
+
isEditMode: false,
|
|
5
|
+
setEditMode: ()=>"EditModeProvider in place"
|
|
6
|
+
});
|
|
7
|
+
const EditModeProvider = ({ children })=>{
|
|
8
|
+
const [isEditMode, setEditMode] = useState(false);
|
|
9
|
+
return /*#__PURE__*/ jsx(EditModeContext.Provider, {
|
|
10
|
+
value: {
|
|
11
|
+
isEditMode,
|
|
12
|
+
setEditMode
|
|
13
|
+
},
|
|
14
|
+
children: children
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const useEditMode = ()=>useContext(EditModeContext);
|
|
18
|
+
export { EditModeProvider, useEditMode };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { EventEmitter } from "../event-emitter.js";
|
|
2
|
+
import { isRpcError } from "../protocol-message-utils.js";
|
|
3
|
+
class EditError extends Error {
|
|
4
|
+
}
|
|
5
|
+
class StaleUpdateError extends Error {
|
|
6
|
+
}
|
|
7
|
+
class EditSession extends EventEmitter {
|
|
8
|
+
#rowEdits = new Map();
|
|
9
|
+
#editCount = 0;
|
|
10
|
+
#invalidCount = 0;
|
|
11
|
+
#sourceTableDataSource;
|
|
12
|
+
#sessionDataSource;
|
|
13
|
+
#inEditMode = false;
|
|
14
|
+
constructor(dataSource){
|
|
15
|
+
super();
|
|
16
|
+
this.#sourceTableDataSource = dataSource;
|
|
17
|
+
}
|
|
18
|
+
get editCount() {
|
|
19
|
+
return this.#editCount;
|
|
20
|
+
}
|
|
21
|
+
set editCount(val) {
|
|
22
|
+
if (val !== this.#editCount) {
|
|
23
|
+
const oldCount = this.#editCount;
|
|
24
|
+
this.#editCount = val;
|
|
25
|
+
if (0 === val) this.emit("editState", "clean");
|
|
26
|
+
else if (0 === oldCount) this.emit("editState", "dirty");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
get invalidCount() {
|
|
30
|
+
return this.#invalidCount;
|
|
31
|
+
}
|
|
32
|
+
set invalidCount(val) {
|
|
33
|
+
if (val !== this.#invalidCount) {
|
|
34
|
+
const oldCount = this.#invalidCount;
|
|
35
|
+
this.#invalidCount = val;
|
|
36
|
+
if (0 === val) this.emit("editState", 0 === this.#editCount ? "clean" : "dirty");
|
|
37
|
+
else if (0 === oldCount) this.emit("editState", "invalid");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
this.#rowEdits.clear();
|
|
42
|
+
this.#editCount = 0;
|
|
43
|
+
this.#inEditMode = false;
|
|
44
|
+
}
|
|
45
|
+
async begin(editSessionMode) {
|
|
46
|
+
try {
|
|
47
|
+
this.#inEditMode = true;
|
|
48
|
+
const sessionDataSource = await this.#sourceTableDataSource?.beginEditSession?.(editSessionMode);
|
|
49
|
+
this.#sessionDataSource = sessionDataSource;
|
|
50
|
+
return sessionDataSource;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this.#inEditMode = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
get dataSource() {
|
|
56
|
+
return this.#sessionDataSource ?? this.#sourceTableDataSource;
|
|
57
|
+
}
|
|
58
|
+
async end(saveChanges = false, force = false) {
|
|
59
|
+
try {
|
|
60
|
+
if (this.#inEditMode) {
|
|
61
|
+
await this.dataSource?.endEditSession?.(saveChanges, force);
|
|
62
|
+
this.clear();
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e instanceof StaleUpdateError) this.emit("editState", "stale");
|
|
66
|
+
else console.error(`[EditSession] ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
get inEditMode() {
|
|
70
|
+
return this.#inEditMode;
|
|
71
|
+
}
|
|
72
|
+
get editState() {
|
|
73
|
+
return 0 === this.editCount ? "clean" : "dirty";
|
|
74
|
+
}
|
|
75
|
+
getOrCreateRowEdits(key) {
|
|
76
|
+
const rowEditDetails = this.#rowEdits.get(key);
|
|
77
|
+
if (rowEditDetails) return rowEditDetails;
|
|
78
|
+
{
|
|
79
|
+
const rowEditDetails = {
|
|
80
|
+
cellEdits: new Map()
|
|
81
|
+
};
|
|
82
|
+
this.#rowEdits.set(key, rowEditDetails);
|
|
83
|
+
return rowEditDetails;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
storeCellEdit(cellEdits, column, originalValue, editedValue, isValid) {
|
|
87
|
+
const cellEdit = cellEdits.get(column);
|
|
88
|
+
if (cellEdit) {
|
|
89
|
+
if (cellEdit.originalValue === editedValue) {
|
|
90
|
+
cellEdits.delete(column);
|
|
91
|
+
cellEdit.isDeleted = true;
|
|
92
|
+
if (cellEdit.isValid) this.editCount -= 1;
|
|
93
|
+
else this.invalidCount -= 1;
|
|
94
|
+
} else if (isValid && false === cellEdit.isValid) {
|
|
95
|
+
cellEdit.isValid = true;
|
|
96
|
+
cellEdit.editedValue = editedValue;
|
|
97
|
+
this.#invalidCount -= 1;
|
|
98
|
+
this.editCount += 1;
|
|
99
|
+
}
|
|
100
|
+
return cellEdit;
|
|
101
|
+
}
|
|
102
|
+
{
|
|
103
|
+
const cellEdit = {
|
|
104
|
+
originalValue,
|
|
105
|
+
editedValue,
|
|
106
|
+
isValid
|
|
107
|
+
};
|
|
108
|
+
cellEdits.set(column, cellEdit);
|
|
109
|
+
if (isValid) this.editCount += 1;
|
|
110
|
+
return cellEdit;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async commit(key, columnName, originalValue, typedValue, isValid) {
|
|
114
|
+
if (!this.#inEditMode) throw new Error("No edit session in progress");
|
|
115
|
+
const rowEditDetails = this.getOrCreateRowEdits(key);
|
|
116
|
+
if (isValid) {
|
|
117
|
+
const { cellEdits } = rowEditDetails;
|
|
118
|
+
const cellEdit = this.storeCellEdit(cellEdits, columnName, originalValue, typedValue, isValid);
|
|
119
|
+
if (cellEdit.isDeleted) {
|
|
120
|
+
if (0 === rowEditDetails.cellEdits.size) this.#rowEdits.delete(key);
|
|
121
|
+
}
|
|
122
|
+
if (this.dataSource?.editCell) {
|
|
123
|
+
const response = await this.dataSource.editCell(key, columnName, typedValue);
|
|
124
|
+
if (isRpcError(response)) {
|
|
125
|
+
cellEdit.isValid = false;
|
|
126
|
+
this.invalidCount += 1;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
editedDuringCurrentSession: cellEdit.originalValue !== typedValue,
|
|
130
|
+
...response
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
const { cellEdits } = rowEditDetails;
|
|
135
|
+
let cellEdit = cellEdits.get(columnName);
|
|
136
|
+
if (cellEdit && false !== cellEdit.isValid) {
|
|
137
|
+
cellEdit.isValid = false;
|
|
138
|
+
this.invalidCount += 1;
|
|
139
|
+
} else if (void 0 === cellEdit) {
|
|
140
|
+
cellEdit = this.storeCellEdit(cellEdits, columnName, originalValue, typedValue, isValid);
|
|
141
|
+
this.invalidCount += 1;
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
editedDuringCurrentSession: false
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export { EditError, EditSession, StaleUpdateError };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatDate } from "../date/index.js";
|
|
3
|
+
const isInlineEditingSession = (editSessionMode)=>editSessionMode.startsWith("inline");
|
|
4
|
+
const timeFormatter = formatDate({
|
|
5
|
+
time: "hh:mm:ss"
|
|
6
|
+
});
|
|
7
|
+
const getVuuEditMessage = (dataRow, column, originalValue)=>{
|
|
8
|
+
const vuuMsg = dataRow.vuuMsg;
|
|
9
|
+
if ("string" == typeof vuuMsg && "" !== vuuMsg) {
|
|
10
|
+
const columnMessages = vuuMsg.split(",");
|
|
11
|
+
const msgForCol = columnMessages.find((msg)=>msg.startsWith(`${column.name}:`));
|
|
12
|
+
if (msgForCol) {
|
|
13
|
+
const [, value, updatedValue, ts] = msgForCol.split(":");
|
|
14
|
+
const updateTime = timeFormatter(new Date(parseInt(ts)));
|
|
15
|
+
return /*#__PURE__*/ jsxs("span", {
|
|
16
|
+
children: [
|
|
17
|
+
"Update rejected. Original value ",
|
|
18
|
+
/*#__PURE__*/ jsx("b", {
|
|
19
|
+
children: originalValue
|
|
20
|
+
}),
|
|
21
|
+
" could not be updated to ",
|
|
22
|
+
/*#__PURE__*/ jsx("b", {
|
|
23
|
+
children: value
|
|
24
|
+
}),
|
|
25
|
+
". It was updated to ",
|
|
26
|
+
/*#__PURE__*/ jsx("b", {
|
|
27
|
+
children: updatedValue
|
|
28
|
+
}),
|
|
29
|
+
" at ",
|
|
30
|
+
updateTime,
|
|
31
|
+
"."
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export { getVuuEditMessage, isInlineEditingSession };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useData } from "../context-definitions/DataProvider.js";
|
|
3
|
+
import { useLayoutEffectSkipFirst } from "../useLayoutEffectSkipFirst.js";
|
|
4
|
+
import { EditSession } from "./EditSession.js";
|
|
5
|
+
const useEditableTable = ({ columns, dataSource: dataSourceProp, editSessionMode = "inline-all-rows", isEditMode, onCancel, onSave, table })=>{
|
|
6
|
+
const { VuuDataSource } = useData();
|
|
7
|
+
const [sessionDataSource, setSessionDataSource] = useState(void 0);
|
|
8
|
+
useLayoutEffectSkipFirst(()=>{
|
|
9
|
+
console.warn("[useEditableTable] columns and or table changed");
|
|
10
|
+
}, [
|
|
11
|
+
columns,
|
|
12
|
+
table
|
|
13
|
+
]);
|
|
14
|
+
const dataSource = useMemo(()=>{
|
|
15
|
+
if (dataSourceProp) return dataSourceProp;
|
|
16
|
+
if (table) return new VuuDataSource({
|
|
17
|
+
columns,
|
|
18
|
+
table
|
|
19
|
+
});
|
|
20
|
+
throw Error("useEditableTable unable to provide DataSource, neither dataSource nor table available as props");
|
|
21
|
+
}, [
|
|
22
|
+
VuuDataSource,
|
|
23
|
+
columns,
|
|
24
|
+
dataSourceProp,
|
|
25
|
+
table
|
|
26
|
+
]);
|
|
27
|
+
const editSession = useMemo(()=>new EditSession(dataSource), [
|
|
28
|
+
dataSource
|
|
29
|
+
]);
|
|
30
|
+
const handleCancel = useCallback(()=>{
|
|
31
|
+
try {
|
|
32
|
+
editSession.end();
|
|
33
|
+
onCancel();
|
|
34
|
+
} catch (e) {}
|
|
35
|
+
}, [
|
|
36
|
+
editSession,
|
|
37
|
+
onCancel
|
|
38
|
+
]);
|
|
39
|
+
const handleSave = useCallback(async (force = false)=>{
|
|
40
|
+
dataSource.resume?.();
|
|
41
|
+
try {
|
|
42
|
+
await editSession.end(true, force);
|
|
43
|
+
if (false === editSession.inEditMode) onSave();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.log(`[useEditableTable] handleSave ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
}, [
|
|
48
|
+
dataSource,
|
|
49
|
+
editSession,
|
|
50
|
+
onSave
|
|
51
|
+
]);
|
|
52
|
+
useMemo(async ()=>{
|
|
53
|
+
console.log(`[useEditableTable] editMode ${isEditMode}`);
|
|
54
|
+
if (isEditMode) try {
|
|
55
|
+
const sessionDataSource = await editSession.begin(editSessionMode);
|
|
56
|
+
if (sessionDataSource) setSessionDataSource(sessionDataSource);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(e);
|
|
59
|
+
onCancel();
|
|
60
|
+
}
|
|
61
|
+
else await editSession.end();
|
|
62
|
+
}, [
|
|
63
|
+
editSession,
|
|
64
|
+
editSessionMode,
|
|
65
|
+
isEditMode,
|
|
66
|
+
onCancel
|
|
67
|
+
]);
|
|
68
|
+
return {
|
|
69
|
+
dataSource,
|
|
70
|
+
editSession,
|
|
71
|
+
onCancel: handleCancel,
|
|
72
|
+
onSave: handleSave,
|
|
73
|
+
sessionDataSource
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export { useEditableTable };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const UP1 = "up1";
|
|
2
|
+
const UP2 = "up2";
|
|
3
|
+
const DOWN1 = "down1";
|
|
4
|
+
const DOWN2 = "down2";
|
|
5
|
+
const decimalPattern = /^-?[0-9]*\.[0-9]*$/;
|
|
6
|
+
const stringIsValidInt = (val)=>parseInt(val, 10).toString() === val;
|
|
7
|
+
const stringIsValidDecimal = (val)=>stringIsValidInt(val) || decimalPattern.test(val);
|
|
8
|
+
const stringIsValidNumber = (val)=>stringIsValidInt(val) || stringIsValidDecimal(val);
|
|
9
|
+
const numericTypeOfStringValue = (val)=>stringIsValidInt(val) ? "int" : stringIsValidDecimal(val) ? "double" : "NaN";
|
|
10
|
+
const isValidNumber = (n)=>"number" == typeof n && isFinite(n);
|
|
11
|
+
const EMPTY = {};
|
|
12
|
+
const shallowEquals = (o1 = EMPTY, o2 = EMPTY)=>{
|
|
13
|
+
const props1 = Object.keys(o1);
|
|
14
|
+
const props2 = Object.keys(o2);
|
|
15
|
+
return props1.length === props2.length && props1.every((key)=>o1[key] === o2[key]);
|
|
16
|
+
};
|
|
17
|
+
function getMovingValueDirection(newValue, direction, prevValue, decimalPlaces) {
|
|
18
|
+
if (void 0 === newValue) return "";
|
|
19
|
+
if (!isFinite(newValue) || void 0 === prevValue || void 0 === direction) return "";
|
|
20
|
+
{
|
|
21
|
+
let diff = newValue - prevValue;
|
|
22
|
+
if (diff) {
|
|
23
|
+
if ("number" == typeof decimalPlaces) diff = newValue.toFixed(decimalPlaces) - prevValue.toFixed(decimalPlaces);
|
|
24
|
+
}
|
|
25
|
+
if (!diff) return "";
|
|
26
|
+
if ("" === direction) if (diff < 0) return DOWN1;
|
|
27
|
+
else return UP1;
|
|
28
|
+
if (diff > 0) if (direction === DOWN1 || direction === DOWN2 || direction === UP2) return UP1;
|
|
29
|
+
else return UP2;
|
|
30
|
+
else if (direction === UP1 || direction === UP2 || direction === DOWN2) return DOWN1;
|
|
31
|
+
else return DOWN2;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function asInteger(value, defaultValue) {
|
|
35
|
+
if ("number" == typeof value) return Number.isInteger(value) ? value : value < 0 ? Math.ceil(value) : Math.floor(value);
|
|
36
|
+
{
|
|
37
|
+
const integerValue = parseInt(value ?? "");
|
|
38
|
+
if (!isNaN(integerValue)) return integerValue;
|
|
39
|
+
if ("number" == typeof defaultValue) return defaultValue;
|
|
40
|
+
throw Error(`parseIndex invalid value ${value}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const dataDescriptorTypeToVuuRowDataItemType = ({ serverDataType = "string" })=>{
|
|
44
|
+
switch(serverDataType){
|
|
45
|
+
case "double":
|
|
46
|
+
case "int":
|
|
47
|
+
case "long":
|
|
48
|
+
return "number";
|
|
49
|
+
case "boolean":
|
|
50
|
+
return "boolean";
|
|
51
|
+
default:
|
|
52
|
+
return "string";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export { DOWN1, DOWN2, UP1, UP2, asInteger, dataDescriptorTypeToVuuRowDataItemType, getMovingValueDirection, isValidNumber, numericTypeOfStringValue, shallowEquals, stringIsValidDecimal, stringIsValidInt, stringIsValidNumber };
|