@vuu-ui/vuu-data-editing 5.0.0-alpha.1 → 5.0.0-alpha.3
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/README.md +36 -0
- package/package.json +7 -6
- package/src/DirectEditSession.js +8 -1
- package/src/EditButtons.js +6 -5
- package/src/EditModeProvider.js +14 -5
- package/src/EditSession.js +37 -6
- package/src/edit-control/EditControlCheckbox.js +25 -0
- package/src/edit-control/EditControlDropdown.js +34 -0
- package/src/edit-control/EditControlLookupDropdown.js +31 -0
- package/src/edit-control/get-data-item-edit-control.js +139 -0
- package/src/edit-control/index.js +4 -0
- package/src/edit-errors.js +7 -0
- package/src/edit-field/EditField.js +101 -19
- package/src/edit-field/useEditField.js +1 -0
- package/src/edit-form/CreateRowForm.js +37 -0
- package/src/edit-form/EditForm.css.js +32 -0
- package/src/edit-form/EditForm.js +85 -0
- package/src/edit-form/EditFormButtons.js +48 -0
- package/src/edit-form/EditFormContext.js +4 -0
- package/src/edit-form/index.js +4 -0
- package/src/form-fields.js +40 -0
- package/src/index.js +9 -0
- package/src/lookup-values/useLookupOptions.js +106 -0
- package/src/useConfirmDiscard.js +34 -0
- package/src/useCustomEditField.js +76 -0
- package/src/useEditForm.js +306 -0
- package/src/useEditSessionState.js +72 -0
- package/src/useEditable.js +26 -9
- package/src/useEditableColumns.js +105 -0
- package/src/useEditableTable.js +21 -8
- package/types/DirectEditSession.d.ts +6 -1
- package/types/EditButtons.d.ts +20 -3
- package/types/EditModeProvider.d.ts +10 -2
- package/types/EditSession.d.ts +15 -0
- package/types/edit-control/EditControlCheckbox.d.ts +8 -0
- package/types/edit-control/EditControlDropdown.d.ts +14 -0
- package/types/edit-control/EditControlLookupDropdown.d.ts +13 -0
- package/types/edit-control/get-data-item-edit-control.d.ts +40 -0
- package/types/edit-control/index.d.ts +4 -0
- package/types/edit-errors.d.ts +9 -0
- package/types/edit-field/EditField.d.ts +37 -3
- package/types/edit-field/useEditField.d.ts +1 -0
- package/types/edit-form/CreateRowForm.d.ts +29 -0
- package/types/edit-form/EditForm.d.ts +36 -0
- package/types/edit-form/EditFormButtons.d.ts +18 -0
- package/types/edit-form/EditFormContext.d.ts +59 -0
- package/types/edit-form/index.d.ts +4 -0
- package/types/form-fields.d.ts +32 -0
- package/types/index.d.ts +12 -2
- package/types/lookup-values/useLookupOptions.d.ts +35 -0
- package/types/useConfirmDiscard.d.ts +23 -0
- package/types/useCustomEditField.d.ts +51 -0
- package/types/useEditForm.d.ts +127 -0
- package/types/useEditSessionState.d.ts +27 -0
- package/types/useEditable.d.ts +30 -3
- package/types/useEditableColumns.d.ts +79 -0
- package/types/useEditableTable.d.ts +18 -3
package/README.md
CHANGED
|
@@ -4,6 +4,41 @@ Shared data editing APIs, state management, and React components for VUU UI.
|
|
|
4
4
|
|
|
5
5
|
This package coordinates edit-session state with editable data sources (both in-memory and remote Vuu session tables) and provides React hooks and UI controls for table editing.
|
|
6
6
|
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Most applications don't need to assemble the pieces in this package by hand.
|
|
10
|
+
`@vuu-ui/vuu-table-extras` provides ready-made patterns built on it:
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { EditableTable } from "@vuu-ui/vuu-table-extras";
|
|
14
|
+
|
|
15
|
+
// Edit options live on the column descriptors, e.g. { name: "ric", editable: false }
|
|
16
|
+
<EditableTable
|
|
17
|
+
allowDelete
|
|
18
|
+
config={{ columns }}
|
|
19
|
+
dataSource={dataSource}
|
|
20
|
+
showInlineAddRow
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
// The selected row in a generated form beside the table
|
|
24
|
+
<EditableTable config={{ columns }} dataSource={dataSource} editStrategy="form" />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the Data Editing guides in the showcase (_Docs / DataEditing_, source in
|
|
28
|
+
[`showcase/src/examples/Docs/DataEditing`](../../showcase/src/examples/Docs/DataEditing/)), starting
|
|
29
|
+
with [Getting started](../../showcase/src/examples/Docs/DataEditing/01-GettingStarted.mdx), and the
|
|
30
|
+
_DataEditing_ examples in the showcase.
|
|
31
|
+
|
|
32
|
+
## Layers
|
|
33
|
+
|
|
34
|
+
| Layer | Use it when | APIs |
|
|
35
|
+
| ----- | ----------- | ---- |
|
|
36
|
+
| Pattern components (`@vuu-ui/vuu-table-extras`) | A standard editing UI fits. | `EditableTable` (inline or form editing), `useBulkEditDialog` |
|
|
37
|
+
| Composable hooks and components (this package) | You need your own layout or behaviour. | `useEditableTable`, `useEditableColumns`, `useEditForm`, `EditForm`, `CreateRowForm`, `EditButtons`, `useConfirmDiscard`, `useEditSessionState`, `useLookupOptions` |
|
|
38
|
+
| Primitives (this package) | You are building a new pattern. | `EditSession`, `DirectEditSession`, `DataEditingProvider`, `EditModeProvider` |
|
|
39
|
+
|
|
40
|
+
The rest of this document describes the primitives and how they work.
|
|
41
|
+
|
|
7
42
|
---
|
|
8
43
|
|
|
9
44
|
## Table of Contents
|
|
@@ -155,6 +190,7 @@ A cell is considered edited only when `originalValue !== editedValue && isValid
|
|
|
155
190
|
- On success, extracts the row key directly from the response map (`response.data.key`) and records it in `#addedRowKeys`.
|
|
156
191
|
- Increments `addCount`.
|
|
157
192
|
- Resets new-row form draft and increments `draftRevision`.
|
|
193
|
+
5. **Rejection**: if the server rejects the row, the draft is kept and the message is stored in `errors` under `rowErrorColumn` (the final column, where an inline add row shows it). Forms show it in their error banner. The next change to the draft clears it.
|
|
158
194
|
|
|
159
195
|
### Row Deletions (`deleteSelectedRows`)
|
|
160
196
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.0.0-alpha.
|
|
2
|
+
"version": "5.0.0-alpha.3",
|
|
3
3
|
"description": "Shared data editing APIs and React components for VUU UI",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"author": "heswell",
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@salt-ds/core": "1.74.0",
|
|
15
|
-
"@vuu-ui/
|
|
16
|
-
"@vuu-ui/vuu-
|
|
15
|
+
"@vuu-ui/core": "5.0.0-alpha.3",
|
|
16
|
+
"@vuu-ui/vuu-ui-controls": "5.0.0-alpha.3",
|
|
17
|
+
"@vuu-ui/vuu-utils": "5.0.0-alpha.3"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
|
-
"@vuu-ui/vuu-data-types": "5.0.0-alpha.
|
|
20
|
-
"@vuu-ui/vuu-protocol-types": "5.0.0-alpha.
|
|
21
|
-
"@vuu-ui/vuu-table-types": "5.0.0-alpha.
|
|
20
|
+
"@vuu-ui/vuu-data-types": "5.0.0-alpha.3",
|
|
21
|
+
"@vuu-ui/vuu-protocol-types": "5.0.0-alpha.3",
|
|
22
|
+
"@vuu-ui/vuu-table-types": "5.0.0-alpha.3"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"react": "^19.2.3"
|
package/src/DirectEditSession.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from "@vuu-ui/vuu-utils";
|
|
2
2
|
class DirectEditSession extends EventEmitter {
|
|
3
3
|
#dataSource;
|
|
4
|
-
|
|
4
|
+
#validate;
|
|
5
|
+
constructor({ dataSource, validate }){
|
|
5
6
|
super();
|
|
6
7
|
this.#dataSource = dataSource;
|
|
8
|
+
this.#validate = validate;
|
|
7
9
|
}
|
|
8
10
|
get dataSource() {
|
|
9
11
|
return this.#dataSource;
|
|
@@ -25,6 +27,11 @@ class DirectEditSession extends EventEmitter {
|
|
|
25
27
|
errorMessage: "Invalid value",
|
|
26
28
|
type: "ERROR_RESULT"
|
|
27
29
|
};
|
|
30
|
+
const errorMessage = this.#validate?.(key, columnName, typedValue);
|
|
31
|
+
if (errorMessage) return {
|
|
32
|
+
errorMessage,
|
|
33
|
+
type: "ERROR_RESULT"
|
|
34
|
+
};
|
|
28
35
|
const editCell = this.#dataSource.editCell;
|
|
29
36
|
if (void 0 === editCell) throw Error("[DirectEditSession] datasource does not support editCell");
|
|
30
37
|
const response = await editCell.call(this.#dataSource, key, columnName, typedValue);
|
package/src/EditButtons.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button } from "@salt-ds/core";
|
|
3
3
|
import { useCallback } from "react";
|
|
4
|
-
import {
|
|
5
|
-
const EditButtons = ({ canCancel, canSave:
|
|
6
|
-
const editState =
|
|
7
|
-
const
|
|
4
|
+
import { useEditSessionState } from "./useEditSessionState.js";
|
|
5
|
+
const EditButtons = ({ canCancel: canCancelProp, canSave: canSaveProp, confirmCancel, confirmSave, editSession, hasSelection = false, onCancel, onDelete, onSave, saveLabel = "Save" })=>{
|
|
6
|
+
const { canCancel: sessionCanCancel, canSave: sessionCanSave, editState, isDirty } = useEditSessionState(editSession);
|
|
7
|
+
const canSave = false !== canSaveProp && sessionCanSave;
|
|
8
|
+
const canCancel = canCancelProp ?? sessionCanCancel;
|
|
8
9
|
const handleSave = useCallback(async ()=>{
|
|
9
10
|
if (confirmSave) {
|
|
10
11
|
const confirmed = await confirmSave();
|
|
@@ -36,7 +37,7 @@ const EditButtons = ({ canCancel, canSave: _canSave, confirmCancel, confirmSave,
|
|
|
36
37
|
children: "Delete"
|
|
37
38
|
}),
|
|
38
39
|
/*#__PURE__*/ jsx(Button, {
|
|
39
|
-
disabled: !
|
|
40
|
+
disabled: !canSave,
|
|
40
41
|
onClick: handleSave,
|
|
41
42
|
sentiment: "accented",
|
|
42
43
|
children: "stale" === editState ? `${saveLabel} (force)` : saveLabel
|
package/src/EditModeProvider.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
let warnedMissingProvider = false;
|
|
3
4
|
const EditModeContext = /*#__PURE__*/ createContext({
|
|
4
5
|
isEditMode: false,
|
|
5
|
-
setEditMode: ()=>
|
|
6
|
+
setEditMode: ()=>{
|
|
7
|
+
if ("production" !== process.env.NODE_ENV && !warnedMissingProvider) {
|
|
8
|
+
warnedMissingProvider = true;
|
|
9
|
+
console.warn("[useEditMode] setEditMode called with no EditModeProvider in scope, edit mode will not change");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
6
12
|
});
|
|
7
13
|
const EditModeProvider = ({ children, isEditMode: isEditModeProp = false })=>{
|
|
8
14
|
const [isEditMode, setEditMode] = useState(isEditModeProp);
|
|
@@ -11,11 +17,14 @@ const EditModeProvider = ({ children, isEditMode: isEditModeProp = false })=>{
|
|
|
11
17
|
}, [
|
|
12
18
|
isEditModeProp
|
|
13
19
|
]);
|
|
14
|
-
|
|
15
|
-
value: {
|
|
20
|
+
const value = useMemo(()=>({
|
|
16
21
|
isEditMode,
|
|
17
22
|
setEditMode
|
|
18
|
-
},
|
|
23
|
+
}), [
|
|
24
|
+
isEditMode
|
|
25
|
+
]);
|
|
26
|
+
return /*#__PURE__*/ jsx(EditModeContext.Provider, {
|
|
27
|
+
value: value,
|
|
19
28
|
children: children
|
|
20
29
|
});
|
|
21
30
|
};
|
package/src/EditSession.js
CHANGED
|
@@ -145,19 +145,31 @@ class EditSession extends EventEmitter {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
setNewRowValue(column, value) {
|
|
148
|
+
const { rowErrorColumn, ...newRowState } = this.#newRowState;
|
|
148
149
|
const errors = {
|
|
149
|
-
...
|
|
150
|
+
...newRowState.errors
|
|
150
151
|
};
|
|
151
152
|
delete errors[column];
|
|
153
|
+
if (void 0 !== rowErrorColumn) delete errors[rowErrorColumn];
|
|
152
154
|
this.#setNewRowState({
|
|
153
|
-
...
|
|
155
|
+
...newRowState,
|
|
154
156
|
errors,
|
|
155
157
|
values: {
|
|
156
|
-
...
|
|
158
|
+
...newRowState.values,
|
|
157
159
|
[column]: value
|
|
158
160
|
}
|
|
159
161
|
});
|
|
160
162
|
}
|
|
163
|
+
clearNewRow() {
|
|
164
|
+
this.#setNewRowState({
|
|
165
|
+
columns: this.#newRowState.columns,
|
|
166
|
+
draftRevision: this.#newRowState.draftRevision + 1,
|
|
167
|
+
errors: {},
|
|
168
|
+
requiredColumns: this.#newRowState.requiredColumns,
|
|
169
|
+
submitting: false,
|
|
170
|
+
values: {}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
161
173
|
async addNewRow() {
|
|
162
174
|
const requiredColumns = this.#newRowState.requiredColumns ?? this.#newRowState.columns;
|
|
163
175
|
const missingErrors = Object.fromEntries(requiredColumns.filter((column)=>{
|
|
@@ -167,8 +179,13 @@ class EditSession extends EventEmitter {
|
|
|
167
179
|
column,
|
|
168
180
|
"Value required"
|
|
169
181
|
]));
|
|
170
|
-
const
|
|
171
|
-
|
|
182
|
+
const { rowErrorColumn, ...newRowState } = this.#newRowState;
|
|
183
|
+
const { [rowErrorColumn ?? ""]: _serverError, ...fieldErrors } = newRowState.errors;
|
|
184
|
+
const errors = void 0 === rowErrorColumn ? {
|
|
185
|
+
...newRowState.errors,
|
|
186
|
+
...missingErrors
|
|
187
|
+
} : {
|
|
188
|
+
...fieldErrors,
|
|
172
189
|
...missingErrors
|
|
173
190
|
};
|
|
174
191
|
if (Object.keys(errors).length > 0) {
|
|
@@ -186,7 +203,8 @@ class EditSession extends EventEmitter {
|
|
|
186
203
|
type: "SUCCESS_RESULT"
|
|
187
204
|
};
|
|
188
205
|
this.#setNewRowState({
|
|
189
|
-
...
|
|
206
|
+
...newRowState,
|
|
207
|
+
errors,
|
|
190
208
|
submitting: true
|
|
191
209
|
});
|
|
192
210
|
try {
|
|
@@ -200,6 +218,7 @@ class EditSession extends EventEmitter {
|
|
|
200
218
|
errors: finalColumn ? {
|
|
201
219
|
[finalColumn]: response.errorMessage
|
|
202
220
|
} : this.#newRowState.errors,
|
|
221
|
+
rowErrorColumn: finalColumn,
|
|
203
222
|
submitting: false
|
|
204
223
|
});
|
|
205
224
|
return response;
|
|
@@ -221,6 +240,7 @@ class EditSession extends EventEmitter {
|
|
|
221
240
|
errors: finalColumn ? {
|
|
222
241
|
[finalColumn]: errorMessage
|
|
223
242
|
} : this.#newRowState.errors,
|
|
243
|
+
rowErrorColumn: finalColumn,
|
|
224
244
|
submitting: false
|
|
225
245
|
});
|
|
226
246
|
return {
|
|
@@ -282,6 +302,14 @@ class EditSession extends EventEmitter {
|
|
|
282
302
|
hasRowChanges(key) {
|
|
283
303
|
return this.#rowEdits.has(key);
|
|
284
304
|
}
|
|
305
|
+
getEditedValues(key) {
|
|
306
|
+
const cellEdits = this.#rowEdits.get(key)?.cellEdits;
|
|
307
|
+
if (!cellEdits) return {};
|
|
308
|
+
return Object.fromEntries(Array.from(cellEdits, ([column, { editedValue }])=>[
|
|
309
|
+
column,
|
|
310
|
+
editedValue
|
|
311
|
+
]));
|
|
312
|
+
}
|
|
285
313
|
isCellEdited(key, columnName) {
|
|
286
314
|
const cellEdit = this.#rowEdits.get(key)?.cellEdits.get(columnName);
|
|
287
315
|
return cellEdit?.isValid === true && cellEdit.originalValue !== cellEdit.editedValue;
|
|
@@ -424,6 +452,9 @@ class EditSession extends EventEmitter {
|
|
|
424
452
|
get dataSource() {
|
|
425
453
|
return this.#sessionDataSource ?? this.#sourceTableDataSource;
|
|
426
454
|
}
|
|
455
|
+
get sourceDataSource() {
|
|
456
|
+
return this.#sourceTableDataSource;
|
|
457
|
+
}
|
|
427
458
|
get sessionDataSource() {
|
|
428
459
|
return this.#sessionDataSource;
|
|
429
460
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Checkbox } from "@salt-ds/core";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
const isChecked = (value)=>true === value || "true" === value;
|
|
5
|
+
const EditControlCheckbox = ({ className, onCommit, value })=>{
|
|
6
|
+
const [checked, setChecked] = useState(isChecked(value));
|
|
7
|
+
useEffect(()=>{
|
|
8
|
+
setChecked(isChecked(value));
|
|
9
|
+
}, [
|
|
10
|
+
value
|
|
11
|
+
]);
|
|
12
|
+
const handleChange = useCallback((evt)=>{
|
|
13
|
+
setChecked(evt.target.checked);
|
|
14
|
+
onCommit(evt, evt.target.checked);
|
|
15
|
+
}, [
|
|
16
|
+
onCommit
|
|
17
|
+
]);
|
|
18
|
+
return /*#__PURE__*/ jsx(Checkbox, {
|
|
19
|
+
checked: checked,
|
|
20
|
+
className: className,
|
|
21
|
+
"data-edit-control": true,
|
|
22
|
+
onChange: handleChange
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export { EditControlCheckbox };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Dropdown, Option } from "@salt-ds/core";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
const toOption = (value)=>"string" == typeof value ? {
|
|
5
|
+
label: value,
|
|
6
|
+
value
|
|
7
|
+
} : value;
|
|
8
|
+
const EditControlDropdown = ({ className, onCommit, value, values })=>{
|
|
9
|
+
const options = values.map(toOption);
|
|
10
|
+
const selectedValue = void 0 === value ? "" : String(value);
|
|
11
|
+
const selected = options.find((option)=>option.value === selectedValue);
|
|
12
|
+
const handleSelectionChange = useCallback((evt, [option])=>{
|
|
13
|
+
if (option) onCommit(evt, option.value);
|
|
14
|
+
}, [
|
|
15
|
+
onCommit
|
|
16
|
+
]);
|
|
17
|
+
return /*#__PURE__*/ jsx(Dropdown, {
|
|
18
|
+
bordered: true,
|
|
19
|
+
className: className,
|
|
20
|
+
"data-edit-control": true,
|
|
21
|
+
"data-icon": "triangle-down",
|
|
22
|
+
onSelectionChange: handleSelectionChange,
|
|
23
|
+
placeholder: "Please select value",
|
|
24
|
+
selected: selected ? [
|
|
25
|
+
selected
|
|
26
|
+
] : [],
|
|
27
|
+
value: selected?.label ?? selectedValue,
|
|
28
|
+
children: options.map((option)=>/*#__PURE__*/ jsx(Option, {
|
|
29
|
+
value: option,
|
|
30
|
+
children: option.label
|
|
31
|
+
}, option.value))
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
export { EditControlDropdown };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useLookupValues } from "../lookup-values/useLookupValues.js";
|
|
4
|
+
import { EditControlDropdown } from "./EditControlDropdown.js";
|
|
5
|
+
const EditControlLookupDropdown = ({ className, lookup: { labelColumn, table, valueColumn }, onCommit, value })=>{
|
|
6
|
+
const optionMap = useMemo(()=>({
|
|
7
|
+
label: labelColumn,
|
|
8
|
+
value: valueColumn
|
|
9
|
+
}), [
|
|
10
|
+
labelColumn,
|
|
11
|
+
valueColumn
|
|
12
|
+
]);
|
|
13
|
+
const lookupValues = useLookupValues({
|
|
14
|
+
enabled: true,
|
|
15
|
+
optionMap,
|
|
16
|
+
table
|
|
17
|
+
});
|
|
18
|
+
const values = useMemo(()=>lookupValues.map(({ label, value })=>({
|
|
19
|
+
label,
|
|
20
|
+
value: String(value)
|
|
21
|
+
})), [
|
|
22
|
+
lookupValues
|
|
23
|
+
]);
|
|
24
|
+
return /*#__PURE__*/ jsx(EditControlDropdown, {
|
|
25
|
+
className: className,
|
|
26
|
+
onCommit: onCommit,
|
|
27
|
+
value: value,
|
|
28
|
+
values: values
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
export { EditControlLookupDropdown };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { VuuDatePicker, VuuInput, VuuTimePicker, VuuTypeaheadInput } from "@vuu-ui/vuu-ui-controls";
|
|
3
|
+
import { EpochTimestamp, getTemporalInfo, isDataValueEditable, isTimeDataValueWithMilliseconds, isValidTimeString, isValidTimeStringMillis, parseTemporalInput } from "@vuu-ui/vuu-utils";
|
|
4
|
+
import { EditControlCheckbox } from "./EditControlCheckbox.js";
|
|
5
|
+
import { EditControlDropdown } from "./EditControlDropdown.js";
|
|
6
|
+
import { EditControlLookupDropdown } from "./EditControlLookupDropdown.js";
|
|
7
|
+
const VARIANT_MIN_CHARACTER_COUNT = {
|
|
8
|
+
default: void 0,
|
|
9
|
+
pick: 0,
|
|
10
|
+
search: 1
|
|
11
|
+
};
|
|
12
|
+
const isTypeaheadEditor = (editor)=>editor?.name === "typeahead";
|
|
13
|
+
const isValueListEditor = (editor)=>Array.isArray(editor?.values);
|
|
14
|
+
const isLookupEditor = (editor)=>editor?.lookup !== void 0;
|
|
15
|
+
const isCheckboxEditor = (editor)=>editor?.name === "checkbox-cell" || editor?.name === "toggle-cell";
|
|
16
|
+
const timeOfDayPattern = /^\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?$/;
|
|
17
|
+
const toEpochTimestamp = (value, { encoding })=>{
|
|
18
|
+
if (("string" == typeof value || "number" == typeof value) && !timeOfDayPattern.test(`${value}`)) return EpochTimestamp.fromWire(value, encoding);
|
|
19
|
+
};
|
|
20
|
+
const pad = (n)=>`${n}`.padStart(2, "0");
|
|
21
|
+
function toTimeString(value, timestamp, { timeZone }, milliseconds = false) {
|
|
22
|
+
if (timestamp) {
|
|
23
|
+
const { hour, minute, second, millisecond } = timestamp.getFields(timeZone);
|
|
24
|
+
const timeString = `${pad(hour)}:${pad(minute)}:${pad(second)}`;
|
|
25
|
+
return milliseconds ? `${timeString}.${`${millisecond}`.padStart(3, "0")}` : timeString;
|
|
26
|
+
}
|
|
27
|
+
if ("string" == typeof value) if (milliseconds) {
|
|
28
|
+
const timeString = 8 === value.length ? `${value}.000` : value.slice(0, 12);
|
|
29
|
+
if (isValidTimeStringMillis(timeString)) return timeString;
|
|
30
|
+
} else {
|
|
31
|
+
const timeString = value.slice(0, 8);
|
|
32
|
+
if (isValidTimeString(timeString)) return timeString;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const getDataItemEditControl = ({ InputProps, TypeaheadProps, className, commitOnBlur, commitWhenCleared, dataDescriptor, editOperation = "update", errorMessage, onCommit, purpose = "edit", table, variant })=>{
|
|
36
|
+
const { editor } = dataDescriptor;
|
|
37
|
+
const temporalInfo = getTemporalInfo(dataDescriptor);
|
|
38
|
+
const value = InputProps?.value ?? InputProps?.inputProps?.value;
|
|
39
|
+
if (!isDataValueEditable(dataDescriptor, editOperation, true)) return /*#__PURE__*/ jsx(VuuInput, {
|
|
40
|
+
variant: "secondary",
|
|
41
|
+
...InputProps,
|
|
42
|
+
onCommit: onCommit,
|
|
43
|
+
readOnly: true,
|
|
44
|
+
"data-edit-control": true
|
|
45
|
+
});
|
|
46
|
+
if (temporalInfo?.kind === "time") {
|
|
47
|
+
const { value, onChange } = InputProps?.inputProps ?? {};
|
|
48
|
+
const baseValue = toEpochTimestamp(value, temporalInfo);
|
|
49
|
+
const handleCommitTime = (evt, timeString)=>{
|
|
50
|
+
if (baseValue) {
|
|
51
|
+
const timestamp = parseTemporalInput(timeString, temporalInfo, baseValue);
|
|
52
|
+
onCommit(evt, timestamp ? `${timestamp.toWire(temporalInfo.encoding)}` : "");
|
|
53
|
+
} else onCommit(evt, timeString);
|
|
54
|
+
};
|
|
55
|
+
return isTimeDataValueWithMilliseconds(dataDescriptor) ? /*#__PURE__*/ jsx(VuuTimePicker, {
|
|
56
|
+
className: className,
|
|
57
|
+
milliseconds: true,
|
|
58
|
+
value: "" === value ? "" : toTimeString(value, baseValue, temporalInfo, true),
|
|
59
|
+
onChange: onChange,
|
|
60
|
+
onCommit: handleCommitTime,
|
|
61
|
+
"data-edit-control": true
|
|
62
|
+
}) : /*#__PURE__*/ jsx(VuuTimePicker, {
|
|
63
|
+
className: className,
|
|
64
|
+
value: "" === value ? "" : toTimeString(value, baseValue, temporalInfo),
|
|
65
|
+
onChange: onChange,
|
|
66
|
+
onCommit: handleCommitTime,
|
|
67
|
+
"data-edit-control": true
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (temporalInfo) {
|
|
71
|
+
const baseValue = toEpochTimestamp(InputProps?.inputProps?.value, temporalInfo);
|
|
72
|
+
const handleCommitDate = (evt, startOfDayMillis)=>{
|
|
73
|
+
let timestamp = EpochTimestamp.fromMillis(startOfDayMillis);
|
|
74
|
+
if (baseValue && "datetime" === temporalInfo.kind) {
|
|
75
|
+
const { year, month, day } = timestamp.getFields(temporalInfo.timeZone);
|
|
76
|
+
const { hour, minute, second, millisecond } = baseValue.getFields(temporalInfo.timeZone);
|
|
77
|
+
timestamp = EpochTimestamp.fromFields({
|
|
78
|
+
year,
|
|
79
|
+
month,
|
|
80
|
+
day,
|
|
81
|
+
hour,
|
|
82
|
+
minute,
|
|
83
|
+
second,
|
|
84
|
+
millisecond
|
|
85
|
+
}, temporalInfo.timeZone, baseValue.subMilliNanos);
|
|
86
|
+
}
|
|
87
|
+
onCommit(evt, `${timestamp.toWire(temporalInfo.encoding)}`);
|
|
88
|
+
};
|
|
89
|
+
return /*#__PURE__*/ jsx(VuuDatePicker, {
|
|
90
|
+
className: className,
|
|
91
|
+
onCommit: handleCommitDate,
|
|
92
|
+
timeZone: temporalInfo.timeZone,
|
|
93
|
+
value: baseValue,
|
|
94
|
+
"data-edit-control": true
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if ("edit" === purpose && (isCheckboxEditor(editor) || void 0 === editor && "boolean" === dataDescriptor.serverDataType)) return /*#__PURE__*/ jsx(EditControlCheckbox, {
|
|
98
|
+
className: className,
|
|
99
|
+
onCommit: onCommit,
|
|
100
|
+
value: value
|
|
101
|
+
});
|
|
102
|
+
if (isLookupEditor(editor)) return /*#__PURE__*/ jsx(EditControlLookupDropdown, {
|
|
103
|
+
className: className,
|
|
104
|
+
lookup: editor.lookup,
|
|
105
|
+
onCommit: onCommit,
|
|
106
|
+
value: value
|
|
107
|
+
});
|
|
108
|
+
else if (isValueListEditor(editor)) return /*#__PURE__*/ jsx(EditControlDropdown, {
|
|
109
|
+
className: className,
|
|
110
|
+
onCommit: onCommit,
|
|
111
|
+
value: value,
|
|
112
|
+
values: editor.values
|
|
113
|
+
});
|
|
114
|
+
else if (table && (isTypeaheadEditor(editor) || "filter" === purpose && "string" === dataDescriptor.serverDataType)) {
|
|
115
|
+
const minCharacterCountToTriggerSuggestions = TypeaheadProps?.minCharacterCountToTriggerSuggestions ?? (isTypeaheadEditor(editor) ? editor.minCharacterCountToTriggerSuggestions : void 0) ?? VARIANT_MIN_CHARACTER_COUNT[variant ?? "default"];
|
|
116
|
+
return /*#__PURE__*/ jsx(VuuTypeaheadInput, {
|
|
117
|
+
...InputProps,
|
|
118
|
+
...TypeaheadProps,
|
|
119
|
+
className: className,
|
|
120
|
+
column: dataDescriptor.name,
|
|
121
|
+
"data-edit-control": true,
|
|
122
|
+
"data-variant": variant,
|
|
123
|
+
minCharacterCountToTriggerSuggestions: minCharacterCountToTriggerSuggestions,
|
|
124
|
+
onCommit: onCommit,
|
|
125
|
+
table: table
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return /*#__PURE__*/ jsx(VuuInput, {
|
|
129
|
+
variant: "secondary",
|
|
130
|
+
...InputProps,
|
|
131
|
+
className: className,
|
|
132
|
+
commitOnBlur: commitOnBlur,
|
|
133
|
+
commitWhenCleared: commitWhenCleared,
|
|
134
|
+
onCommit: onCommit,
|
|
135
|
+
errorMessage: errorMessage,
|
|
136
|
+
"data-edit-control": true
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
export { getDataItemEditControl };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const toError = (cause)=>cause instanceof Error ? cause : new Error(String(cause));
|
|
2
|
+
const reportEditError = (source, onError, cause, operation)=>{
|
|
3
|
+
const error = toError(cause);
|
|
4
|
+
if (onError) onError(error, operation);
|
|
5
|
+
else console.error("[%s] %s edit session failed", source, operation, error);
|
|
6
|
+
};
|
|
7
|
+
export { reportEditError, toError };
|