nexus-shared 3.0.0 → 3.2.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 (162) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/README.md +1123 -1000
  3. package/dist/Client.Index.d.ts +6 -0
  4. package/dist/Client.Index.js +6 -0
  5. package/dist/Components/Chats/ChatParts.d.ts +2 -2
  6. package/dist/Components/Chats/ChatParts.js +3 -7
  7. package/dist/Components/Chats/ChatThread.js +3 -3
  8. package/dist/Components/Documents/Button.d.ts +6 -1
  9. package/dist/Components/Documents/Button.js +2 -2
  10. package/dist/Components/Documents/Menu.js +21 -9
  11. package/dist/Components/Documents/SplitButton.d.ts +3 -1
  12. package/dist/Components/Documents/SplitButton.js +7 -5
  13. package/dist/Components/Documents/TabButtons.d.ts +5 -2
  14. package/dist/Components/Documents/TabButtons.js +2 -2
  15. package/dist/Components/Forms/ApiForm.d.ts +1 -1
  16. package/dist/Components/Forms/ApiForm.js +1 -1
  17. package/dist/Components/Forms/Crud.js +486 -206
  18. package/dist/Components/Forms/CrudHistory.d.ts +66 -0
  19. package/dist/Components/Forms/CrudHistory.js +140 -0
  20. package/dist/Components/Forms/CrudParts.d.ts +5 -1
  21. package/dist/Components/Forms/CrudParts.js +38 -5
  22. package/dist/Components/Forms/ExcelImport.d.ts +23 -7
  23. package/dist/Components/Forms/ExcelImport.js +189 -60
  24. package/dist/Components/Forms/Form.js +9 -14
  25. package/dist/Components/Forms/SubmitForm.d.ts +11 -2
  26. package/dist/Components/Forms/SubmitForm.js +23 -12
  27. package/dist/Components/Inputs/CodeBox.d.ts +69 -0
  28. package/dist/Components/Inputs/CodeBox.js +316 -0
  29. package/dist/Components/Inputs/DatePicker.js +4 -1
  30. package/dist/Components/Inputs/DateRangePicker.js +4 -1
  31. package/dist/Components/Inputs/DateTimePicker.js +4 -1
  32. package/dist/Components/Inputs/Dropdown.js +4 -1
  33. package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
  34. package/dist/Components/Inputs/DropdownParts.js +4 -6
  35. package/dist/Components/Inputs/InputRenderer.js +3 -0
  36. package/dist/Components/Inputs/NumberBox.js +26 -2
  37. package/dist/Components/Inputs/RowsInput.js +2 -2
  38. package/dist/Components/Inputs/Slider.js +4 -1
  39. package/dist/Components/Inputs/TabularForm.js +42 -10
  40. package/dist/Components/Inputs/TextArea.js +4 -1
  41. package/dist/Components/Inputs/TextBox.js +4 -1
  42. package/dist/Components/Inputs/TimePanel.js +4 -3
  43. package/dist/Components/Inputs/TimePicker.js +4 -1
  44. package/dist/Components/Inputs/TreeView.d.ts +55 -5
  45. package/dist/Components/Inputs/TreeView.js +372 -23
  46. package/dist/Components/Layouts/Popup.js +1 -4
  47. package/dist/Components/Layouts/ThemePicker.d.ts +21 -3
  48. package/dist/Components/Layouts/ThemePicker.js +89 -6
  49. package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
  50. package/dist/Components/Layouts/ThemeScript.js +4 -2
  51. package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
  52. package/dist/Components/Layouts/ThemeSwitcher.js +9 -8
  53. package/dist/Components/Viewers/Avatar.d.ts +21 -0
  54. package/dist/Components/Viewers/Avatar.js +21 -0
  55. package/dist/Components/Viewers/DataTable.js +252 -62
  56. package/dist/Components/Viewers/DataTableColumns.d.ts +7 -3
  57. package/dist/Components/Viewers/DataTableColumns.js +25 -68
  58. package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
  59. package/dist/Components/Viewers/DataTableFilters.js +68 -0
  60. package/dist/Components/Viewers/DataTableParts.d.ts +28 -10
  61. package/dist/Components/Viewers/DataTableParts.js +42 -13
  62. package/dist/Helpers/AnimationHelpers.d.ts +11 -0
  63. package/dist/Helpers/AnimationHelpers.js +20 -2
  64. package/dist/Helpers/ApiModules.d.ts +3 -34
  65. package/dist/Helpers/ApiModules.js +3 -3
  66. package/dist/Helpers/ApiRoutes.d.ts +68 -26
  67. package/dist/Helpers/ApiRoutes.js +31 -27
  68. package/dist/Helpers/ArrayHelpers.d.ts +0 -0
  69. package/dist/Helpers/ArrayHelpers.js +2 -0
  70. package/dist/Helpers/CodeHelpers.d.ts +97 -0
  71. package/dist/Helpers/CodeHelpers.js +332 -0
  72. package/dist/Helpers/CrudBackend.d.ts +101 -8
  73. package/dist/Helpers/CrudBackend.js +172 -11
  74. package/dist/Helpers/CrudColumns.d.ts +51 -0
  75. package/dist/Helpers/CrudColumns.js +69 -0
  76. package/dist/Helpers/CrudHelpers.d.ts +50 -5
  77. package/dist/Helpers/CrudHelpers.js +109 -12
  78. package/dist/Helpers/ExcelBackend.d.ts +3 -3
  79. package/dist/Helpers/ExcelBackend.js +8 -2
  80. package/dist/Helpers/ExcelHelpers.d.ts +36 -8
  81. package/dist/Helpers/ExcelHelpers.js +55 -13
  82. package/dist/Helpers/FormStore.d.ts +9 -5
  83. package/dist/Helpers/FormStore.js +25 -5
  84. package/dist/Helpers/InputParamsHelpers.js +8 -0
  85. package/dist/Helpers/MessageBuilder.js +3 -0
  86. package/dist/Helpers/NexusHelpers.d.ts +0 -0
  87. package/dist/Helpers/NexusHelpers.js +1 -0
  88. package/dist/Helpers/NumberHelpers.d.ts +7 -1
  89. package/dist/Helpers/NumberHelpers.js +41 -3
  90. package/dist/Helpers/PopoverHelpers.d.ts +17 -5
  91. package/dist/Helpers/PopoverHelpers.js +52 -17
  92. package/dist/Helpers/PopoverHooks.d.ts +24 -0
  93. package/dist/Helpers/PopoverHooks.js +62 -0
  94. package/dist/Helpers/RequestCache.d.ts +35 -0
  95. package/dist/Helpers/RequestCache.js +113 -0
  96. package/dist/Helpers/RowsStore.d.ts +7 -2
  97. package/dist/Helpers/RowsStore.js +15 -3
  98. package/dist/Helpers/TableFilters.d.ts +25 -0
  99. package/dist/Helpers/TableFilters.js +162 -0
  100. package/dist/Helpers/TableHelpers.d.ts +7 -4
  101. package/dist/Helpers/TableHelpers.js +15 -6
  102. package/dist/Helpers/TableJoins.d.ts +70 -0
  103. package/dist/Helpers/TableJoins.js +250 -0
  104. package/dist/Helpers/TableStore.d.ts +16 -3
  105. package/dist/Helpers/TableStore.js +25 -5
  106. package/dist/Helpers/ThemeHelpers.d.ts +21 -4
  107. package/dist/Helpers/ThemeHelpers.js +46 -9
  108. package/dist/Helpers/TreeStore.d.ts +225 -11
  109. package/dist/Helpers/TreeStore.js +683 -90
  110. package/dist/Helpers/UserPreferences.d.ts +22 -0
  111. package/dist/Helpers/UserPreferences.js +116 -0
  112. package/dist/Interfaces/CrudInterfaces.d.ts +67 -10
  113. package/dist/Interfaces/ExcelInterfaces.d.ts +12 -0
  114. package/dist/Interfaces/FormInterfaces.d.ts +37 -6
  115. package/dist/Interfaces/InputInterfaces.d.ts +24 -0
  116. package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
  117. package/dist/Interfaces/PreferenceInterfaces.d.ts +51 -0
  118. package/dist/Interfaces/PreferenceInterfaces.js +25 -0
  119. package/dist/Interfaces/TableInterfaces.d.ts +145 -16
  120. package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
  121. package/dist/Interfaces/ThemeInterfaces.js +15 -0
  122. package/dist/Services/ApiProxy.d.ts +36 -1
  123. package/dist/Services/ApiProxy.js +182 -52
  124. package/dist/Services/ApiProxyRules.d.ts +203 -0
  125. package/dist/Services/ApiProxyRules.js +426 -0
  126. package/dist/Services/ExcelApi.d.ts +3 -1
  127. package/dist/Services/ExcelApi.js +1 -1
  128. package/dist/Services/JoinSources.d.ts +48 -0
  129. package/dist/Services/JoinSources.js +74 -0
  130. package/dist/Services/ServerApi.d.ts +13 -2
  131. package/dist/Services/ServerApi.js +8 -3
  132. package/dist/Services/ThemeService.d.ts +18 -2
  133. package/dist/Services/ThemeService.js +80 -16
  134. package/dist/Shared.Index.d.ts +6 -0
  135. package/dist/Shared.Index.js +6 -0
  136. package/package.json +4 -2
  137. package/src/Styles/Nexus.Avatar.css +79 -0
  138. package/src/Styles/Nexus.Base.css +51 -4
  139. package/src/Styles/Nexus.Button.css +27 -0
  140. package/src/Styles/Nexus.Calendar.css +4 -6
  141. package/src/Styles/Nexus.Chat.css +5 -51
  142. package/src/Styles/Nexus.Choice.css +4 -6
  143. package/src/Styles/Nexus.Crud.css +16 -9
  144. package/src/Styles/Nexus.Dropdown.css +12 -61
  145. package/src/Styles/Nexus.Editor.css +4 -6
  146. package/src/Styles/Nexus.Excel.css +34 -15
  147. package/src/Styles/Nexus.Form.css +25 -67
  148. package/src/Styles/Nexus.Index.css +1 -0
  149. package/src/Styles/Nexus.Input.css +794 -637
  150. package/src/Styles/Nexus.Markdown.css +0 -28
  151. package/src/Styles/Nexus.Menu.css +8 -30
  152. package/src/Styles/Nexus.Popup.css +22 -29
  153. package/src/Styles/Nexus.RichText.css +2 -32
  154. package/src/Styles/Nexus.Rows.css +51 -10
  155. package/src/Styles/Nexus.Slider.css +3 -5
  156. package/src/Styles/Nexus.Tab.Buttons.css +20 -7
  157. package/src/Styles/Nexus.Table.css +1387 -1296
  158. package/src/Styles/Nexus.Theme.Picker.css +342 -232
  159. package/src/Styles/Nexus.Time.css +9 -12
  160. package/src/Styles/Nexus.Toast.css +6 -8
  161. package/src/Styles/Nexus.Tokens.css +17 -0
  162. package/src/Styles/Nexus.Tree.css +76 -11
@@ -1,4 +1,4 @@
1
- import { type ThemeMode } from "../Interfaces/ThemeInterfaces.ts";
1
+ import { type AnimationMode, type Density, type ScaleGrid, type ThemeMode } from "../Interfaces/ThemeInterfaces.ts";
2
2
  export interface ThemeState {
3
3
  /** The active theme id. */
4
4
  theme: string;
@@ -6,12 +6,28 @@ export interface ThemeState {
6
6
  mode: ThemeMode;
7
7
  /** The mode actually shown. */
8
8
  resolvedMode: "light" | "dark";
9
+ density: Density;
10
+ /** The chosen animations, which may be `system`. */
11
+ animations: AnimationMode;
12
+ /** Whether things move now: the choice, or the system setting under `system`. */
13
+ motion: boolean;
14
+ /** The scale grid; always `off` outside a development build. */
15
+ grid: ScaleGrid;
9
16
  setTheme: (theme: string) => void;
10
17
  setMode: (mode: ThemeMode) => void;
18
+ setDensity: (density: Density) => void;
19
+ setAnimations: (animations: AnimationMode) => void;
20
+ setGrid: (grid: ScaleGrid) => void;
11
21
  }
12
22
  /** Switches the theme on this page and saves it for the next visit. */
13
23
  export declare function setTheme(theme: string): void;
14
24
  /** Switches light, dark, or system on this page and saves it for the next visit. */
15
25
  export declare function setThemeMode(mode: ThemeMode): void;
16
- /** The current theme and mode, and functions to change them. */
26
+ /** Switches compact or cozy on this page and saves it: every control and row follows at once. */
27
+ export declare function setDensity(density: Density): void;
28
+ /** Turns animations on, off, or back to the system setting on this page, and saves it. */
29
+ export declare function setAnimations(animations: AnimationMode): void;
30
+ /** Shows the scale grid (`px`, `cm`) or hides it (`off`), and saves it. Outside a development build it never shows. */
31
+ export declare function setScaleGrid(grid: ScaleGrid): void;
32
+ /** The current theme, mode, density, animations and scale grid, and functions to change them. */
17
33
  export declare function useTheme(): ThemeState;
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { useSyncExternalStore } from "react";
3
- import { THEME_ID_PATTERN } from "../Helpers/ThemeHelpers.js";
4
- import { DEFAULT_THEME, THEME_ATTRIBUTE, THEME_MODE_ATTRIBUTE, THEME_MODE_STORAGE_KEY, THEME_STORAGE_KEY, } from "../Interfaces/ThemeInterfaces.js";
3
+ import { isDevelopmentBuild, motionFor, REDUCED_MOTION_QUERY, THEME_ID_PATTERN } from "../Helpers/ThemeHelpers.js";
4
+ import { ANIMATIONS_ATTRIBUTE, ANIMATIONS_STORAGE_KEY, DEFAULT_THEME, DENSITY_ATTRIBUTE, DENSITY_STORAGE_KEY, GRID_ATTRIBUTE, GRID_STORAGE_KEY, MOTION_ATTRIBUTE, THEME_ATTRIBUTE, THEME_MODE_ATTRIBUTE, THEME_MODE_STORAGE_KEY, THEME_STORAGE_KEY, } from "../Interfaces/ThemeInterfaces.js";
5
5
  const DARK_QUERY = "(prefers-color-scheme: dark)";
6
6
  // Before hydration there is no document; the real values follow right after.
7
- const SERVER_SNAPSHOT = `${DEFAULT_THEME} system light`;
8
- // Colors switch at once, instead of every button and border running its own transition.
7
+ const SERVER_SNAPSHOT = `${DEFAULT_THEME} system light compact system full off`;
8
+ // Colors and sizes switch at once, instead of every button and border running its own transition.
9
9
  function withoutTransitions(change) {
10
10
  const style = document.createElement("style");
11
11
  style.textContent = "*,*::before,*::after{transition:none!important}";
@@ -22,12 +22,33 @@ function save(key, value) {
22
22
  // Storage blocked (private mode, for example): the choice lasts until the page reloads.
23
23
  }
24
24
  }
25
+ /** Sets an attribute on `<html>`, or takes it away for the choice that means "nothing chosen". */
26
+ function mark(attribute, value, none) {
27
+ const root = document.documentElement;
28
+ if (value === none)
29
+ root.removeAttribute(attribute);
30
+ else
31
+ root.setAttribute(attribute, value);
32
+ }
33
+ const isMode = (value) => value === "light" || value === "dark" || value === "system";
34
+ const isDensity = (value) => value === "compact" || value === "cozy";
35
+ const isAnimations = (value) => value === "on" || value === "off" || value === "system";
36
+ const isGrid = (value) => value === "off" || value === "px" || value === "cm";
25
37
  function applyTheme(theme) {
26
38
  withoutTransitions(() => document.documentElement.setAttribute(THEME_ATTRIBUTE, theme));
27
39
  }
28
40
  function applyMode(mode) {
29
- const root = document.documentElement;
30
- withoutTransitions(() => (mode === "system" ? root.removeAttribute(THEME_MODE_ATTRIBUTE) : root.setAttribute(THEME_MODE_ATTRIBUTE, mode)));
41
+ withoutTransitions(() => mark(THEME_MODE_ATTRIBUTE, mode, "system"));
42
+ }
43
+ function applyDensity(density) {
44
+ withoutTransitions(() => document.documentElement.setAttribute(DENSITY_ATTRIBUTE, density));
45
+ }
46
+ function applyAnimations(animations) {
47
+ mark(ANIMATIONS_ATTRIBUTE, animations, "system");
48
+ document.documentElement.setAttribute(MOTION_ATTRIBUTE, motionFor(animations, window.matchMedia(REDUCED_MOTION_QUERY).matches));
49
+ }
50
+ function applyGrid(grid) {
51
+ mark(GRID_ATTRIBUTE, isDevelopmentBuild() ? grid : "off", "off");
31
52
  }
32
53
  /** Switches the theme on this page and saves it for the next visit. */
33
54
  export function setTheme(theme) {
@@ -41,27 +62,63 @@ export function setThemeMode(mode) {
41
62
  applyMode(mode);
42
63
  save(THEME_MODE_STORAGE_KEY, mode);
43
64
  }
65
+ /** Switches compact or cozy on this page and saves it: every control and row follows at once. */
66
+ export function setDensity(density) {
67
+ if (!isDensity(density))
68
+ return;
69
+ applyDensity(density);
70
+ save(DENSITY_STORAGE_KEY, density);
71
+ }
72
+ /** Turns animations on, off, or back to the system setting on this page, and saves it. */
73
+ export function setAnimations(animations) {
74
+ if (!isAnimations(animations))
75
+ return;
76
+ applyAnimations(animations);
77
+ save(ANIMATIONS_STORAGE_KEY, animations);
78
+ }
79
+ /** Shows the scale grid (`px`, `cm`) or hides it (`off`), and saves it. Outside a development build it never shows. */
80
+ export function setScaleGrid(grid) {
81
+ if (!isGrid(grid))
82
+ return;
83
+ applyGrid(grid);
84
+ save(GRID_STORAGE_KEY, grid);
85
+ }
44
86
  function readSnapshot() {
45
87
  const root = document.documentElement;
46
88
  const theme = root.getAttribute(THEME_ATTRIBUTE) || DEFAULT_THEME;
47
89
  const attribute = root.getAttribute(THEME_MODE_ATTRIBUTE);
48
90
  const mode = attribute === "light" || attribute === "dark" ? attribute : "system";
49
91
  const resolved = mode === "system" ? (window.matchMedia(DARK_QUERY).matches ? "dark" : "light") : mode;
50
- return `${theme} ${mode} ${resolved}`;
92
+ const density = root.getAttribute(DENSITY_ATTRIBUTE) === "cozy" ? "cozy" : "compact";
93
+ const chosen = root.getAttribute(ANIMATIONS_ATTRIBUTE);
94
+ const animations = chosen === "on" || chosen === "off" ? chosen : "system";
95
+ // Read as the stylesheets read it; a page without ThemeScript has no attribute, and its animations run.
96
+ const motion = root.getAttribute(MOTION_ATTRIBUTE) === "reduce" ? "reduce" : "full";
97
+ const grid = root.getAttribute(GRID_ATTRIBUTE);
98
+ return `${theme} ${mode} ${resolved} ${density} ${animations} ${motion} ${grid === "px" || grid === "cm" ? grid : "off"}`;
51
99
  }
52
100
  // The attributes on <html> are the source of truth, so every picker on the page stays in step,
53
101
  // including changes made by the system setting or in another tab.
54
102
  function subscribe(onChange) {
55
103
  const observer = new MutationObserver(onChange);
56
- observer.observe(document.documentElement, { attributes: true, attributeFilter: [THEME_ATTRIBUTE, THEME_MODE_ATTRIBUTE] });
104
+ observer.observe(document.documentElement, {
105
+ attributes: true,
106
+ attributeFilter: [THEME_ATTRIBUTE, THEME_MODE_ATTRIBUTE, DENSITY_ATTRIBUTE, ANIMATIONS_ATTRIBUTE, MOTION_ATTRIBUTE, GRID_ATTRIBUTE],
107
+ });
57
108
  const media = window.matchMedia(DARK_QUERY);
58
109
  media.addEventListener("change", onChange);
59
110
  const onStorage = (event) => {
60
- if (event.key === THEME_STORAGE_KEY && event.newValue && THEME_ID_PATTERN.test(event.newValue))
61
- applyTheme(event.newValue);
62
- if (event.key === THEME_MODE_STORAGE_KEY && (event.newValue === "light" || event.newValue === "dark" || event.newValue === "system")) {
63
- applyMode(event.newValue);
64
- }
111
+ const value = event.newValue;
112
+ if (event.key === THEME_STORAGE_KEY && value && THEME_ID_PATTERN.test(value))
113
+ applyTheme(value);
114
+ else if (event.key === THEME_MODE_STORAGE_KEY && isMode(value))
115
+ applyMode(value);
116
+ else if (event.key === DENSITY_STORAGE_KEY && isDensity(value))
117
+ applyDensity(value);
118
+ else if (event.key === ANIMATIONS_STORAGE_KEY && isAnimations(value))
119
+ applyAnimations(value);
120
+ else if (event.key === GRID_STORAGE_KEY && isGrid(value))
121
+ applyGrid(value);
65
122
  };
66
123
  window.addEventListener("storage", onStorage);
67
124
  return () => {
@@ -70,14 +127,21 @@ function subscribe(onChange) {
70
127
  window.removeEventListener("storage", onStorage);
71
128
  };
72
129
  }
73
- /** The current theme and mode, and functions to change them. */
130
+ /** The current theme, mode, density, animations and scale grid, and functions to change them. */
74
131
  export function useTheme() {
75
- const [theme, mode, resolvedMode] = useSyncExternalStore(subscribe, readSnapshot, () => SERVER_SNAPSHOT).split(" ");
132
+ const [theme, mode, resolvedMode, density, animations, motion, grid] = useSyncExternalStore(subscribe, readSnapshot, () => SERVER_SNAPSHOT).split(" ");
76
133
  return {
77
134
  theme: theme ?? DEFAULT_THEME,
78
- mode: (mode ?? "system"),
135
+ mode: isMode(mode) ? mode : "system",
79
136
  resolvedMode: resolvedMode === "dark" ? "dark" : "light",
137
+ density: density === "cozy" ? "cozy" : "compact",
138
+ animations: isAnimations(animations) ? animations : "system",
139
+ motion: motion !== "reduce",
140
+ grid: isGrid(grid) ? grid : "off",
80
141
  setTheme,
81
142
  setMode: setThemeMode,
143
+ setDensity,
144
+ setAnimations,
145
+ setGrid: setScaleGrid,
82
146
  };
83
147
  }
@@ -14,7 +14,9 @@ export * from "./Helpers/ChatHelpers.ts";
14
14
  export * from "./Helpers/ChatStore.ts";
15
15
  export * from "./Helpers/ChatSummaries.ts";
16
16
  export * from "./Helpers/ClassHelpers.ts";
17
+ export * from "./Helpers/CodeHelpers.ts";
17
18
  export * from "./Helpers/CrudBackend.ts";
19
+ export * from "./Helpers/CrudColumns.ts";
18
20
  export * from "./Helpers/CrudHelpers.ts";
19
21
  export * from "./Helpers/DateHelpers.ts";
20
22
  export * from "./Helpers/DateTimeHelpers.ts";
@@ -30,9 +32,12 @@ export * from "./Helpers/MessageBuilder.ts";
30
32
  export * from "./Helpers/NepaliCalendar.ts";
31
33
  export * from "./Helpers/NumberHelpers.ts";
32
34
  export * from "./Helpers/Permissions.ts";
35
+ export * from "./Helpers/RequestCache.ts";
33
36
  export * from "./Helpers/RichTextHelpers.ts";
34
37
  export * from "./Helpers/RowsStore.ts";
38
+ export * from "./Helpers/TableFilters.ts";
35
39
  export * from "./Helpers/TableHelpers.ts";
40
+ export * from "./Helpers/TableJoins.ts";
36
41
  export * from "./Helpers/TableStore.ts";
37
42
  export * from "./Helpers/ThemeHelpers.ts";
38
43
  export * from "./Helpers/TimeHelpers.ts";
@@ -46,6 +51,7 @@ export * from "./Interfaces/ExcelInterfaces.ts";
46
51
  export * from "./Interfaces/FormInterfaces.ts";
47
52
  export * from "./Interfaces/InputInterfaces.ts";
48
53
  export * from "./Interfaces/MessageInterfaces.ts";
54
+ export * from "./Interfaces/PreferenceInterfaces.ts";
49
55
  export * from "./Interfaces/TableInterfaces.ts";
50
56
  export * from "./Interfaces/ThemeInterfaces.ts";
51
57
  export * from "./Interfaces/TypeInterfaces.ts";
@@ -17,7 +17,9 @@ export * from "./Helpers/ChatHelpers.js";
17
17
  export * from "./Helpers/ChatStore.js";
18
18
  export * from "./Helpers/ChatSummaries.js";
19
19
  export * from "./Helpers/ClassHelpers.js";
20
+ export * from "./Helpers/CodeHelpers.js";
20
21
  export * from "./Helpers/CrudBackend.js";
22
+ export * from "./Helpers/CrudColumns.js";
21
23
  export * from "./Helpers/CrudHelpers.js";
22
24
  export * from "./Helpers/DateHelpers.js";
23
25
  export * from "./Helpers/DateTimeHelpers.js";
@@ -33,9 +35,12 @@ export * from "./Helpers/MessageBuilder.js";
33
35
  export * from "./Helpers/NepaliCalendar.js";
34
36
  export * from "./Helpers/NumberHelpers.js";
35
37
  export * from "./Helpers/Permissions.js";
38
+ export * from "./Helpers/RequestCache.js";
36
39
  export * from "./Helpers/RichTextHelpers.js";
37
40
  export * from "./Helpers/RowsStore.js";
41
+ export * from "./Helpers/TableFilters.js";
38
42
  export * from "./Helpers/TableHelpers.js";
43
+ export * from "./Helpers/TableJoins.js";
39
44
  export * from "./Helpers/TableStore.js";
40
45
  export * from "./Helpers/ThemeHelpers.js";
41
46
  export * from "./Helpers/TimeHelpers.js";
@@ -50,6 +55,7 @@ export * from "./Interfaces/ExcelInterfaces.js";
50
55
  export * from "./Interfaces/FormInterfaces.js";
51
56
  export * from "./Interfaces/InputInterfaces.js";
52
57
  export * from "./Interfaces/MessageInterfaces.js";
58
+ export * from "./Interfaces/PreferenceInterfaces.js";
53
59
  export * from "./Interfaces/TableInterfaces.js";
54
60
  export * from "./Interfaces/ThemeInterfaces.js";
55
61
  export * from "./Interfaces/TypeInterfaces.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-shared",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "The Nexus component library for React and Next.js: params-driven inputs and forms, a data table with a CRUD on top, popups and toasts, an API layer with permissions, Markdown and rich text editors, and 7 themes in light and dark.",
5
5
  "keywords": [
6
6
  "react",
@@ -49,7 +49,9 @@
49
49
  "test": "node scripts/test-all.mjs",
50
50
  "check:themes": "node scripts/check-themes.mjs",
51
51
  "test:api": "node scripts/test-api.mts",
52
+ "test:proxy": "node scripts/test-api-proxy.mts",
52
53
  "test:chat": "node scripts/test-chat.mts",
54
+ "test:codes": "node scripts/test-code-helpers.mts",
53
55
  "test:dates": "node scripts/test-date-helpers.mts",
54
56
  "test:datetimes": "node scripts/test-datetime-helpers.mts",
55
57
  "test:drag": "node scripts/test-drag-helpers.mts",
@@ -67,7 +69,7 @@
67
69
  "test:types": "tsc -p scripts/type-checks/tsconfig.json"
68
70
  },
69
71
  "dependencies": {
70
- "nexus-icons": "^1.1.0"
72
+ "nexus-icons": "^1.2.0"
71
73
  },
72
74
  "peerDependencies": {
73
75
  "react": ">=19",
@@ -0,0 +1,79 @@
1
+ /*
2
+ * A person's avatar, wherever one is drawn: the chat, a dropdown's avatar column, the admin bar.
3
+ */
4
+ @layer nexus.components {
5
+ .nx-avatar {
6
+ position: relative;
7
+ display: inline-grid;
8
+ flex-shrink: 0;
9
+ place-items: center;
10
+ width: var(--nx-avatar-size, 28px);
11
+ height: var(--nx-avatar-size, 28px);
12
+ padding: 0;
13
+ overflow: hidden;
14
+ border: 0;
15
+ border-radius: 50%;
16
+ background: color-mix(in oklab, var(--nx-text) 10%, var(--nx-surface));
17
+ color: var(--nx-text);
18
+ font: inherit;
19
+ font-size: max(8px, calc(var(--nx-avatar-size, 28px) * 0.4));
20
+ font-weight: 600;
21
+ line-height: 1;
22
+ }
23
+
24
+ .nx-avatar--sm {
25
+ --nx-avatar-size: 22px;
26
+ }
27
+
28
+ .nx-avatar--lg {
29
+ --nx-avatar-size: 44px;
30
+ }
31
+
32
+ .nx-avatar[data-tone="primary"] {
33
+ background: var(--nx-primary-soft);
34
+ }
35
+
36
+ .nx-avatar[data-tone="info"] {
37
+ background: var(--nx-info-soft);
38
+ }
39
+
40
+ .nx-avatar[data-tone="success"] {
41
+ background: var(--nx-success-soft);
42
+ }
43
+
44
+ .nx-avatar[data-tone="warning"] {
45
+ background: var(--nx-warning-soft);
46
+ }
47
+
48
+ .nx-avatar img {
49
+ position: absolute;
50
+ inset: 0;
51
+ width: 100%;
52
+ height: 100%;
53
+ object-fit: cover;
54
+ }
55
+
56
+ /* A picture that can be changed: hovering or focusing it lays a camera over it. Opacity alone, no animation. */
57
+ button.nx-avatar {
58
+ cursor: pointer;
59
+ }
60
+
61
+ button.nx-avatar:focus-visible {
62
+ outline: none;
63
+ box-shadow: var(--nx-focus-ring);
64
+ }
65
+
66
+ .nx-avatar__veil {
67
+ position: absolute;
68
+ inset: 0;
69
+ display: grid;
70
+ place-items: center;
71
+ background: rgb(16 24 40 / 0.55);
72
+ color: #fff;
73
+ opacity: 0;
74
+ }
75
+
76
+ button.nx-avatar:is(:hover, :focus-visible) .nx-avatar__veil {
77
+ opacity: 1;
78
+ }
79
+ }
@@ -33,6 +33,20 @@
33
33
  outline-offset: 2px;
34
34
  }
35
35
 
36
+ /*
37
+ * One scroll bar for every scroller, the design lab's: thin and quiet, a thumb of the text colour at 22% on no
38
+ * track. On Windows, Chrome and Edge draw it 10px wide with small arrows and darken the thumb under the pointer
39
+ * themselves, and Firefox's thin bar is 8px; Safari, which has no `scrollbar-color` yet, draws its own thin bar.
40
+ * `scrollbar-width` is not inherited, so it is set on every element, and a gutter kept with `scrollbar-gutter` is as
41
+ * wide as the bar it waits for. There is no `::-webkit-scrollbar` here because Chrome, Edge and Safari ignore it on an
42
+ * element that sets `scrollbar-width`. A component that needs another look sets the two properties on its own
43
+ * scroller. With forced colours the browser puts the system's colours back.
44
+ */
45
+ * {
46
+ scrollbar-width: thin;
47
+ scrollbar-color: color-mix(in oklab, var(--nx-text) 22%, transparent) transparent;
48
+ }
49
+
36
50
  /* Hidden on screen but still read by screen readers and reachable with the keyboard. */
37
51
  .nx-visually-hidden {
38
52
  position: absolute;
@@ -62,9 +76,42 @@
62
76
  }
63
77
  }
64
78
 
65
- @media (prefers-reduced-motion: reduce) {
66
- .nx-spin {
67
- animation-duration: 2.4s;
68
- }
79
+ /* A spinner says something is still happening, so with animations off it turns slowly rather than stopping. */
80
+ [data-nx-motion="reduce"] .nx-spin {
81
+ animation-duration: 2.4s;
82
+ }
83
+
84
+ /*
85
+ * The scale grid, a development tool from the theme panel: a ruled sheet over the whole window, so an edge one pixel
86
+ * out is seen rather than argued about. `px` rules every 4px with a solid line every 48px; `cm` rules millimetres
87
+ * with the centimetre solid. Lines start on the window's edge, so every reading counts from there. It takes no
88
+ * clicks and moves nothing; popups and menus sit in the top layer, above it. ThemeScript sets it in a development
89
+ * build only. The lines mix the text colour, so they read on all seven themes in both modes.
90
+ */
91
+ [data-nx-grid] body::after {
92
+ --nx-grid-minor: color-mix(in oklab, var(--nx-text) 14%, transparent);
93
+ --nx-grid-major: color-mix(in oklab, var(--nx-text) 34%, transparent);
94
+
95
+ position: fixed;
96
+ inset: 0;
97
+ z-index: 2147483000;
98
+ pointer-events: none;
99
+ content: "";
100
+ }
101
+
102
+ [data-nx-grid="px"] body::after {
103
+ background-image:
104
+ repeating-linear-gradient(to right, var(--nx-grid-major) 0 1px, transparent 1px 48px),
105
+ repeating-linear-gradient(to bottom, var(--nx-grid-major) 0 1px, transparent 1px 48px),
106
+ repeating-linear-gradient(to right, var(--nx-grid-minor) 0 1px, transparent 1px 4px),
107
+ repeating-linear-gradient(to bottom, var(--nx-grid-minor) 0 1px, transparent 1px 4px);
108
+ }
109
+
110
+ [data-nx-grid="cm"] body::after {
111
+ background-image:
112
+ repeating-linear-gradient(to right, var(--nx-grid-major) 0 1px, transparent 1px 1cm),
113
+ repeating-linear-gradient(to bottom, var(--nx-grid-major) 0 1px, transparent 1px 1cm),
114
+ repeating-linear-gradient(to right, var(--nx-grid-minor) 0 1px, transparent 1px 1mm),
115
+ repeating-linear-gradient(to bottom, var(--nx-grid-minor) 0 1px, transparent 1px 1mm);
69
116
  }
70
117
  }
@@ -150,4 +150,31 @@
150
150
  display: flex;
151
151
  width: 100%;
152
152
  }
153
+
154
+ /* Something new behind the button (red), or a state that is on (primary): a dot on its corner, ringed in the
155
+ surface so it reads over the icon. */
156
+ .nx-btn:has(> .nx-btn__dot) {
157
+ position: relative;
158
+ }
159
+
160
+ .nx-btn__dot {
161
+ position: absolute;
162
+ top: 6px;
163
+ right: 6px;
164
+ width: 7px;
165
+ height: 7px;
166
+ border: 1.5px solid var(--nx-surface);
167
+ border-radius: 50%;
168
+ background: var(--nx-danger-solid);
169
+ }
170
+
171
+ .nx-btn__dot[data-tone="primary"] {
172
+ background: var(--nx-primary-solid);
173
+ }
174
+
175
+ /* A small icon sits nearer the corner: the dot moves into it, clear of a narrow icon such as ⋮. */
176
+ .nx-btn--icon.nx-btn--sm > .nx-btn__dot {
177
+ top: 3px;
178
+ right: 3px;
179
+ }
153
180
  }
@@ -265,11 +265,9 @@
265
265
  color: var(--nx-field-accent);
266
266
  }
267
267
 
268
- @media (prefers-reduced-motion: reduce) {
269
- .nx-cal__day,
270
- .nx-cal__nav,
271
- .nx-cal__select {
272
- transition: none;
273
- }
268
+ [data-nx-motion="reduce"] .nx-cal__day,
269
+ [data-nx-motion="reduce"] .nx-cal__nav,
270
+ [data-nx-motion="reduce"] .nx-cal__select {
271
+ transition: none;
274
272
  }
275
273
  }
@@ -112,7 +112,7 @@
112
112
 
113
113
  .nx-chat-panel {
114
114
  --nx-chat-height: min(60vh, 520px);
115
- --nx-chat-avatar: 28px;
115
+ --nx-avatar-size: 28px;
116
116
 
117
117
  container-type: inline-size;
118
118
  display: flex;
@@ -787,50 +787,6 @@
787
787
 
788
788
  /* The people */
789
789
 
790
- .nx-chat-avatar {
791
- position: relative;
792
- display: inline-grid;
793
- flex-shrink: 0;
794
- place-items: center;
795
- width: var(--nx-chat-avatar, 28px);
796
- height: var(--nx-chat-avatar, 28px);
797
- overflow: hidden;
798
- border-radius: 50%;
799
- background: color-mix(in oklab, var(--nx-text) 10%, var(--nx-surface));
800
- color: var(--nx-text);
801
- font-size: max(9px, calc(var(--nx-chat-avatar, 28px) * 0.4));
802
- font-weight: 600;
803
- line-height: 1;
804
- }
805
-
806
- .nx-chat-avatar--sm {
807
- --nx-chat-avatar: 22px;
808
- }
809
-
810
- .nx-chat-avatar[data-tone="primary"] {
811
- background: var(--nx-primary-soft);
812
- }
813
-
814
- .nx-chat-avatar[data-tone="info"] {
815
- background: var(--nx-info-soft);
816
- }
817
-
818
- .nx-chat-avatar[data-tone="success"] {
819
- background: var(--nx-success-soft);
820
- }
821
-
822
- .nx-chat-avatar[data-tone="warning"] {
823
- background: var(--nx-warning-soft);
824
- }
825
-
826
- .nx-chat-avatar img {
827
- position: absolute;
828
- inset: 0;
829
- width: 100%;
830
- height: 100%;
831
- object-fit: cover;
832
- }
833
-
834
790
  .nx-chat-people {
835
791
  display: flex;
836
792
  flex-direction: column;
@@ -1215,8 +1171,8 @@
1215
1171
  display: grid;
1216
1172
  flex-shrink: 0;
1217
1173
  place-items: center;
1218
- width: var(--nx-chat-avatar, 28px);
1219
- height: var(--nx-chat-avatar, 28px);
1174
+ width: var(--nx-avatar-size, 28px);
1175
+ height: var(--nx-avatar-size, 28px);
1220
1176
  border-radius: 50%;
1221
1177
  background: var(--nx-surface-2);
1222
1178
  color: var(--nx-text-muted);
@@ -1481,9 +1437,7 @@
1481
1437
  }
1482
1438
  }
1483
1439
 
1484
- @media (prefers-reduced-motion: reduce) {
1485
- .nx-chat-btn {
1486
- transition: none;
1487
- }
1440
+ [data-nx-motion="reduce"] .nx-chat-btn {
1441
+ transition: none;
1488
1442
  }
1489
1443
  }
@@ -335,11 +335,9 @@
335
335
  border-color: var(--nx-danger);
336
336
  }
337
337
 
338
- @media (prefers-reduced-motion: reduce) {
339
- .nx-choice__control,
340
- .nx-choice__control::before,
341
- .nx-choices--cards .nx-choice {
342
- transition: none;
343
- }
338
+ [data-nx-motion="reduce"] .nx-choice__control,
339
+ [data-nx-motion="reduce"] .nx-choice__control::before,
340
+ [data-nx-motion="reduce"] .nx-choices--cards .nx-choice {
341
+ transition: none;
344
342
  }
345
343
  }
@@ -26,15 +26,6 @@
26
26
  gap: var(--nx-space-3);
27
27
  }
28
28
 
29
- .nx-crud-details__note {
30
- display: flex;
31
- align-items: center;
32
- gap: var(--nx-space-2);
33
- margin: 0;
34
- color: var(--nx-text-muted);
35
- font-size: var(--nx-text-sm);
36
- }
37
-
38
29
  .nx-crud-details__list {
39
30
  display: grid;
40
31
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
@@ -59,11 +50,27 @@
59
50
  }
60
51
 
61
52
  .nx-crud-details__value {
53
+ /* A value still to come keeps its line, so the full record lands without moving anything. */
54
+ min-height: 1lh;
62
55
  margin: 0;
63
56
  overflow-wrap: anywhere;
64
57
  font-size: var(--nx-text-md);
65
58
  }
66
59
 
60
+ /* A field only the full record has, when that record could not be loaded. */
61
+ .nx-crud-details__missing {
62
+ color: var(--nx-text-muted);
63
+ }
64
+
65
+ /*
66
+ * The details popup's footer is a form's bar (Nexus.Form.css): its status line on the left - "Loading…" for a slow
67
+ * record, "Not all fields loaded." for one that failed - and Edit and Close on the right. The line takes only the room
68
+ * the buttons leave, not a form's 14rem, so the bar stays one row on a phone and what it says never moves anything.
69
+ */
70
+ .nx-crud-details__foot > .nx-form-status {
71
+ flex-basis: 0;
72
+ }
73
+
67
74
  /* No list the user may see. */
68
75
  .nx-crud--denied {
69
76
  display: flex;