nexus-shared 3.1.0 → 4.0.0
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/CHANGELOG.md +2268 -446
- package/LICENSE +21 -21
- package/README.md +1705 -1084
- package/dist/Client.Index.d.ts +17 -0
- package/dist/Client.Index.js +17 -0
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Forms/ApiForm.js +19 -2
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +871 -377
- package/dist/Components/Forms/CrudActions.d.ts +136 -0
- package/dist/Components/Forms/CrudActions.js +184 -0
- package/dist/Components/Forms/CrudHistory.d.ts +66 -0
- package/dist/Components/Forms/CrudHistory.js +140 -0
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +30 -2
- package/dist/Components/Forms/CrudParts.js +56 -5
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +114 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
- package/dist/Components/Forms/CrudWorkspace.js +169 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +4 -4
- package/dist/Components/Forms/Form.js +125 -16
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +195 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/CodeBox.d.ts +69 -0
- package/dist/Components/Inputs/CodeBox.js +316 -0
- package/dist/Components/Inputs/DatePicker.js +4 -1
- package/dist/Components/Inputs/DateRangePicker.js +4 -1
- package/dist/Components/Inputs/DateTimePicker.js +4 -1
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -5
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +10 -11
- package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
- package/dist/Components/Inputs/InputRenderer.js +39 -31
- package/dist/Components/Inputs/NumberBox.js +4 -2
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +70 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +1 -1
- package/dist/Components/Inputs/RowsInput.js +18 -3
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +29 -29
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +14 -2
- package/dist/Components/Inputs/TextBox.js +4 -1
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TimePicker.js +4 -1
- package/dist/Components/Inputs/TreeView.d.ts +107 -6
- package/dist/Components/Inputs/TreeView.js +513 -30
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +13 -6
- package/dist/Components/Layouts/Popup.js +37 -9
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Layouts/ThemePicker.d.ts +3 -2
- package/dist/Components/Layouts/ThemePicker.js +16 -4
- package/dist/Components/Viewers/DataTable.js +205 -61
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +46 -12
- package/dist/Components/Viewers/DataTableParts.js +62 -21
- package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
- package/dist/Components/Viewers/OwnerFiles.js +589 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.d.ts +3 -34
- package/dist/Helpers/ApiModules.js +3 -3
- package/dist/Helpers/ApiRoutes.d.ts +68 -26
- package/dist/Helpers/ApiRoutes.js +31 -27
- package/dist/Helpers/ArrayHelpers.d.ts +0 -0
- package/dist/Helpers/ArrayHelpers.js +2 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/CodeHelpers.d.ts +97 -0
- package/dist/Helpers/CodeHelpers.js +332 -0
- package/dist/Helpers/CrudBackend.d.ts +114 -6
- package/dist/Helpers/CrudBackend.js +214 -11
- package/dist/Helpers/CrudColumns.d.ts +98 -0
- package/dist/Helpers/CrudColumns.js +160 -0
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +130 -6
- package/dist/Helpers/CrudHelpers.js +304 -12
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudRowForms.d.ts +15 -0
- package/dist/Helpers/CrudRowForms.js +43 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +78 -0
- package/dist/Helpers/FileAdapter.js +102 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +90 -5
- package/dist/Helpers/FormStore.js +318 -22
- package/dist/Helpers/InputConditions.d.ts +50 -0
- package/dist/Helpers/InputConditions.js +168 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +385 -0
- package/dist/Helpers/InputNarrowing.d.ts +18 -0
- package/dist/Helpers/InputNarrowing.js +67 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +162 -71
- package/dist/Helpers/MessageBuilder.js +2 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
- package/dist/Helpers/NexusDriveAdapter.js +207 -0
- package/dist/Helpers/NexusHelpers.d.ts +0 -0
- package/dist/Helpers/NexusHelpers.js +1 -0
- package/dist/Helpers/NumberHelpers.d.ts +95 -0
- package/dist/Helpers/NumberHelpers.js +126 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +47 -0
- package/dist/Helpers/RequestCache.js +132 -0
- package/dist/Helpers/RowsStore.d.ts +37 -2
- package/dist/Helpers/RowsStore.js +128 -10
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +21 -15
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +24 -3
- package/dist/Helpers/TableHelpers.js +58 -12
- package/dist/Helpers/TableJoins.d.ts +83 -0
- package/dist/Helpers/TableJoins.js +284 -0
- package/dist/Helpers/TableStore.d.ts +26 -1
- package/dist/Helpers/TableStore.js +73 -7
- package/dist/Helpers/TreeStore.d.ts +306 -11
- package/dist/Helpers/TreeStore.js +1048 -90
- package/dist/Helpers/UserPreferences.d.ts +8 -0
- package/dist/Helpers/UserPreferences.js +17 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +512 -14
- package/dist/Interfaces/FileInterfaces.d.ts +162 -0
- package/dist/Interfaces/FileInterfaces.js +4 -0
- package/dist/Interfaces/FormInterfaces.d.ts +393 -29
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +39 -3
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +8 -0
- package/dist/Interfaces/PreferenceInterfaces.js +2 -5
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +160 -5
- package/dist/Services/ApiProxy.d.ts +36 -1
- package/dist/Services/ApiProxy.js +182 -52
- package/dist/Services/ApiProxyRules.d.ts +203 -0
- package/dist/Services/ApiProxyRules.js +426 -0
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +80 -0
- package/dist/Services/JoinSources.js +150 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Services/ServerApi.d.ts +13 -2
- package/dist/Services/ServerApi.js +8 -3
- package/dist/Shared.Index.d.ts +20 -0
- package/dist/Shared.Index.js +20 -0
- package/package.json +92 -85
- package/src/Styles/Nexus.Avatar.css +80 -79
- package/src/Styles/Nexus.Base.css +117 -117
- package/src/Styles/Nexus.Button.css +186 -180
- package/src/Styles/Nexus.Calendar.css +273 -273
- package/src/Styles/Nexus.Chat.css +1443 -1443
- package/src/Styles/Nexus.Choice.css +359 -343
- package/src/Styles/Nexus.Crud.css +542 -168
- package/src/Styles/Nexus.DateTime.css +81 -81
- package/src/Styles/Nexus.Dropdown.css +733 -725
- package/src/Styles/Nexus.Editor.css +295 -295
- package/src/Styles/Nexus.Excel.css +389 -389
- package/src/Styles/Nexus.Files.css +410 -0
- package/src/Styles/Nexus.Form.css +282 -279
- package/src/Styles/Nexus.Index.css +41 -37
- package/src/Styles/Nexus.Input.css +238 -4
- package/src/Styles/Nexus.Markdown.css +237 -237
- package/src/Styles/Nexus.Menu.css +246 -237
- package/src/Styles/Nexus.Popup.css +800 -724
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Rows.css +637 -637
- package/src/Styles/Nexus.Slider.css +276 -276
- package/src/Styles/Nexus.Tab.Buttons.css +497 -432
- package/src/Styles/Nexus.Table.css +98 -35
- package/src/Styles/Nexus.Theme.Picker.css +59 -3
- package/src/Styles/Nexus.Themes.css +200 -194
- package/src/Styles/Nexus.Time.css +144 -144
- package/src/Styles/Nexus.Toast.css +263 -263
- package/src/Styles/Nexus.Tokens.css +94 -89
- package/src/Styles/Nexus.Tree.css +530 -431
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InputNarrowing, InputParams } from "../Interfaces/FormInterfaces.ts";
|
|
2
|
+
import type { OptionValue } from "../Interfaces/InputInterfaces.ts";
|
|
3
|
+
/** The value that narrows, or null while the other input has none: an empty text, an empty list and `false` count as none. */
|
|
4
|
+
export declare function narrowingValue(value: unknown): OptionValue | null;
|
|
5
|
+
/** What a narrowed box says while the other input is empty: "Pick a group first". */
|
|
6
|
+
export declare function narrowingPlaceholder(narrowing: InputNarrowing, otherLabel: unknown): string;
|
|
7
|
+
/** A narrowing by a fixed value (`{ key, value }`), not by another input. */
|
|
8
|
+
export declare function isFixedNarrowing(narrowing: InputNarrowing): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* An input's params narrowed by the other input's value as it is now (or by the fixed `value` the narrowing gives): static `options` filtered to the records whose
|
|
11
|
+
* `key` field holds it; `loadOptions` asked with `request.narrow` (crudPicker reads it: from the server when its
|
|
12
|
+
* endpoint takes the parent, else from the list it keeps, narrowed in the browser), and a `loadKey` of its own per
|
|
13
|
+
* value, so the list loads afresh when the other value changes. While the other input is empty, no option is offered
|
|
14
|
+
* and the box says what to pick first. Params without `narrowedBy` are answered as they are.
|
|
15
|
+
*
|
|
16
|
+
* @example narrowInputParams(packageParams, form.get("subscriptionGroupId"), "Group")
|
|
17
|
+
*/
|
|
18
|
+
export declare function narrowInputParams(params: InputParams, otherValue: unknown, otherLabel?: unknown): InputParams;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Options narrowed by another input's value (`narrowedBy`): a Package list of the Group picked, a Sub-region list of the
|
|
3
|
+
* Country picked. The form reads the other value as it draws and hands the input its params narrowed by it (this file,
|
|
4
|
+
* pure, so Node tests run it); the form's store clears the input's value when the other one changes (FormStore).
|
|
5
|
+
*/
|
|
6
|
+
/** The value that narrows, or null while the other input has none: an empty text, an empty list and `false` count as none. */
|
|
7
|
+
export function narrowingValue(value) {
|
|
8
|
+
if (value === null || value === undefined || value === "" || value === false)
|
|
9
|
+
return null;
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value.length === 1 ? narrowingValue(value[0]) : null;
|
|
12
|
+
if (typeof value === "string" || typeof value === "number")
|
|
13
|
+
return value;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
/** What a narrowed box says while the other input is empty: "Pick a group first". */
|
|
17
|
+
export function narrowingPlaceholder(narrowing, otherLabel) {
|
|
18
|
+
if (narrowing.placeholder)
|
|
19
|
+
return narrowing.placeholder;
|
|
20
|
+
if (narrowing.name === undefined)
|
|
21
|
+
return "";
|
|
22
|
+
const label = typeof otherLabel === "string" && otherLabel.trim() ? otherLabel.trim().toLocaleLowerCase() : narrowing.name;
|
|
23
|
+
return `Pick ${/^[aeiou]/i.test(label) ? "an" : "a"} ${label} first`;
|
|
24
|
+
}
|
|
25
|
+
const EMPTY_PAGE = { items: [], total: 0, hasMore: false };
|
|
26
|
+
/** A narrowing by a fixed value (`{ key, value }`), not by another input. */
|
|
27
|
+
export function isFixedNarrowing(narrowing) {
|
|
28
|
+
return narrowing.name === undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* An input's params narrowed by the other input's value as it is now (or by the fixed `value` the narrowing gives): static `options` filtered to the records whose
|
|
32
|
+
* `key` field holds it; `loadOptions` asked with `request.narrow` (crudPicker reads it: from the server when its
|
|
33
|
+
* endpoint takes the parent, else from the list it keeps, narrowed in the browser), and a `loadKey` of its own per
|
|
34
|
+
* value, so the list loads afresh when the other value changes. While the other input is empty, no option is offered
|
|
35
|
+
* and the box says what to pick first. Params without `narrowedBy` are answered as they are.
|
|
36
|
+
*
|
|
37
|
+
* @example narrowInputParams(packageParams, form.get("subscriptionGroupId"), "Group")
|
|
38
|
+
*/
|
|
39
|
+
export function narrowInputParams(params, otherValue, otherLabel) {
|
|
40
|
+
const narrowing = params.narrowedBy;
|
|
41
|
+
if (!narrowing)
|
|
42
|
+
return params;
|
|
43
|
+
const fixed = narrowing.name === undefined;
|
|
44
|
+
const key = narrowing.key ?? narrowing.name ?? "";
|
|
45
|
+
const value = narrowingValue(fixed ? narrowing.value : otherValue);
|
|
46
|
+
const orUnset = narrowing.orUnset === true;
|
|
47
|
+
const keeps = (item) => {
|
|
48
|
+
if (item === null || typeof item !== "object")
|
|
49
|
+
return false;
|
|
50
|
+
const field = item[key];
|
|
51
|
+
return (value !== null && String(field) === String(value)) || (orUnset && (field === null || field === undefined || field === ""));
|
|
52
|
+
};
|
|
53
|
+
const own = params;
|
|
54
|
+
const narrowed = { ...own, loadKey: `${own.loadKey === undefined ? "" : String(own.loadKey)}|${narrowing.name ?? key}=${value === null ? "" : String(value)}` };
|
|
55
|
+
const placeholder = value === null ? narrowingPlaceholder(narrowing, otherLabel) : "";
|
|
56
|
+
if (placeholder)
|
|
57
|
+
narrowed.placeholder = placeholder;
|
|
58
|
+
// Nothing to narrow by offers nothing, unless the options with no value in the field are offered too (`orUnset`).
|
|
59
|
+
const none = value === null && !orUnset;
|
|
60
|
+
if (Array.isArray(own.options))
|
|
61
|
+
narrowed.options = none ? [] : own.options.filter(keeps);
|
|
62
|
+
const load = own.loadOptions;
|
|
63
|
+
if (load) {
|
|
64
|
+
narrowed.loadOptions = (request) => (none ? Promise.resolve(EMPTY_PAGE) : load({ ...request, narrow: { key, value, ...(orUnset && { orUnset }) } }));
|
|
65
|
+
}
|
|
66
|
+
return narrowed;
|
|
67
|
+
}
|
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type InputParams, type InputValidationContext, type RowsInputProps, type RowValue } from "../Interfaces/FormInterfaces.ts";
|
|
2
2
|
/** Keys that place an input in a form; a renderer leaves them out of the component's props. */
|
|
3
3
|
export declare const INPUT_LAYOUT_KEYS: readonly ["span", "width", "grow", "hidden"];
|
|
4
4
|
/** Inputs whose value is `true` or `false`. */
|
|
5
5
|
export declare const isBooleanInput: (params: InputParams) => params is import("../Shared.Index.ts").CheckBoxParams | import("../Shared.Index.ts").SwitchParams | import("../Shared.Index.ts").RadioParams;
|
|
6
|
+
/**
|
|
7
|
+
* The member name of an input type, for a message or a log: `inputTypeName(EInputTypes.RichText)` is "RichText". A number
|
|
8
|
+
* that is no member, such as one read from old data, comes back as its digits.
|
|
9
|
+
*/
|
|
10
|
+
export declare const inputTypeName: (type: number) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether an input holds a value of the form. A picture does not: it is the owner's file, changed at once through the
|
|
13
|
+
* file backend, so the form keeps no field for it and a save sends nothing under its name.
|
|
14
|
+
*/
|
|
15
|
+
export declare const holdsValue: (params: InputParams) => params is import("../Shared.Index.ts").TextInputParams | import("../Shared.Index.ts").TextAreaParams | import("../Shared.Index.ts").NumberInputParams | import("../Shared.Index.ts").CodeInputParams | import("../Shared.Index.ts").CheckBoxParams | import("../Shared.Index.ts").SwitchParams | import("../Shared.Index.ts").RadioParams | import("../Shared.Index.ts").CheckBoxGroupParams | import("../Shared.Index.ts").RadioGroupParams | import("../Shared.Index.ts").DropdownParams | import("../Shared.Index.ts").DateParams | import("../Shared.Index.ts").DateRangeParams | import("../Shared.Index.ts").TimeParams | import("../Shared.Index.ts").DateTimeParams | import("../Shared.Index.ts").SliderParams | import("../Shared.Index.ts").RangeSliderParams | import("../Shared.Index.ts").MarkdownParams | import("../Shared.Index.ts").RichTextParams | import("../Shared.Index.ts").TreeParams | import("../Shared.Index.ts").TabularParams | import("../Shared.Index.ts").GroupParams;
|
|
16
|
+
/**
|
|
17
|
+
* Whether what is typed in an input is worth asking about before it is dropped: not a password or a one-time code (a
|
|
18
|
+
* `Code` box), which are typed again in a moment and never kept in a draft. Such a value alone makes no form ask
|
|
19
|
+
* "Discard your changes?" on Cancel or close, nor the page ask before it is left.
|
|
20
|
+
*/
|
|
21
|
+
export declare const keepsTyping: (params: InputParams) => boolean;
|
|
6
22
|
/** Inputs whose value is an array of rows. */
|
|
7
23
|
export declare const isRowsInput: (params: InputParams) => params is import("../Shared.Index.ts").TabularParams | import("../Shared.Index.ts").GroupParams;
|
|
8
24
|
/**
|
|
@@ -23,6 +39,33 @@ export declare function normalizeInputValue(params: InputParams, value: unknown)
|
|
|
23
39
|
export declare function isEmptyInputValue(value: unknown): boolean;
|
|
24
40
|
/** Equal values: the same primitive, or arrays with the same items in the same order. */
|
|
25
41
|
export declare function inputValuesEqual(a: unknown, b: unknown): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The inputs with some of them fixed for one opening of a form: each input `locked` names (input name -> why) turns
|
|
44
|
+
* read-only and says why on any try to change it (`readOnlyMessage`), the lock-and-note look every input has. Its value
|
|
45
|
+
* stays in the form and is sent as before. A required input with no value in `values` stays free: fixed empty, the form
|
|
46
|
+
* would never ask for it, since read-only inputs are not checked. Names a form does not have are skipped.
|
|
47
|
+
*
|
|
48
|
+
* Each fixed input is a copy made from its own property descriptors, never a spread: a spread would read a getter once
|
|
49
|
+
* (`get readOnly()` or `get options()` following other values through `dependsOn`) and freeze it.
|
|
50
|
+
*
|
|
51
|
+
* To show fixed inputs disabled instead (dimmed, out of the Tab order, no note), write `disabled` for `readOnly` in the
|
|
52
|
+
* copy below.
|
|
53
|
+
*
|
|
54
|
+
* `known` are records the caller holds for the fixed values - the row picked in another list that a new record is added
|
|
55
|
+
* for: a fixed dropdown whose value one of them holds (by the dropdown's `valueKey`) gets it as a selected option, so its
|
|
56
|
+
* box names the value at once, with no lookup.
|
|
57
|
+
*
|
|
58
|
+
* @example lockInputs(REGION_INPUTS, { countryId: "Added inside Bagmati." }, { countryId: "c7" })
|
|
59
|
+
*/
|
|
60
|
+
export declare function lockInputs(inputs: readonly InputParams[], locked: Readonly<Record<string, string>>, values?: Readonly<Record<string, unknown>>, known?: readonly Readonly<Record<string, unknown>>[]): InputParams[];
|
|
61
|
+
/**
|
|
62
|
+
* A form's `onFieldChange` that never writes the inputs fixed for an opening (`lockInputs`): a rule such as "a province
|
|
63
|
+
* sits right under its country" still changes a free field, and leaves a fixed one as the form opened it. With nothing
|
|
64
|
+
* fixed, or no rule, the rule as it is.
|
|
65
|
+
*
|
|
66
|
+
* @example lockedFieldChange(form.onFieldChange, ["countryId", "parentId"])
|
|
67
|
+
*/
|
|
68
|
+
export declare function lockedFieldChange<TValues extends Record<string, unknown>>(onFieldChange: ((name: string, value: unknown, values: TValues) => Partial<TValues> | null | undefined | void) | undefined, fixed: readonly string[]): ((name: string, value: unknown, values: TValues) => Partial<TValues> | null | undefined | void) | undefined;
|
|
26
69
|
/**
|
|
27
70
|
* Checks a value against its input's rules and returns the first message, or `null` when it is valid: the same
|
|
28
71
|
* message the input shows. Pure, so a form can check thousands of values on submit, including rows not on screen.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_DATE_PREFERENCES } from "../Interfaces/DateInterfaces.js";
|
|
2
|
+
import { EInputTypes } from "../Interfaces/FormInterfaces.js";
|
|
3
|
+
import { formatCode, loadCode, validateCode } from "./CodeHelpers.js";
|
|
2
4
|
import { formatDate, formatDateRange, toDateRange, toIsoDate, usesDevanagari } from "./DateHelpers.js";
|
|
3
5
|
import { formatDateTime, toUtcDateTime } from "./DateTimeHelpers.js";
|
|
4
6
|
import { formatNumber, parseNumber } from "./NumberHelpers.js";
|
|
@@ -14,9 +16,27 @@ import { chooseRowMessage, duplicateRowMessage, maxRowsMessage, minRowsMessage,
|
|
|
14
16
|
/** Keys that place an input in a form; a renderer leaves them out of the component's props. */
|
|
15
17
|
export const INPUT_LAYOUT_KEYS = ["span", "width", "grow", "hidden"];
|
|
16
18
|
/** Inputs whose value is `true` or `false`. */
|
|
17
|
-
export const isBooleanInput = (params) => params.type ===
|
|
19
|
+
export const isBooleanInput = (params) => params.type === EInputTypes.CheckBox || params.type === EInputTypes.Switch || params.type === EInputTypes.Radio;
|
|
20
|
+
/** Each `EInputTypes` number with its member name. */
|
|
21
|
+
const INPUT_TYPE_NAMES = new Map(Object.entries(EInputTypes).map(([name, value]) => [value, name]));
|
|
22
|
+
/**
|
|
23
|
+
* The member name of an input type, for a message or a log: `inputTypeName(EInputTypes.RichText)` is "RichText". A number
|
|
24
|
+
* that is no member, such as one read from old data, comes back as its digits.
|
|
25
|
+
*/
|
|
26
|
+
export const inputTypeName = (type) => INPUT_TYPE_NAMES.get(type) ?? String(type);
|
|
27
|
+
/**
|
|
28
|
+
* Whether an input holds a value of the form. A picture does not: it is the owner's file, changed at once through the
|
|
29
|
+
* file backend, so the form keeps no field for it and a save sends nothing under its name.
|
|
30
|
+
*/
|
|
31
|
+
export const holdsValue = (params) => params.type !== EInputTypes.Picture;
|
|
32
|
+
/**
|
|
33
|
+
* Whether what is typed in an input is worth asking about before it is dropped: not a password or a one-time code (a
|
|
34
|
+
* `Code` box), which are typed again in a moment and never kept in a draft. Such a value alone makes no form ask
|
|
35
|
+
* "Discard your changes?" on Cancel or close, nor the page ask before it is left.
|
|
36
|
+
*/
|
|
37
|
+
export const keepsTyping = (params) => params.type !== EInputTypes.Password && params.type !== EInputTypes.Code;
|
|
18
38
|
/** Inputs whose value is an array of rows. */
|
|
19
|
-
export const isRowsInput = (params) => params.type ===
|
|
39
|
+
export const isRowsInput = (params) => params.type === EInputTypes.Tabular || params.type === EInputTypes.Group;
|
|
20
40
|
/**
|
|
21
41
|
* The name an input's messages use, as the input itself picks it: `fieldName`, or the label when it is text. A checkbox's
|
|
22
42
|
* label is usually a sentence, so it uses `fieldName` only.
|
|
@@ -25,35 +45,36 @@ export function inputFieldName(params) {
|
|
|
25
45
|
const { fieldName } = params;
|
|
26
46
|
if (fieldName)
|
|
27
47
|
return fieldName;
|
|
28
|
-
if (params.type ===
|
|
48
|
+
if (params.type === EInputTypes.CheckBox || params.type === EInputTypes.Switch)
|
|
29
49
|
return undefined;
|
|
30
50
|
return typeof params.label === "string" ? params.label : undefined;
|
|
31
51
|
}
|
|
32
52
|
/** The value of an input with nothing in it: "" for text, `null` for a number or a date, `false`, `[]`, and so on. */
|
|
33
53
|
export function emptyInputValue(params) {
|
|
34
54
|
switch (params.type) {
|
|
35
|
-
case
|
|
36
|
-
case
|
|
37
|
-
case
|
|
38
|
-
case
|
|
39
|
-
case
|
|
55
|
+
case EInputTypes.Number:
|
|
56
|
+
case EInputTypes.Date:
|
|
57
|
+
case EInputTypes.Time:
|
|
58
|
+
case EInputTypes.DateTime:
|
|
59
|
+
case EInputTypes.RadioGroup:
|
|
60
|
+
case EInputTypes.Picture:
|
|
40
61
|
return null;
|
|
41
|
-
case
|
|
42
|
-
case
|
|
43
|
-
case
|
|
62
|
+
case EInputTypes.CheckBox:
|
|
63
|
+
case EInputTypes.Switch:
|
|
64
|
+
case EInputTypes.Radio:
|
|
44
65
|
return false;
|
|
45
|
-
case
|
|
46
|
-
case
|
|
47
|
-
case
|
|
48
|
-
case
|
|
66
|
+
case EInputTypes.CheckBoxGroup:
|
|
67
|
+
case EInputTypes.Tree:
|
|
68
|
+
case EInputTypes.Tabular:
|
|
69
|
+
case EInputTypes.Group:
|
|
49
70
|
return [];
|
|
50
|
-
case
|
|
71
|
+
case EInputTypes.Dropdown:
|
|
51
72
|
return params.multiple ? [] : null;
|
|
52
|
-
case
|
|
73
|
+
case EInputTypes.DateRange:
|
|
53
74
|
return [null, null];
|
|
54
|
-
case
|
|
75
|
+
case EInputTypes.Slider:
|
|
55
76
|
return params.min ?? 0;
|
|
56
|
-
case
|
|
77
|
+
case EInputTypes.RangeSlider:
|
|
57
78
|
return [params.min ?? 0, params.max ?? 100];
|
|
58
79
|
default:
|
|
59
80
|
return "";
|
|
@@ -76,38 +97,41 @@ export function normalizeInputValue(params, value) {
|
|
|
76
97
|
return emptyInputValue(params);
|
|
77
98
|
}
|
|
78
99
|
switch (params.type) {
|
|
79
|
-
case
|
|
100
|
+
case EInputTypes.Number: {
|
|
80
101
|
if (typeof value === "number")
|
|
81
102
|
return Number.isNaN(value) ? null : value;
|
|
82
103
|
return typeof value === "string" ? parseNumber(value, params.locale) : null;
|
|
83
104
|
}
|
|
84
|
-
case
|
|
85
|
-
case
|
|
86
|
-
case
|
|
105
|
+
case EInputTypes.CheckBox:
|
|
106
|
+
case EInputTypes.Switch:
|
|
107
|
+
case EInputTypes.Radio:
|
|
87
108
|
return value === true || value === "true" || value === 1;
|
|
88
|
-
case
|
|
109
|
+
case EInputTypes.Date:
|
|
89
110
|
return toIsoDate(value) ?? (typeof value === "string" ? value : null);
|
|
90
|
-
case
|
|
111
|
+
case EInputTypes.DateRange:
|
|
91
112
|
return toDateRange(Array.isArray(value) ? value : null);
|
|
92
|
-
case
|
|
113
|
+
case EInputTypes.Time:
|
|
93
114
|
return toIsoTime(value, normalizeTimeStep(params.step) % 60 !== 0) ?? (typeof value === "string" ? value : null);
|
|
94
|
-
case
|
|
115
|
+
case EInputTypes.DateTime:
|
|
95
116
|
return toUtcDateTime(value) ?? (typeof value === "string" ? value : null);
|
|
96
|
-
case
|
|
97
|
-
case
|
|
98
|
-
case
|
|
99
|
-
case
|
|
117
|
+
case EInputTypes.CheckBoxGroup:
|
|
118
|
+
case EInputTypes.Tree:
|
|
119
|
+
case EInputTypes.Tabular:
|
|
120
|
+
case EInputTypes.Group:
|
|
100
121
|
return Array.isArray(value) ? value : [];
|
|
101
|
-
case
|
|
122
|
+
case EInputTypes.Dropdown:
|
|
102
123
|
if (params.multiple)
|
|
103
124
|
return Array.isArray(value) ? value : [value];
|
|
104
125
|
return Array.isArray(value) ? (value[0] ?? null) : value;
|
|
105
|
-
case
|
|
126
|
+
case EInputTypes.Slider:
|
|
106
127
|
return typeof value === "number" ? value : (parseNumber(String(value)) ?? params.min ?? 0);
|
|
107
|
-
case
|
|
128
|
+
case EInputTypes.RangeSlider:
|
|
108
129
|
return Array.isArray(value) && value.length === 2 ? value : emptyInputValue(params);
|
|
109
|
-
case
|
|
130
|
+
case EInputTypes.RadioGroup:
|
|
110
131
|
return value;
|
|
132
|
+
case EInputTypes.Code:
|
|
133
|
+
// Only separators and case: a saved code that does not fit stays as it is, so its check refuses the save.
|
|
134
|
+
return loadCode(value, params);
|
|
111
135
|
default:
|
|
112
136
|
return typeof value === "string" ? value : String(value);
|
|
113
137
|
}
|
|
@@ -130,6 +154,67 @@ export function inputValuesEqual(a, b) {
|
|
|
130
154
|
return false;
|
|
131
155
|
return a.every((item, index) => Object.is(item, b[index]));
|
|
132
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The inputs with some of them fixed for one opening of a form: each input `locked` names (input name -> why) turns
|
|
159
|
+
* read-only and says why on any try to change it (`readOnlyMessage`), the lock-and-note look every input has. Its value
|
|
160
|
+
* stays in the form and is sent as before. A required input with no value in `values` stays free: fixed empty, the form
|
|
161
|
+
* would never ask for it, since read-only inputs are not checked. Names a form does not have are skipped.
|
|
162
|
+
*
|
|
163
|
+
* Each fixed input is a copy made from its own property descriptors, never a spread: a spread would read a getter once
|
|
164
|
+
* (`get readOnly()` or `get options()` following other values through `dependsOn`) and freeze it.
|
|
165
|
+
*
|
|
166
|
+
* To show fixed inputs disabled instead (dimmed, out of the Tab order, no note), write `disabled` for `readOnly` in the
|
|
167
|
+
* copy below.
|
|
168
|
+
*
|
|
169
|
+
* `known` are records the caller holds for the fixed values - the row picked in another list that a new record is added
|
|
170
|
+
* for: a fixed dropdown whose value one of them holds (by the dropdown's `valueKey`) gets it as a selected option, so its
|
|
171
|
+
* box names the value at once, with no lookup.
|
|
172
|
+
*
|
|
173
|
+
* @example lockInputs(REGION_INPUTS, { countryId: "Added inside Bagmati." }, { countryId: "c7" })
|
|
174
|
+
*/
|
|
175
|
+
export function lockInputs(inputs, locked, values = {}, known = []) {
|
|
176
|
+
return inputs.map(params => {
|
|
177
|
+
const reason = locked[params.name];
|
|
178
|
+
if (reason === undefined)
|
|
179
|
+
return params;
|
|
180
|
+
const required = params.required === true;
|
|
181
|
+
if (required && isEmptyInputValue(values[params.name]))
|
|
182
|
+
return params;
|
|
183
|
+
const fixed = {
|
|
184
|
+
...Object.getOwnPropertyDescriptors(params),
|
|
185
|
+
readOnly: { value: true, enumerable: true, writable: true, configurable: true },
|
|
186
|
+
readOnlyMessage: { value: reason, enumerable: true, writable: true, configurable: true },
|
|
187
|
+
};
|
|
188
|
+
const value = values[params.name];
|
|
189
|
+
if (params.type === EInputTypes.Dropdown && !params.multiple && !isEmptyInputValue(value)) {
|
|
190
|
+
const valueKey = params.valueKey ?? "value";
|
|
191
|
+
const record = known.find(item => String(item[valueKey]) === String(value));
|
|
192
|
+
if (record)
|
|
193
|
+
fixed.selectedOptions = { value: [...(params.selectedOptions ?? []), record], enumerable: true, writable: true, configurable: true };
|
|
194
|
+
}
|
|
195
|
+
return Object.create(Object.getPrototypeOf(params), fixed);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A form's `onFieldChange` that never writes the inputs fixed for an opening (`lockInputs`): a rule such as "a province
|
|
200
|
+
* sits right under its country" still changes a free field, and leaves a fixed one as the form opened it. With nothing
|
|
201
|
+
* fixed, or no rule, the rule as it is.
|
|
202
|
+
*
|
|
203
|
+
* @example lockedFieldChange(form.onFieldChange, ["countryId", "parentId"])
|
|
204
|
+
*/
|
|
205
|
+
export function lockedFieldChange(onFieldChange, fixed) {
|
|
206
|
+
if (!onFieldChange || fixed.length === 0)
|
|
207
|
+
return onFieldChange;
|
|
208
|
+
return (name, value, values) => {
|
|
209
|
+
const patch = onFieldChange(name, value, values);
|
|
210
|
+
if (!patch)
|
|
211
|
+
return patch;
|
|
212
|
+
const kept = { ...patch };
|
|
213
|
+
for (const field of fixed)
|
|
214
|
+
delete kept[field];
|
|
215
|
+
return kept;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
133
218
|
function dateSettings(params, preferences) {
|
|
134
219
|
return {
|
|
135
220
|
calendar: params.calendar ?? preferences.calendar,
|
|
@@ -160,35 +245,35 @@ export function validateInput(params, value, context = {}) {
|
|
|
160
245
|
const name = inputFieldName(params);
|
|
161
246
|
const preferences = context.preferences ?? DEFAULT_DATE_PREFERENCES;
|
|
162
247
|
switch (params.type) {
|
|
163
|
-
case
|
|
248
|
+
case EInputTypes.Number:
|
|
164
249
|
return validateNumber(value, params, name, limit => formatNumber(limit, { locale: params.locale, grouping: true }));
|
|
165
|
-
case
|
|
166
|
-
case
|
|
250
|
+
case EInputTypes.CheckBox:
|
|
251
|
+
case EInputTypes.Switch:
|
|
167
252
|
return validateCheck(value === true, params, params.fieldName);
|
|
168
|
-
case
|
|
253
|
+
case EInputTypes.Radio:
|
|
169
254
|
// One row at a time can have it on; whether one must is the rows' rule.
|
|
170
255
|
return null;
|
|
171
|
-
case
|
|
256
|
+
case EInputTypes.CheckBoxGroup:
|
|
172
257
|
return validateSelection(asArray(value), params, name);
|
|
173
|
-
case
|
|
258
|
+
case EInputTypes.RadioGroup: {
|
|
174
259
|
const { validate } = params;
|
|
175
260
|
return validateSelection(asArray(value), { required: params.required, validate: validate && (values => validate(values[0] ?? null)) }, name);
|
|
176
261
|
}
|
|
177
|
-
case
|
|
262
|
+
case EInputTypes.Dropdown:
|
|
178
263
|
return validateSelection(asArray(value), { required: params.required, min: params.min, max: params.max, validate: params.validate }, name);
|
|
179
|
-
case
|
|
264
|
+
case EInputTypes.Date: {
|
|
180
265
|
const settings = dateSettings(params, preferences);
|
|
181
266
|
return validateDate(value, params, name, iso => formatDate(iso, settings));
|
|
182
267
|
}
|
|
183
|
-
case
|
|
268
|
+
case EInputTypes.DateRange: {
|
|
184
269
|
const settings = dateSettings(params, preferences);
|
|
185
270
|
return validateDateRange(value, params, name, iso => formatDate(iso, settings));
|
|
186
271
|
}
|
|
187
|
-
case
|
|
272
|
+
case EInputTypes.Time: {
|
|
188
273
|
const settings = timeSettings(params, preferences);
|
|
189
274
|
return validateTime(value, params, name, iso => formatTime(iso, settings));
|
|
190
275
|
}
|
|
191
|
-
case
|
|
276
|
+
case EInputTypes.DateTime: {
|
|
192
277
|
const seconds = normalizeTimeStep(params.step) % 60 !== 0;
|
|
193
278
|
return validateDateTime(value, params, name, {
|
|
194
279
|
...dateSettings(params, preferences),
|
|
@@ -197,21 +282,24 @@ export function validateInput(params, value, context = {}) {
|
|
|
197
282
|
seconds,
|
|
198
283
|
});
|
|
199
284
|
}
|
|
200
|
-
case
|
|
285
|
+
case EInputTypes.Code:
|
|
286
|
+
return validateCode(value, params, name);
|
|
287
|
+
case EInputTypes.Slider:
|
|
201
288
|
return params.validate?.(value) || null;
|
|
202
|
-
case
|
|
289
|
+
case EInputTypes.RangeSlider:
|
|
203
290
|
return params.validate?.(value) || null;
|
|
204
|
-
case
|
|
291
|
+
case EInputTypes.Tree:
|
|
292
|
+
case EInputTypes.Picture:
|
|
205
293
|
return null;
|
|
206
|
-
case
|
|
294
|
+
case EInputTypes.RichText:
|
|
207
295
|
return validateRichText(value, params, name);
|
|
208
|
-
case
|
|
209
|
-
case
|
|
296
|
+
case EInputTypes.Tabular:
|
|
297
|
+
case EInputTypes.Group: {
|
|
210
298
|
const result = validateRows(asArray(value), params, context);
|
|
211
299
|
if (result.message)
|
|
212
300
|
return result.message;
|
|
213
301
|
const first = result.cells[0];
|
|
214
|
-
return first ? `${rowsItemNames(params, params.type ===
|
|
302
|
+
return first ? `${rowsItemNames(params, params.type === EInputTypes.Tabular ? "Row" : "Item").title} ${first.index + 1}: ${first.message}` : null;
|
|
215
303
|
}
|
|
216
304
|
default:
|
|
217
305
|
return validateText(value, params, name);
|
|
@@ -257,7 +345,7 @@ export function rowsMessage(rows, rules, fallback = "Row") {
|
|
|
257
345
|
if (rules.max !== undefined && count > rules.max)
|
|
258
346
|
return maxRowsMessage(rules.max, names.item, names.items);
|
|
259
347
|
for (const params of rules.inputs) {
|
|
260
|
-
if (params.type !==
|
|
348
|
+
if (params.type !== EInputTypes.Radio || !params.required || params.hidden)
|
|
261
349
|
continue;
|
|
262
350
|
if (!rows.some(row => row[params.name] === true))
|
|
263
351
|
return chooseRowMessage(radioName(params), names.item);
|
|
@@ -342,45 +430,48 @@ export function formatInputValue(params, value, context = {}) {
|
|
|
342
430
|
return "";
|
|
343
431
|
const preferences = context.preferences ?? DEFAULT_DATE_PREFERENCES;
|
|
344
432
|
switch (params.type) {
|
|
345
|
-
case
|
|
433
|
+
case EInputTypes.Number: {
|
|
346
434
|
const text = formatNumber(value, { ...params, grouping: params.grouping ?? true });
|
|
347
435
|
const prefix = typeof params.prefix === "string" ? params.prefix + " " : "";
|
|
348
436
|
const suffix = typeof params.suffix === "string" ? " " + params.suffix : "";
|
|
349
437
|
return prefix + text + suffix;
|
|
350
438
|
}
|
|
351
|
-
case
|
|
352
|
-
case
|
|
353
|
-
case
|
|
439
|
+
case EInputTypes.CheckBox:
|
|
440
|
+
case EInputTypes.Switch:
|
|
441
|
+
case EInputTypes.Radio:
|
|
354
442
|
return typeof params.label === "string" ? params.label : "";
|
|
355
|
-
case
|
|
443
|
+
case EInputTypes.Dropdown:
|
|
356
444
|
return asArray(value)
|
|
357
445
|
.map(item => optionLabel(params.options, item, params.valueKey, params.labelKey))
|
|
358
446
|
.join(", ");
|
|
359
|
-
case
|
|
360
|
-
case
|
|
447
|
+
case EInputTypes.CheckBoxGroup:
|
|
448
|
+
case EInputTypes.RadioGroup:
|
|
361
449
|
return asArray(value)
|
|
362
450
|
.map(item => optionLabel(params.options, item))
|
|
363
451
|
.join(", ");
|
|
364
|
-
case
|
|
452
|
+
case EInputTypes.Date:
|
|
365
453
|
return formatDate(value, dateSettings(params, preferences));
|
|
366
|
-
case
|
|
454
|
+
case EInputTypes.DateRange:
|
|
367
455
|
return formatDateRange(value, dateSettings(params, preferences));
|
|
368
|
-
case
|
|
456
|
+
case EInputTypes.Time:
|
|
369
457
|
return formatTime(value, timeSettings(params, preferences));
|
|
370
|
-
case
|
|
458
|
+
case EInputTypes.DateTime:
|
|
371
459
|
return formatDateTime(value, { ...dateSettings(params, preferences), timeFormat: params.timeFormat ?? preferences.timeFormat, timeZone: params.timeZone });
|
|
372
|
-
case
|
|
460
|
+
case EInputTypes.Code:
|
|
461
|
+
return formatCode(value, params);
|
|
462
|
+
case EInputTypes.Slider:
|
|
373
463
|
return formatNumber(value, params);
|
|
374
|
-
case
|
|
464
|
+
case EInputTypes.RangeSlider:
|
|
375
465
|
return asArray(value)
|
|
376
466
|
.map(item => formatNumber(item, params))
|
|
377
467
|
.join(" – ");
|
|
378
|
-
case
|
|
379
|
-
case
|
|
380
|
-
case
|
|
468
|
+
case EInputTypes.Tree:
|
|
469
|
+
case EInputTypes.Tabular:
|
|
470
|
+
case EInputTypes.Group:
|
|
471
|
+
case EInputTypes.Picture:
|
|
381
472
|
return "";
|
|
382
473
|
default: {
|
|
383
|
-
const text = params.type ===
|
|
474
|
+
const text = params.type === EInputTypes.RichText ? richTextToPlainText(String(value)) : String(value).trim();
|
|
384
475
|
const line = text.split("\n", 1)[0] ?? "";
|
|
385
476
|
return line.length > 60 ? line.slice(0, 57) + "…" : line;
|
|
386
477
|
}
|
|
@@ -17,6 +17,8 @@ const DEFAULT_WORDS = {
|
|
|
17
17
|
unflag: { verb: "unflag", done: "unflagged", doing: "Unflagging", object: "the record" },
|
|
18
18
|
pin: { verb: "pin", done: "pinned", doing: "Pinning", object: "the record" },
|
|
19
19
|
unpin: { verb: "unpin", done: "unpinned", doing: "Unpinning", object: "the record" },
|
|
20
|
+
favorite: { verb: "add {object} to the favourites", done: "added to the favourites", doing: "Adding {object} to the favourites", object: "the record" },
|
|
21
|
+
unfavorite: { verb: "remove {object} from the favourites", done: "removed from the favourites", doing: "Removing {object} from the favourites", object: "the record" },
|
|
20
22
|
send: { verb: "send", done: "sent", doing: "Sending", object: "the message", sendsValues: true },
|
|
21
23
|
submit: { verb: "submit", done: "submitted", doing: "Submitting", object: "the form", sendsValues: true },
|
|
22
24
|
upload: { verb: "upload", done: "uploaded", doing: "Uploading", object: "the file", sendsValues: true },
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ApiClient } from "./ApiClient.ts";
|
|
2
|
+
import { FileAdapter } from "./FileAdapter.ts";
|
|
3
|
+
import { type ApiModuleValue, type ApiResult } from "../Interfaces/ApiInterfaces.ts";
|
|
4
|
+
import type { FileCallOptions, FileCopy, FileOwner, FileUpload, OwnerFiles, StoredFile } from "../Interfaces/FileInterfaces.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Why an owner's files are read only when its module refuses a write ticket with a 404 but a read ticket finds it: the
|
|
7
|
+
* owner is not in use now (pending, ended, in the trash or deleted). Its files stay where they are.
|
|
8
|
+
*/
|
|
9
|
+
export declare const OWNER_NOT_IN_USE_REASON = "This record is not in use now, so its files can be seen but not changed.";
|
|
10
|
+
/** What the adapter sends its calls with: the app's `api` (nexus-shared/client), so its token and headers go with them. */
|
|
11
|
+
export type FileApiClient = Pick<ApiClient<{
|
|
12
|
+
toast?: false;
|
|
13
|
+
}>, "send">;
|
|
14
|
+
export interface NexusDriveAdapterOptions {
|
|
15
|
+
/** The client the calls go through: `api` from nexus-shared/client. */
|
|
16
|
+
client: FileApiClient;
|
|
17
|
+
/** Reads the editor's pointer file. Default the browser's `fetch`. */
|
|
18
|
+
fetch?: typeof fetch;
|
|
19
|
+
/** The app's Drive module. Default `NexusModule.Drive`. */
|
|
20
|
+
drive?: ApiModuleValue;
|
|
21
|
+
/** The pointer file that names the current editor. Default `nexus-image-editor.txt` (Drive's `copy-image-editor.mjs`). */
|
|
22
|
+
editorPointer?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The Nexus Drive (`Nexus.Drives`, `App/Api/File/...`) behind the owner modules' tickets (`App/Api/Files/...`).
|
|
26
|
+
* Register another with `defineFileAdapter` to keep files elsewhere.
|
|
27
|
+
*/
|
|
28
|
+
export declare class NexusDriveAdapter extends FileAdapter {
|
|
29
|
+
readonly drive: ApiModuleValue;
|
|
30
|
+
readonly editorPointer: string;
|
|
31
|
+
private readonly tickets;
|
|
32
|
+
private readonly client;
|
|
33
|
+
private readonly fetcher;
|
|
34
|
+
constructor(options: NexusDriveAdapterOptions);
|
|
35
|
+
private driveEndpoint;
|
|
36
|
+
/** A write ticket for the owner, kept while it is good. */
|
|
37
|
+
private writeTicket;
|
|
38
|
+
/** Calls Drive with the owner's write ticket, asking for a fresh one once when Drive says it ran out. */
|
|
39
|
+
private withTicket;
|
|
40
|
+
/** A read ticket for several owners of one category. */
|
|
41
|
+
private readTicket;
|
|
42
|
+
list(owner: FileOwner<ApiModuleValue>, options?: FileCallOptions): Promise<ApiResult<OwnerFiles>>;
|
|
43
|
+
primaries(module: ApiModuleValue, category: string, ownerIds: readonly string[], options?: FileCallOptions): Promise<ApiResult<(StoredFile | null)[]>>;
|
|
44
|
+
upload(owner: FileOwner<ApiModuleValue>, upload: FileUpload, options?: FileCallOptions): Promise<ApiResult<StoredFile>>;
|
|
45
|
+
setPrimary(owner: FileOwner<ApiModuleValue>, fileId: string | null, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
46
|
+
rename(owner: FileOwner<ApiModuleValue>, fileId: string, name: string, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
47
|
+
remove(owner: FileOwner<ApiModuleValue>, fileId: string, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
48
|
+
fileUrl(file: StoredFile, copy: FileCopy): string;
|
|
49
|
+
/** Reads Drive's pointer file (`Assets/File/nexus-image-editor.txt`), which names the versioned module beside it. */
|
|
50
|
+
editorModuleUrl(signal?: AbortSignal): Promise<string>;
|
|
51
|
+
}
|
|
52
|
+
/** Reads one of Drive's `FileResponseContract`s; null when it is not one. */
|
|
53
|
+
export declare function readStoredFile(value: unknown): StoredFile | null;
|
|
54
|
+
/** Reads Drive's `FileListResponseContract`. */
|
|
55
|
+
export declare function readOwnerFiles(value: unknown): OwnerFiles;
|