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.
Files changed (234) hide show
  1. package/CHANGELOG.md +2268 -446
  2. package/LICENSE +21 -21
  3. package/README.md +1705 -1084
  4. package/dist/Client.Index.d.ts +17 -0
  5. package/dist/Client.Index.js +17 -0
  6. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  7. package/dist/Components/Documents/DocumentFrame.js +83 -0
  8. package/dist/Components/Documents/Menu.d.ts +4 -0
  9. package/dist/Components/Documents/Menu.js +40 -24
  10. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  11. package/dist/Components/Documents/PrintDocument.js +62 -0
  12. package/dist/Components/Documents/Tabs.d.ts +59 -0
  13. package/dist/Components/Documents/Tabs.js +70 -0
  14. package/dist/Components/Forms/ApiForm.js +19 -2
  15. package/dist/Components/Forms/Crud.d.ts +2 -2
  16. package/dist/Components/Forms/Crud.js +871 -377
  17. package/dist/Components/Forms/CrudActions.d.ts +136 -0
  18. package/dist/Components/Forms/CrudActions.js +184 -0
  19. package/dist/Components/Forms/CrudHistory.d.ts +66 -0
  20. package/dist/Components/Forms/CrudHistory.js +140 -0
  21. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  22. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  23. package/dist/Components/Forms/CrudParts.d.ts +30 -2
  24. package/dist/Components/Forms/CrudParts.js +56 -5
  25. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  26. package/dist/Components/Forms/CrudRowForms.js +114 -0
  27. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  28. package/dist/Components/Forms/CrudScopes.js +76 -0
  29. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  30. package/dist/Components/Forms/CrudTree.js +363 -0
  31. package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
  32. package/dist/Components/Forms/CrudWorkspace.js +169 -0
  33. package/dist/Components/Forms/ExcelImport.js +3 -3
  34. package/dist/Components/Forms/Form.d.ts +4 -4
  35. package/dist/Components/Forms/Form.js +125 -16
  36. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  37. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  38. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  39. package/dist/Components/Forms/RecordFlows.js +282 -0
  40. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  41. package/dist/Components/Forms/SubmitForm.js +195 -10
  42. package/dist/Components/Inputs/CheckBox.js +1 -1
  43. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  44. package/dist/Components/Inputs/ChoiceField.js +28 -2
  45. package/dist/Components/Inputs/CodeBox.d.ts +69 -0
  46. package/dist/Components/Inputs/CodeBox.js +316 -0
  47. package/dist/Components/Inputs/DatePicker.js +4 -1
  48. package/dist/Components/Inputs/DateRangePicker.js +4 -1
  49. package/dist/Components/Inputs/DateTimePicker.js +4 -1
  50. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  51. package/dist/Components/Inputs/Dropdown.js +8 -5
  52. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  53. package/dist/Components/Inputs/DropdownParts.js +16 -4
  54. package/dist/Components/Inputs/Field.d.ts +7 -1
  55. package/dist/Components/Inputs/Field.js +2 -2
  56. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  57. package/dist/Components/Inputs/GroupForm.js +10 -11
  58. package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
  59. package/dist/Components/Inputs/InputRenderer.js +39 -31
  60. package/dist/Components/Inputs/NumberBox.js +4 -2
  61. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  62. package/dist/Components/Inputs/PictureInput.js +70 -0
  63. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  64. package/dist/Components/Inputs/RadioGroup.js +8 -3
  65. package/dist/Components/Inputs/RowsInput.d.ts +1 -1
  66. package/dist/Components/Inputs/RowsInput.js +18 -3
  67. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  68. package/dist/Components/Inputs/TabularForm.js +29 -29
  69. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  70. package/dist/Components/Inputs/TextArea.js +14 -2
  71. package/dist/Components/Inputs/TextBox.js +4 -1
  72. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  73. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  74. package/dist/Components/Inputs/TimePicker.js +4 -1
  75. package/dist/Components/Inputs/TreeView.d.ts +107 -6
  76. package/dist/Components/Inputs/TreeView.js +513 -30
  77. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  78. package/dist/Components/Layouts/MessageBox.js +56 -7
  79. package/dist/Components/Layouts/Popup.d.ts +13 -6
  80. package/dist/Components/Layouts/Popup.js +37 -9
  81. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  82. package/dist/Components/Layouts/PopupDock.js +2 -1
  83. package/dist/Components/Layouts/PopupHost.js +5 -4
  84. package/dist/Components/Layouts/ThemePicker.d.ts +3 -2
  85. package/dist/Components/Layouts/ThemePicker.js +16 -4
  86. package/dist/Components/Viewers/DataTable.js +205 -61
  87. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  88. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  89. package/dist/Components/Viewers/DataTableParts.d.ts +46 -12
  90. package/dist/Components/Viewers/DataTableParts.js +62 -21
  91. package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
  92. package/dist/Components/Viewers/OwnerFiles.js +589 -0
  93. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  94. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  95. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  96. package/dist/Components/Viewers/RecordCards.js +509 -0
  97. package/dist/Helpers/ApiModules.d.ts +3 -34
  98. package/dist/Helpers/ApiModules.js +3 -3
  99. package/dist/Helpers/ApiRoutes.d.ts +68 -26
  100. package/dist/Helpers/ApiRoutes.js +31 -27
  101. package/dist/Helpers/ArrayHelpers.d.ts +0 -0
  102. package/dist/Helpers/ArrayHelpers.js +2 -0
  103. package/dist/Helpers/BusyHooks.d.ts +6 -0
  104. package/dist/Helpers/BusyHooks.js +39 -0
  105. package/dist/Helpers/CodeHelpers.d.ts +97 -0
  106. package/dist/Helpers/CodeHelpers.js +332 -0
  107. package/dist/Helpers/CrudBackend.d.ts +114 -6
  108. package/dist/Helpers/CrudBackend.js +214 -11
  109. package/dist/Helpers/CrudColumns.d.ts +98 -0
  110. package/dist/Helpers/CrudColumns.js +160 -0
  111. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  112. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  113. package/dist/Helpers/CrudHelpers.d.ts +130 -6
  114. package/dist/Helpers/CrudHelpers.js +304 -12
  115. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  116. package/dist/Helpers/CrudNarrowPick.js +157 -0
  117. package/dist/Helpers/CrudRowForms.d.ts +15 -0
  118. package/dist/Helpers/CrudRowForms.js +43 -0
  119. package/dist/Helpers/CrudScopes.d.ts +32 -0
  120. package/dist/Helpers/CrudScopes.js +79 -0
  121. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  122. package/dist/Helpers/DropdownHelpers.js +6 -2
  123. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  124. package/dist/Helpers/ExcelHelpers.js +12 -11
  125. package/dist/Helpers/FileAdapter.d.ts +78 -0
  126. package/dist/Helpers/FileAdapter.js +102 -0
  127. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  128. package/dist/Helpers/FilePrimaries.js +138 -0
  129. package/dist/Helpers/FormConfirm.d.ts +32 -0
  130. package/dist/Helpers/FormConfirm.js +40 -0
  131. package/dist/Helpers/FormStore.d.ts +90 -5
  132. package/dist/Helpers/FormStore.js +318 -22
  133. package/dist/Helpers/InputConditions.d.ts +50 -0
  134. package/dist/Helpers/InputConditions.js +168 -0
  135. package/dist/Helpers/InputMemory.d.ts +106 -0
  136. package/dist/Helpers/InputMemory.js +385 -0
  137. package/dist/Helpers/InputNarrowing.d.ts +18 -0
  138. package/dist/Helpers/InputNarrowing.js +67 -0
  139. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  140. package/dist/Helpers/InputParamsHelpers.js +162 -71
  141. package/dist/Helpers/MessageBuilder.js +2 -0
  142. package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
  143. package/dist/Helpers/NexusDriveAdapter.js +207 -0
  144. package/dist/Helpers/NexusHelpers.d.ts +0 -0
  145. package/dist/Helpers/NexusHelpers.js +1 -0
  146. package/dist/Helpers/NumberHelpers.d.ts +95 -0
  147. package/dist/Helpers/NumberHelpers.js +126 -0
  148. package/dist/Helpers/Permissions.js +2 -1
  149. package/dist/Helpers/QrCode.d.ts +26 -0
  150. package/dist/Helpers/QrCode.js +463 -0
  151. package/dist/Helpers/RecordCards.d.ts +205 -0
  152. package/dist/Helpers/RecordCards.js +378 -0
  153. package/dist/Helpers/RequestCache.d.ts +47 -0
  154. package/dist/Helpers/RequestCache.js +132 -0
  155. package/dist/Helpers/RowsStore.d.ts +37 -2
  156. package/dist/Helpers/RowsStore.js +128 -10
  157. package/dist/Helpers/ServerPreview.d.ts +69 -0
  158. package/dist/Helpers/ServerPreview.js +99 -0
  159. package/dist/Helpers/TableColumns.js +4 -3
  160. package/dist/Helpers/TableExport.d.ts +7 -1
  161. package/dist/Helpers/TableExport.js +21 -15
  162. package/dist/Helpers/TableFilters.d.ts +1 -1
  163. package/dist/Helpers/TableFilters.js +24 -23
  164. package/dist/Helpers/TableHelpers.d.ts +24 -3
  165. package/dist/Helpers/TableHelpers.js +58 -12
  166. package/dist/Helpers/TableJoins.d.ts +83 -0
  167. package/dist/Helpers/TableJoins.js +284 -0
  168. package/dist/Helpers/TableStore.d.ts +26 -1
  169. package/dist/Helpers/TableStore.js +73 -7
  170. package/dist/Helpers/TreeStore.d.ts +306 -11
  171. package/dist/Helpers/TreeStore.js +1048 -90
  172. package/dist/Helpers/UserPreferences.d.ts +8 -0
  173. package/dist/Helpers/UserPreferences.js +17 -0
  174. package/dist/Interfaces/CrudInterfaces.d.ts +512 -14
  175. package/dist/Interfaces/FileInterfaces.d.ts +162 -0
  176. package/dist/Interfaces/FileInterfaces.js +4 -0
  177. package/dist/Interfaces/FormInterfaces.d.ts +393 -29
  178. package/dist/Interfaces/FormInterfaces.js +79 -1
  179. package/dist/Interfaces/InputInterfaces.d.ts +39 -3
  180. package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
  181. package/dist/Interfaces/PreferenceInterfaces.d.ts +8 -0
  182. package/dist/Interfaces/PreferenceInterfaces.js +2 -5
  183. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  184. package/dist/Interfaces/RecordInterfaces.js +1 -0
  185. package/dist/Interfaces/TableInterfaces.d.ts +160 -5
  186. package/dist/Services/ApiProxy.d.ts +36 -1
  187. package/dist/Services/ApiProxy.js +182 -52
  188. package/dist/Services/ApiProxyRules.d.ts +203 -0
  189. package/dist/Services/ApiProxyRules.js +426 -0
  190. package/dist/Services/ExcelApi.js +5 -0
  191. package/dist/Services/FileApi.d.ts +13 -0
  192. package/dist/Services/FileApi.js +29 -0
  193. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  194. package/dist/Services/ImageEditorLoader.js +51 -0
  195. package/dist/Services/JoinSources.d.ts +80 -0
  196. package/dist/Services/JoinSources.js +150 -0
  197. package/dist/Services/ModuleImport.d.ts +7 -0
  198. package/dist/Services/ModuleImport.js +22 -0
  199. package/dist/Services/ServerApi.d.ts +13 -2
  200. package/dist/Services/ServerApi.js +8 -3
  201. package/dist/Shared.Index.d.ts +20 -0
  202. package/dist/Shared.Index.js +20 -0
  203. package/package.json +92 -85
  204. package/src/Styles/Nexus.Avatar.css +80 -79
  205. package/src/Styles/Nexus.Base.css +117 -117
  206. package/src/Styles/Nexus.Button.css +186 -180
  207. package/src/Styles/Nexus.Calendar.css +273 -273
  208. package/src/Styles/Nexus.Chat.css +1443 -1443
  209. package/src/Styles/Nexus.Choice.css +359 -343
  210. package/src/Styles/Nexus.Crud.css +542 -168
  211. package/src/Styles/Nexus.DateTime.css +81 -81
  212. package/src/Styles/Nexus.Dropdown.css +733 -725
  213. package/src/Styles/Nexus.Editor.css +295 -295
  214. package/src/Styles/Nexus.Excel.css +389 -389
  215. package/src/Styles/Nexus.Files.css +410 -0
  216. package/src/Styles/Nexus.Form.css +282 -279
  217. package/src/Styles/Nexus.Index.css +41 -37
  218. package/src/Styles/Nexus.Input.css +238 -4
  219. package/src/Styles/Nexus.Markdown.css +237 -237
  220. package/src/Styles/Nexus.Menu.css +246 -237
  221. package/src/Styles/Nexus.Popup.css +800 -724
  222. package/src/Styles/Nexus.Print.css +288 -0
  223. package/src/Styles/Nexus.QrCode.css +12 -0
  224. package/src/Styles/Nexus.Record.css +430 -0
  225. package/src/Styles/Nexus.Rows.css +637 -637
  226. package/src/Styles/Nexus.Slider.css +276 -276
  227. package/src/Styles/Nexus.Tab.Buttons.css +497 -432
  228. package/src/Styles/Nexus.Table.css +98 -35
  229. package/src/Styles/Nexus.Theme.Picker.css +59 -3
  230. package/src/Styles/Nexus.Themes.css +200 -194
  231. package/src/Styles/Nexus.Time.css +144 -144
  232. package/src/Styles/Nexus.Toast.css +263 -263
  233. package/src/Styles/Nexus.Tokens.css +94 -89
  234. package/src/Styles/Nexus.Tree.css +530 -431
@@ -31,6 +31,30 @@ export interface TextRules {
31
31
  /** Runs after the built-in rules pass. */
32
32
  validate?: TextValidator;
33
33
  }
34
+ /**
35
+ * What a code box takes, one character to a box:
36
+ * - `digits`: 0–9, with the phone's number pad. A one-time code, a PIN, an SSN.
37
+ * - `letters`: A–Z.
38
+ * - `alphanumeric`: letters and digits, such as a licence key or a booking reference.
39
+ * - `hex`: 0–9 and A–F, such as a colour or a device id.
40
+ */
41
+ export type CodeCharacters = "digits" | "letters" | "alphanumeric" | "hex";
42
+ /** The shape and rules of a code: how many boxes, in which groups, and what each box takes. Plain values, so params stay serializable. */
43
+ export interface CodeRules {
44
+ /** How many boxes. Default 6. Left out when `groups` is given. */
45
+ length?: number;
46
+ /** Boxes in groups, with a gap or a `separator` between them: `[3, 2, 4]` for an SSN, `[3, 3]` for a one-time code. */
47
+ groups?: readonly number[];
48
+ /** Default `digits`. */
49
+ characters?: CodeCharacters;
50
+ /** A regular expression for one character, in place of `characters`: `"[A-HJ-NP-Z2-9]"` leaves out I, O, 0 and 1. */
51
+ allowed?: string;
52
+ /** Letters as typed are turned to this case. Default `upper`; `any` keeps them as typed. */
53
+ letterCase?: "upper" | "lower" | "any";
54
+ required?: boolean;
55
+ /** Runs after the built-in rules pass, with the code. */
56
+ validate?: TextValidator;
57
+ }
34
58
  /** Custom check for a number. Receives the value, or `null` when the field is empty; returns a message to fail. */
35
59
  export type NumberValidator = (value: number | null) => string | null | undefined;
36
60
  /** Validation rules for a number, named like the HTML attributes. */
@@ -103,8 +127,10 @@ export type DropdownItem = object;
103
127
  * - `avatar`: a round photo from a URL; the initials of the item's label when there is no URL or it fails to load.
104
128
  * - `image`: a small picture from a URL, 4:3, such as a flag or a logo.
105
129
  * - `icon`: a Tabler icon by name, such as `"wifi"`.
130
+ * - `money`: a figure with 2 places in the person's number format (1,00,000.00 under lakh and crore), at the end of the
131
+ * column in even digits, such as a fee's price beside its name; empty for a missing one.
106
132
  */
107
- export type DropdownColumnType = "text" | "muted" | "code" | "avatar" | "image" | "icon";
133
+ export type DropdownColumnType = "text" | "muted" | "code" | "avatar" | "image" | "icon" | "money";
108
134
  /** One column of a dropdown's rows. Plain values, so a list of fields can describe it; `render` is the only function. */
109
135
  export interface DropdownColumn<TItem extends DropdownItem = DropdownItem> {
110
136
  /** The item's field, e.g. `"username"`. */
@@ -113,9 +139,9 @@ export interface DropdownColumn<TItem extends DropdownItem = DropdownItem> {
113
139
  header?: string;
114
140
  /** Default `text`. */
115
141
  type?: DropdownColumnType;
116
- /** A CSS grid track: `"6rem"`, `"auto"`, `"1fr"`. Default: `auto` for avatar, image, icon, and code; `minmax(0, 1fr)` for text. */
142
+ /** A CSS grid track: `"6rem"`, `"auto"`, `"1fr"`. Default: `auto` for avatar, image, icon, code, and money; `minmax(0, 1fr)` for text. */
117
143
  width?: string;
118
- /** Default `start`. */
144
+ /** Default `start`; `end` for `money`. */
119
145
  align?: "start" | "center" | "end";
120
146
  /** Draws the cell instead of `type`. */
121
147
  render?: (item: TItem) => ReactNode;
@@ -130,6 +156,16 @@ export interface DropdownPageRequest {
130
156
  skip: number;
131
157
  /** Aborted when a newer search replaces this request, or the dropdown unmounts. */
132
158
  signal: AbortSignal;
159
+ /**
160
+ * Only the options whose `key` field holds `value`: set by a form for an input another narrows (`narrowedBy`). A source
161
+ * of its own should honour it (`crudPicker` does); a static list is narrowed by the form itself. `orUnset`: the options
162
+ * whose `key` field is empty are offered too (`narrowedBy.orUnset`), and a `null` value then offers only those.
163
+ */
164
+ narrow?: {
165
+ key: string;
166
+ value: OptionValue | null;
167
+ orUnset?: boolean;
168
+ };
133
169
  }
134
170
  /** What `loadOptions` returns for one page. */
135
171
  export interface DropdownPage<TItem extends DropdownItem = DropdownItem> {
@@ -1,5 +1,5 @@
1
1
  /** The actions the builder knows by name (`MESSAGE_ACTION_NAMES`); `actionWords(name)` gives their words. */
2
- export type MessageActionName = "load" | "save" | "create" | "update" | "delete" | "trash" | "restore" | "recover" | "flag" | "unflag" | "pin" | "unpin" | "send" | "submit" | "upload" | "download" | "import" | "export" | "copy" | "approve" | "reject" | "cancel";
2
+ export type MessageActionName = "load" | "save" | "create" | "update" | "delete" | "trash" | "restore" | "recover" | "flag" | "unflag" | "pin" | "unpin" | "favorite" | "unfavorite" | "send" | "submit" | "upload" | "download" | "import" | "export" | "copy" | "approve" | "reject" | "cancel";
3
3
  /** The words of an action. */
4
4
  export interface ActionWords {
5
5
  /** "save", or with the object inside: "move {object} to the trash". */
@@ -1,3 +1,4 @@
1
+ import type { InputDesign } from "./InputInterfaces.ts";
1
2
  /** The words of the app itself. Data keeps the language it was entered in. */
2
3
  export type InterfaceLanguage = "en" | "ne";
3
4
  /**
@@ -31,6 +32,12 @@ export interface UserPreferences {
31
32
  * the table says itself. `null` (the default): not chosen, each table's own `openOnClick`.
32
33
  */
33
34
  rowClick: boolean | null;
35
+ /**
36
+ * The design every input takes when its page or form names none: the label above the box (`standard`), on its edge
37
+ * (`outlined`) or inside its top (`inbox`). `null` (the default): not chosen, `standard`. An input whose page fixes a
38
+ * design - a grid's cells, a demo of the designs - keeps it.
39
+ */
40
+ inputDesign: InputDesign | null;
34
41
  }
35
42
  export declare const DEFAULT_USER_PREFERENCES: UserPreferences;
36
43
  /** Local storage keys of the settings. A number scale is kept as its member value ("0", "1"), a row click as "1" or "0". */
@@ -40,4 +47,5 @@ export declare const PREFERENCE_STORAGE_KEYS: {
40
47
  readonly thousands: "nx-thousands";
41
48
  readonly pageSize: "nx-page-size";
42
49
  readonly rowClick: "nx-row-click";
50
+ readonly inputDesign: "nx-input-design";
43
51
  };
@@ -1,8 +1,3 @@
1
- /*
2
- * The person's own settings beyond the theme and the dates: the interface language, how numbers are written, and what a
3
- * table does before anyone touches its own controls. Saved on this device and read by the components that show them,
4
- * so a change applies to everything on the page at once. The date settings are `DatePreferences` (DateInterfaces.ts).
5
- */
6
1
  /**
7
2
  * The core's `ENumberScales`, by member value: where the groups of a number fall. `International` writes 1,234,567
8
3
  * (thousand, million, billion); `SouthAsian` writes 12,34,567 (thousand, lakh, crore).
@@ -17,6 +12,7 @@ export const DEFAULT_USER_PREFERENCES = {
17
12
  thousands: "comma",
18
13
  pageSize: null,
19
14
  rowClick: null,
15
+ inputDesign: null,
20
16
  };
21
17
  /** Local storage keys of the settings. A number scale is kept as its member value ("0", "1"), a row click as "1" or "0". */
22
18
  export const PREFERENCE_STORAGE_KEYS = {
@@ -25,4 +21,5 @@ export const PREFERENCE_STORAGE_KEYS = {
25
21
  thousands: "nx-thousands",
26
22
  pageSize: "nx-page-size",
27
23
  rowClick: "nx-row-click",
24
+ inputDesign: "nx-input-design",
28
25
  };
@@ -0,0 +1,367 @@
1
+ import type { ReactNode } from "react";
2
+ import type { PopupSize } from "../Components/Layouts/Popup.tsx";
3
+ import type { ApiEndpoint, ApiResult, ApiTarget } from "./ApiInterfaces.ts";
4
+ import type { CrudDetailsPicture } from "./CrudInterfaces.ts";
5
+ import type { FormStore } from "../Helpers/FormStore.ts";
6
+ import type { ApiRequest, ApiSend, FormRuleResult, FormSubmitTone, FormValues, InputParams } from "./FormInterfaces.ts";
7
+ import type { Tone } from "./TypeInterfaces.ts";
8
+ /**
9
+ * How a part of the page is read: an endpoint or a URL, read with GET through the app's `api`, or a function of the
10
+ * page's own answering an `ApiResult` (the API client's shape: it never throws).
11
+ */
12
+ export type RecordRead<T = any> = ApiTarget | ((signal: AbortSignal) => Promise<ApiResult<T>>);
13
+ /** A source given with what is known before it is read: the name the session already has, drawn while it loads. */
14
+ export interface RecordSourceParams<T = any> {
15
+ read: RecordRead<T>;
16
+ /** Drawn while the read runs (the hero's name from the session), so nothing moves when it lands. */
17
+ held?: Partial<T>;
18
+ }
19
+ /**
20
+ * What an answer writes into the card's data, which every card on the same source then shows:
21
+ * - `"answer"`: a record answer, merged over the data as held now (only the fields it carries); a list answer replaces a
22
+ * list. Default. An answer that is neither falls back to the values (a card's Edit) or a read again (the rest).
23
+ * - `"values"`: the values sent, merged over the data as held now.
24
+ * - `"item"`: the answer is one item: it replaces the item of the same key, or is added at the end.
25
+ * - `"remove"`: the item acted on leaves the list.
26
+ * - `"reload"`: the source is read again. `"none"`: nothing.
27
+ * - field names: those the answer carries, over the data as held now.
28
+ * - a function: what to merge over the data as held now (a record), or the data's new value; `null` writes nothing.
29
+ */
30
+ export type RecordWrites<TData = any, TValues = any> = "answer" | "values" | "item" | "remove" | "reload" | "none" | readonly string[] | ((result: unknown, data: TData, values: TValues) => TData | Partial<TData> | null | undefined);
31
+ /** A short mark beside a name: "Primary", "Not verified", "This device". */
32
+ export interface RecordMark {
33
+ label: ReactNode;
34
+ tone?: Tone;
35
+ }
36
+ /**
37
+ * Where a step's values go: an endpoint, a URL (sent as a POST of JSON), a request to a URL outside the app's modules
38
+ * (`{ url, method }`), or a function of the page's own.
39
+ */
40
+ export type RecordSubmit<TValues extends FormValues = any> = string | ApiRequest | ApiEndpoint | ApiSend<TValues>;
41
+ /** What every step, confirm and action is handed: the card's data and the item as held now, and the ways on. */
42
+ export interface RecordContext<TData = any, TItem = any> {
43
+ /** The card's data as the page holds it now. */
44
+ data: TData;
45
+ /** The item acted on, as held now (found again by its key after each write); `undefined` for a card's own action. */
46
+ item: TItem | undefined;
47
+ /** The page or the card is read-only. */
48
+ readOnly: boolean;
49
+ /** Reads the card's source again: its data, or `null` when it could not be read (said in a toast, the data kept). */
50
+ reload: () => Promise<TData | null>;
51
+ /** Writes the card's source: every card on it shows the new data. */
52
+ write: (next: TData) => void;
53
+ /** Runs one of the card's item actions on an item: Make primary after a Verify left no primary, the code step after an Add. */
54
+ run: (action: string, item: [TItem] extends [undefined] ? any : TItem) => void;
55
+ /** Opens a popup for this card (and this item): a content card's own button, a two-step setup. */
56
+ open: (flow: RecordFlowParams<TData, TItem>) => void;
57
+ }
58
+ /** How a step is shown by `goTo`. */
59
+ export interface RecordGoToOptions {
60
+ /** Shown afresh: what was typed in it before (a password, an old code) is dropped, and it opens on its values again. */
61
+ fresh?: boolean;
62
+ }
63
+ /** What a popup's step is handed: the context, which step it is, its form, and the answer of the step that led to it. */
64
+ export interface RecordStepContext<TData = any, TItem = any> extends RecordContext<TData, TItem> {
65
+ step: string;
66
+ /** The answer of the step before, the one that led here. */
67
+ result: unknown;
68
+ /**
69
+ * The step's form: its values as typed now (`form.get("countryId")`), for inputs whose label, `hidden` or options follow
70
+ * another input through getters (give them `dependsOn`). Each step has its own; one shown fresh gets a new one.
71
+ */
72
+ form: FormStore;
73
+ /** Shows another step of the popup; `{ fresh: true }` shows it as if for the first time. */
74
+ goTo: (step: string, options?: RecordGoToOptions) => void;
75
+ /** Closes the popup. */
76
+ close: () => void;
77
+ }
78
+ /** What a step's `next` and `done` are handed after its save: the context, the answer, and the values sent. */
79
+ export interface RecordStepAnswer<TData = any, TItem = any, TValues = any> extends RecordStepContext<TData, TItem> {
80
+ result: unknown;
81
+ values: TValues;
82
+ }
83
+ /** A button in a step that calls an endpoint and stays on the step: "Send a new code", held while the server says. */
84
+ export interface RecordCallParams<TData = any, TItem = any> {
85
+ label: ReactNode | ((context: RecordStepContext<TData, TItem>) => ReactNode);
86
+ icon?: ReactNode;
87
+ submit: ApiTarget | ApiRequest | ((body: unknown) => Promise<unknown>);
88
+ /** What is sent. Default: nothing. */
89
+ transform?: (context: RecordStepContext<TData, TItem>) => unknown;
90
+ /** What its answer writes (default `"answer"`, else nothing). */
91
+ writes?: RecordWrites<TData>;
92
+ /** Held until this moment (an ISO text or a time in ms), counted down in its label: the server's resend time. */
93
+ waitUntil?: (context: RecordStepContext<TData, TItem>) => string | number | null | undefined;
94
+ /** The label while it waits. Default "Again in 23s". */
95
+ waitLabel?: (seconds: number) => ReactNode;
96
+ /** The toast after it answered. Default the server's message; `false` for none. */
97
+ successMessage?: ReactNode | false;
98
+ /** The input given the cursor after it answered: the code's boxes. */
99
+ focus?: string;
100
+ }
101
+ /** One step of a popup: a sentence, its inputs, where they go, what the answer writes, and what follows. */
102
+ export interface RecordStepParams<TData = any, TItem = any, TValues extends FormValues = any> {
103
+ /** Names the step for `next` and `start`. Default its place: "1", "2". */
104
+ id?: string;
105
+ /** The popup's title on this step. Default the flow's. */
106
+ title?: ReactNode | ((context: RecordStepContext<TData, TItem>) => ReactNode);
107
+ /** The line under the title. Default the flow's. */
108
+ description?: ReactNode | ((context: RecordStepContext<TData, TItem>) => ReactNode);
109
+ /** A sentence over the inputs: what will happen, where a code went. It follows the data as held now. */
110
+ lead?: ReactNode | ((context: RecordStepContext<TData, TItem>) => ReactNode);
111
+ /**
112
+ * The inputs. None: the step is its sentence and its button ("Send a code"). A function is handed the step's `form`,
113
+ * so an input's label or `hidden` can follow another input as typed (a region level named after the place above it).
114
+ */
115
+ inputs?: readonly InputParams[] | ((context: RecordStepContext<TData, TItem>) => readonly InputParams[]);
116
+ /** What the form opens on. Default: the item for an item's step, the card's data for a card's Edit, nothing for an Add. */
117
+ values?: (context: RecordStepContext<TData, TItem>) => Partial<TValues>;
118
+ /** Where the values go. */
119
+ submit?: RecordSubmit<TValues>;
120
+ /**
121
+ * The step's own rule over all its values, asked before anything is sent, as a form's `validate`: messages by input
122
+ * name, or one text for the whole step (a new password and its confirmation that differ).
123
+ */
124
+ validate?: (values: TValues, context: RecordStepContext<TData, TItem>) => FormRuleResult;
125
+ /**
126
+ * Shown afresh each time it is reached again (by `next` or `goTo`): a step of a password or a code, whose old values
127
+ * are no use. Default false: a step shown again keeps what was typed in it.
128
+ */
129
+ fresh?: boolean;
130
+ /**
131
+ * What is sent. Default: the values merged over what the step opened on, as held now - the whole record for a card's
132
+ * Edit (so an endpoint that takes the whole record works), the item for an item's step; the values alone for an Add.
133
+ */
134
+ transform?: (values: TValues, context: RecordStepContext<TData, TItem>) => unknown;
135
+ /** Default "Save". */
136
+ submitLabel?: ReactNode;
137
+ /** Default a floppy disk; `null` for none. */
138
+ submitIcon?: ReactNode | null;
139
+ /** `danger` for a step whose save removes something. */
140
+ submitTone?: FormSubmitTone;
141
+ /** What the answer writes into the card's data. Default `"answer"`. */
142
+ writes?: RecordWrites<TData, TValues>;
143
+ /** The step after this one, by its id; nothing closes the popup. */
144
+ next?: string | ((answer: RecordStepAnswer<TData, TItem, TValues>) => string | null | undefined);
145
+ /** After the last step's save, once the popup has closed: open what follows (`run`). */
146
+ done?: (answer: RecordStepAnswer<TData, TItem, TValues>) => void;
147
+ /** A button under the form that calls an endpoint and stays on the step. */
148
+ also?: RecordCallParams<TData, TItem>;
149
+ /** The toast after the save. Default the server's message; `false` for none. */
150
+ successMessage?: ReactNode | false | ((answer: RecordStepAnswer<TData, TItem, TValues>) => ReactNode | false);
151
+ /** Told of a refusal, which the form already shows on its inputs: a code that offers another way. */
152
+ onError?: (answer: ApiResult, context: RecordStepContext<TData, TItem>) => void;
153
+ /**
154
+ * Cancel, the close button and leaving the page ask before dropping changes. Default true. A password or a code typed
155
+ * is never asked about (it is no change worth keeping); false for a step whose other values are not worth it either.
156
+ */
157
+ confirmCancel?: boolean;
158
+ /** The page's own part under the form: a "Set a password" link after a refusal. */
159
+ render?: (context: RecordStepContext<TData, TItem>) => ReactNode;
160
+ }
161
+ /**
162
+ * A popup: one step (the flow is that step) or several (`steps`), opened by a card's Edit or Add, or by an item's
163
+ * action. One popup per card, action and item: opened again, it comes back as it was.
164
+ */
165
+ export interface RecordFlowParams<TData = any, TItem = any, TValues extends FormValues = any> extends RecordStepParams<TData, TItem, TValues> {
166
+ /** The button that opens it: "Edit", "Add". */
167
+ label?: string;
168
+ /** The button's icon and the popup's. */
169
+ icon?: ReactNode;
170
+ /** Default `md` for a card's Edit with more than two inputs, else `sm`. */
171
+ size?: PopupSize;
172
+ /** Several steps, in order: the first unless `start` says. */
173
+ steps?: readonly RecordStepParams<TData, TItem>[];
174
+ /** The step it opens on: "code" when a code already waits. */
175
+ start?: string | ((context: RecordContext<TData, TItem>) => string);
176
+ }
177
+ /** A question before a change that needs no form: "Remove ram@example.com?", then the call. */
178
+ export interface RecordConfirmParams<TData = any, TItem = any> {
179
+ title: ReactNode | ((context: RecordContext<TData, TItem>) => ReactNode);
180
+ message?: ReactNode | ((context: RecordContext<TData, TItem>) => ReactNode);
181
+ /** Default "Remove" for a danger tone, else "OK". */
182
+ confirmLabel?: ReactNode;
183
+ /** Default `danger`. */
184
+ tone?: "danger" | "warning" | "question" | "info";
185
+ submit: ApiTarget | ApiRequest | ((body: unknown) => Promise<unknown>);
186
+ /** What is sent. Default: the item, or the card's data. */
187
+ transform?: (context: RecordContext<TData, TItem>) => unknown;
188
+ /** What the answer writes. Default `"answer"`, else a read again. */
189
+ writes?: RecordWrites<TData>;
190
+ /** The toast after it. Default the server's message, else "Done". `false` for none. */
191
+ successMessage?: ReactNode | false | ((context: RecordContext<TData, TItem>) => ReactNode | false);
192
+ /** After the call answered and the card was written. */
193
+ done?: (context: RecordContext<TData, TItem> & {
194
+ result: unknown;
195
+ }) => void;
196
+ }
197
+ /** A button in a card's heading: a popup (`flow`), a question then a call (`confirm`), or the page's own (`onSelect`). */
198
+ export interface RecordCardActionParams<TData = any> {
199
+ id: string;
200
+ label: string;
201
+ icon?: ReactNode;
202
+ tone?: "danger";
203
+ /** Not shown for this data. Asked only once the data is read (see `loading`). */
204
+ hidden?: (data: TData) => boolean;
205
+ /**
206
+ * Shown switched off with this reason, said when pressed: "You have 5 emails, the most there may be." Asked only once
207
+ * the data is read.
208
+ */
209
+ held?: (data: TData) => string | false | null | undefined;
210
+ /**
211
+ * How it shows while the data is read, before `hidden` and `held` can be asked: `held` (default) switched off, saying
212
+ * "Still being read."; `hidden` not yet drawn - for an action seldom shown (Turn off on a setting mostly off).
213
+ */
214
+ loading?: "held" | "hidden";
215
+ flow?: RecordFlowParams<TData, undefined>;
216
+ confirm?: RecordConfirmParams<TData, undefined>;
217
+ onSelect?: (context: RecordContext<TData, undefined>) => void;
218
+ }
219
+ /** An icon button on the right of an item: named as a tooltip and to a screen reader, hidden or held with its reason. */
220
+ export interface RecordItemActionParams<TData = any, TItem = any> {
221
+ id: string;
222
+ /** Its name, the tooltip and what a screen reader says (with the item's name): "Verify", "Make primary". */
223
+ label: string | ((item: TItem, data: TData) => string);
224
+ icon: ReactNode;
225
+ tone?: "danger";
226
+ /** Not shown on this item. */
227
+ hidden?: (item: TItem, data: TData) => boolean;
228
+ /** Shown switched off with this reason, said when pressed: "The primary email cannot be removed." */
229
+ held?: (item: TItem, data: TData) => string | false | null | undefined;
230
+ flow?: RecordFlowParams<TData, TItem>;
231
+ confirm?: RecordConfirmParams<TData, TItem>;
232
+ onSelect?: (context: RecordContext<TData, TItem>) => void;
233
+ }
234
+ /** What one card has whatever it holds. */
235
+ export interface RecordCardBase<TData = any> {
236
+ /** The card's element id, so a link can lead to it (`/dashboard/profile#emails`); unique on the page. */
237
+ id: string;
238
+ /** The source it is drawn from (a key of `sources`). Default the first. */
239
+ source?: string;
240
+ title: ReactNode;
241
+ icon?: ReactNode;
242
+ /** A sentence under the title. */
243
+ description?: ReactNode | ((data: TData) => ReactNode);
244
+ /** Buttons in the heading, after the card's own Edit or Add. */
245
+ actions?: readonly RecordCardActionParams<TData>[];
246
+ /** Not shown for this data (judged once it is read). */
247
+ hidden?: (data: TData) => boolean;
248
+ /** Takes the page's whole width. */
249
+ wide?: boolean;
250
+ /** No buttons and no actions in this card. */
251
+ readOnly?: boolean;
252
+ }
253
+ /** One value of a fields card, when its input alone does not say how to show it. */
254
+ export interface RecordFieldParams<TData = any> {
255
+ /** The input the value is drawn by (a choice by its label, a date in the person's format) and edited with. */
256
+ input: InputParams;
257
+ /** Default the input's label. */
258
+ label?: ReactNode;
259
+ /** `chips` for a list written with commas (keywords), `lines` for text on its own lines. Default by the input's type. */
260
+ look?: "text" | "lines" | "chips";
261
+ /** The value drawn by the page. */
262
+ show?: (value: unknown, data: TData) => ReactNode;
263
+ /** In the Edit popup. Default true; false for a value only shown here (the primary email). */
264
+ edit?: boolean;
265
+ hidden?: (data: TData) => boolean;
266
+ /** Takes the card's whole width. Default for long text and chips. */
267
+ wide?: boolean;
268
+ /**
269
+ * Lines the value holds while it is read, so the card is as tall as it will be: about what the value usually takes.
270
+ * Default 2 for long text, else 1.
271
+ */
272
+ heldLines?: number;
273
+ }
274
+ /** A card of labels and values, drawn from the inputs its Edit popup uses. */
275
+ export interface RecordFieldsCardParams<TData = any> extends RecordCardBase<TData> {
276
+ fields: readonly (InputParams | RecordFieldParams<TData>)[];
277
+ /**
278
+ * The heading's Edit: a popup of the card's inputs (those with `edit` not false) on the data as loaded, whose values are
279
+ * merged over the record as held now, so an endpoint that takes the whole record works. Needs `submit`.
280
+ */
281
+ edit?: RecordFlowParams<TData, undefined>;
282
+ /** What an empty value says. Default "Not said". */
283
+ empty?: ReactNode;
284
+ }
285
+ /** How one item reads: a picture or an icon, its name, lines under it, and marks. */
286
+ export interface RecordItemView {
287
+ title: ReactNode;
288
+ lines?: readonly ReactNode[];
289
+ marks?: readonly RecordMark[];
290
+ /** A picture at the start (an `Avatar`, an `OwnerPicture`). */
291
+ picture?: ReactNode;
292
+ /** An icon at the start, where there is no picture. */
293
+ icon?: ReactNode;
294
+ }
295
+ /** A card of items: a list with Add in its heading and icon actions on each item's right. */
296
+ export interface RecordItemsCardParams<TData = any, TItem = any> extends RecordCardBase<TData> {
297
+ /** Where the items are: a field of the data (`"items"`), or a function. Default: the data is the list. */
298
+ items?: string | ((data: TData) => readonly TItem[]);
299
+ /** What tells one item from another: a field, or a function. */
300
+ itemKey: string | ((item: TItem) => string | number);
301
+ item: (item: TItem, data: TData) => RecordItemView;
302
+ /** The item's name for a screen reader ("Verify: ram@example.com"). Default its title when it is text. */
303
+ itemLabel?: (item: TItem) => string;
304
+ itemActions?: readonly RecordItemActionParams<TData, TItem>[];
305
+ /** The heading's Add: a popup form; held at the `limit`, saying why. Needs `submit`. */
306
+ add?: RecordFlowParams<TData, undefined>;
307
+ /** The most there may be: shown as "3 of 5", and Add held there. A field of the data, a number or a function. */
308
+ limit?: number | string | ((data: TData) => number | null | undefined);
309
+ /** Why Add is held at the limit. Default "The most there may be is 5. Remove one first." */
310
+ limitReason?: string | ((limit: number) => string);
311
+ /** What an empty list says. Default "Nothing here yet." */
312
+ empty?: ReactNode;
313
+ /** Rows drawn while the list loads: as many as it usually has, so nothing under the card moves. Default 2. */
314
+ heldRows?: number;
315
+ /** Lines under each held row's name: as many as a row usually has (`item(...).lines`). Default 1. */
316
+ heldRowLines?: number;
317
+ }
318
+ /** A card of the page's own. */
319
+ export interface RecordContentCardParams<TData = any> extends RecordCardBase<TData> {
320
+ render: (context: RecordContext<TData, undefined>) => ReactNode;
321
+ }
322
+ export type RecordCardParams<TData = any, TItem = any> = RecordFieldsCardParams<TData> | RecordItemsCardParams<TData, TItem> | RecordContentCardParams<TData>;
323
+ /** The card across the top: the picture (a click opens the pictures window), the name, a line or two, and marks. */
324
+ export interface RecordHeroParams<TData = any> {
325
+ /** Default the first source. */
326
+ source?: string;
327
+ /** The owner's main picture by its Drive params (`CrudDetailsPicture`), or anything to draw in its place. */
328
+ picture?: (data: TData) => CrudDetailsPicture | ReactNode;
329
+ title: (data: TData) => ReactNode;
330
+ /** Lines under the name. While the source loads they are given what `held` says; an empty one keeps its line. */
331
+ lines?: (data: TData) => readonly ReactNode[];
332
+ marks?: (data: TData) => readonly RecordMark[];
333
+ actions?: readonly RecordCardActionParams<TData>[];
334
+ }
335
+ /** What the page shows when a read or a change finds the session ended: its own words, and the way back in. */
336
+ export interface RecordSignedOutParams {
337
+ /** Default "Your session has ended". */
338
+ title?: ReactNode;
339
+ /** Default the answer's message. */
340
+ message?: ReactNode | ((answer: ApiResult) => ReactNode);
341
+ /** Default "Sign in again". */
342
+ label?: ReactNode;
343
+ onSignIn: () => void;
344
+ /** Told once, when the session is found ended. */
345
+ onEnded?: (answer: ApiResult) => void;
346
+ }
347
+ export interface RecordCardsProps {
348
+ /**
349
+ * What the page reads, by name, each once when it is first drawn (a new `key` reads them afresh):
350
+ * `{ profile: { read: ACCOUNT_API.profile, held: { fullName } }, emails: ACCOUNT_API.emails }`.
351
+ */
352
+ sources: Readonly<Record<string, RecordRead | RecordSourceParams>>;
353
+ hero?: RecordHeroParams;
354
+ cards: readonly RecordCardParams[];
355
+ /** No buttons and no actions anywhere: a person who may only look. */
356
+ readOnly?: boolean;
357
+ /** Without it, an ended session is a failed read like any other. */
358
+ signedOut?: RecordSignedOutParams;
359
+ /** Which answers mean the session ended. Default: a 401 that names no input (a wrong password names its box). */
360
+ isSessionEnded?: (answer: ApiResult) => boolean;
361
+ /** Tells this page's popups from another's. Default "record". */
362
+ name?: string;
363
+ /** The level of the cards' headings. Default 2, under the page's own. */
364
+ headingLevel?: 2 | 3;
365
+ "aria-label"?: string;
366
+ className?: string;
367
+ }
@@ -0,0 +1 @@
1
+ export {};