nexus-shared 3.2.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 (187) hide show
  1. package/CHANGELOG.md +1564 -0
  2. package/README.md +1705 -1123
  3. package/dist/Client.Index.d.ts +14 -0
  4. package/dist/Client.Index.js +14 -0
  5. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  6. package/dist/Components/Documents/DocumentFrame.js +83 -0
  7. package/dist/Components/Documents/Menu.d.ts +4 -0
  8. package/dist/Components/Documents/Menu.js +40 -24
  9. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  10. package/dist/Components/Documents/PrintDocument.js +62 -0
  11. package/dist/Components/Documents/Tabs.d.ts +59 -0
  12. package/dist/Components/Documents/Tabs.js +70 -0
  13. package/dist/Components/Forms/ApiForm.js +19 -2
  14. package/dist/Components/Forms/Crud.d.ts +2 -2
  15. package/dist/Components/Forms/Crud.js +576 -243
  16. package/dist/Components/Forms/CrudActions.d.ts +136 -0
  17. package/dist/Components/Forms/CrudActions.js +184 -0
  18. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  19. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  20. package/dist/Components/Forms/CrudParts.d.ts +30 -2
  21. package/dist/Components/Forms/CrudParts.js +55 -5
  22. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  23. package/dist/Components/Forms/CrudRowForms.js +114 -0
  24. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  25. package/dist/Components/Forms/CrudScopes.js +76 -0
  26. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  27. package/dist/Components/Forms/CrudTree.js +363 -0
  28. package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
  29. package/dist/Components/Forms/CrudWorkspace.js +169 -0
  30. package/dist/Components/Forms/ExcelImport.js +3 -3
  31. package/dist/Components/Forms/Form.d.ts +4 -4
  32. package/dist/Components/Forms/Form.js +123 -17
  33. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  34. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  35. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  36. package/dist/Components/Forms/RecordFlows.js +282 -0
  37. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  38. package/dist/Components/Forms/SubmitForm.js +195 -10
  39. package/dist/Components/Inputs/CheckBox.js +1 -1
  40. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  41. package/dist/Components/Inputs/ChoiceField.js +28 -2
  42. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  43. package/dist/Components/Inputs/Dropdown.js +5 -5
  44. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  45. package/dist/Components/Inputs/DropdownParts.js +16 -4
  46. package/dist/Components/Inputs/Field.d.ts +7 -1
  47. package/dist/Components/Inputs/Field.js +2 -2
  48. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  49. package/dist/Components/Inputs/GroupForm.js +10 -11
  50. package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
  51. package/dist/Components/Inputs/InputRenderer.js +37 -32
  52. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  53. package/dist/Components/Inputs/PictureInput.js +70 -0
  54. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  55. package/dist/Components/Inputs/RadioGroup.js +8 -3
  56. package/dist/Components/Inputs/RowsInput.d.ts +1 -1
  57. package/dist/Components/Inputs/RowsInput.js +18 -3
  58. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  59. package/dist/Components/Inputs/TabularForm.js +29 -29
  60. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  61. package/dist/Components/Inputs/TextArea.js +11 -2
  62. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  63. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  64. package/dist/Components/Inputs/TreeView.d.ts +53 -2
  65. package/dist/Components/Inputs/TreeView.js +147 -13
  66. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  67. package/dist/Components/Layouts/MessageBox.js +56 -7
  68. package/dist/Components/Layouts/Popup.d.ts +13 -6
  69. package/dist/Components/Layouts/Popup.js +37 -9
  70. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  71. package/dist/Components/Layouts/PopupDock.js +2 -1
  72. package/dist/Components/Layouts/PopupHost.js +5 -4
  73. package/dist/Components/Viewers/DataTable.js +150 -48
  74. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  75. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  76. package/dist/Components/Viewers/DataTableParts.d.ts +21 -5
  77. package/dist/Components/Viewers/DataTableParts.js +26 -14
  78. package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
  79. package/dist/Components/Viewers/OwnerFiles.js +589 -0
  80. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  81. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  82. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  83. package/dist/Components/Viewers/RecordCards.js +509 -0
  84. package/dist/Helpers/BusyHooks.d.ts +6 -0
  85. package/dist/Helpers/BusyHooks.js +39 -0
  86. package/dist/Helpers/CrudBackend.d.ts +20 -2
  87. package/dist/Helpers/CrudBackend.js +46 -2
  88. package/dist/Helpers/CrudColumns.d.ts +53 -6
  89. package/dist/Helpers/CrudColumns.js +98 -7
  90. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  91. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  92. package/dist/Helpers/CrudHelpers.d.ts +81 -2
  93. package/dist/Helpers/CrudHelpers.js +195 -0
  94. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  95. package/dist/Helpers/CrudNarrowPick.js +157 -0
  96. package/dist/Helpers/CrudRowForms.d.ts +15 -0
  97. package/dist/Helpers/CrudRowForms.js +43 -0
  98. package/dist/Helpers/CrudScopes.d.ts +32 -0
  99. package/dist/Helpers/CrudScopes.js +79 -0
  100. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  101. package/dist/Helpers/DropdownHelpers.js +6 -2
  102. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  103. package/dist/Helpers/ExcelHelpers.js +12 -11
  104. package/dist/Helpers/FileAdapter.d.ts +78 -0
  105. package/dist/Helpers/FileAdapter.js +102 -0
  106. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  107. package/dist/Helpers/FilePrimaries.js +138 -0
  108. package/dist/Helpers/FormConfirm.d.ts +32 -0
  109. package/dist/Helpers/FormConfirm.js +40 -0
  110. package/dist/Helpers/FormStore.d.ts +90 -5
  111. package/dist/Helpers/FormStore.js +318 -22
  112. package/dist/Helpers/InputConditions.d.ts +50 -0
  113. package/dist/Helpers/InputConditions.js +168 -0
  114. package/dist/Helpers/InputMemory.d.ts +106 -0
  115. package/dist/Helpers/InputMemory.js +385 -0
  116. package/dist/Helpers/InputNarrowing.d.ts +18 -0
  117. package/dist/Helpers/InputNarrowing.js +67 -0
  118. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  119. package/dist/Helpers/InputParamsHelpers.js +157 -74
  120. package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
  121. package/dist/Helpers/NexusDriveAdapter.js +207 -0
  122. package/dist/Helpers/NumberHelpers.d.ts +95 -0
  123. package/dist/Helpers/NumberHelpers.js +126 -0
  124. package/dist/Helpers/Permissions.js +2 -1
  125. package/dist/Helpers/QrCode.d.ts +26 -0
  126. package/dist/Helpers/QrCode.js +463 -0
  127. package/dist/Helpers/RecordCards.d.ts +205 -0
  128. package/dist/Helpers/RecordCards.js +378 -0
  129. package/dist/Helpers/RequestCache.d.ts +14 -2
  130. package/dist/Helpers/RequestCache.js +22 -3
  131. package/dist/Helpers/RowsStore.d.ts +37 -2
  132. package/dist/Helpers/RowsStore.js +128 -10
  133. package/dist/Helpers/ServerPreview.d.ts +69 -0
  134. package/dist/Helpers/ServerPreview.js +99 -0
  135. package/dist/Helpers/TableColumns.js +4 -3
  136. package/dist/Helpers/TableExport.d.ts +7 -1
  137. package/dist/Helpers/TableExport.js +9 -3
  138. package/dist/Helpers/TableFilters.d.ts +1 -1
  139. package/dist/Helpers/TableFilters.js +24 -23
  140. package/dist/Helpers/TableHelpers.d.ts +23 -2
  141. package/dist/Helpers/TableHelpers.js +51 -10
  142. package/dist/Helpers/TableJoins.d.ts +13 -0
  143. package/dist/Helpers/TableJoins.js +34 -0
  144. package/dist/Helpers/TableStore.d.ts +21 -1
  145. package/dist/Helpers/TableStore.js +64 -7
  146. package/dist/Helpers/TreeStore.d.ts +81 -0
  147. package/dist/Helpers/TreeStore.js +365 -0
  148. package/dist/Interfaces/CrudInterfaces.d.ts +465 -5
  149. package/dist/Interfaces/FileInterfaces.d.ts +162 -0
  150. package/dist/Interfaces/FileInterfaces.js +4 -0
  151. package/dist/Interfaces/FormInterfaces.d.ts +391 -30
  152. package/dist/Interfaces/FormInterfaces.js +79 -1
  153. package/dist/Interfaces/InputInterfaces.d.ts +15 -3
  154. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  155. package/dist/Interfaces/RecordInterfaces.js +1 -0
  156. package/dist/Interfaces/TableInterfaces.d.ts +74 -1
  157. package/dist/Services/ExcelApi.js +5 -0
  158. package/dist/Services/FileApi.d.ts +13 -0
  159. package/dist/Services/FileApi.js +29 -0
  160. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  161. package/dist/Services/ImageEditorLoader.js +51 -0
  162. package/dist/Services/JoinSources.d.ts +37 -5
  163. package/dist/Services/JoinSources.js +90 -14
  164. package/dist/Services/ModuleImport.d.ts +7 -0
  165. package/dist/Services/ModuleImport.js +22 -0
  166. package/dist/Shared.Index.d.ts +16 -0
  167. package/dist/Shared.Index.js +16 -0
  168. package/package.json +7 -2
  169. package/src/Styles/Nexus.Avatar.css +6 -5
  170. package/src/Styles/Nexus.Button.css +10 -4
  171. package/src/Styles/Nexus.Choice.css +16 -0
  172. package/src/Styles/Nexus.Crud.css +383 -9
  173. package/src/Styles/Nexus.Dropdown.css +8 -0
  174. package/src/Styles/Nexus.Files.css +410 -0
  175. package/src/Styles/Nexus.Form.css +5 -2
  176. package/src/Styles/Nexus.Index.css +4 -0
  177. package/src/Styles/Nexus.Input.css +42 -0
  178. package/src/Styles/Nexus.Menu.css +10 -1
  179. package/src/Styles/Nexus.Popup.css +77 -1
  180. package/src/Styles/Nexus.Print.css +288 -0
  181. package/src/Styles/Nexus.QrCode.css +12 -0
  182. package/src/Styles/Nexus.Record.css +430 -0
  183. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  184. package/src/Styles/Nexus.Table.css +67 -5
  185. package/src/Styles/Nexus.Themes.css +12 -6
  186. package/src/Styles/Nexus.Tokens.css +8 -3
  187. package/src/Styles/Nexus.Tree.css +31 -1
@@ -0,0 +1,410 @@
1
+ /*
2
+ * An owner's pictures and documents (OwnerFiles.tsx, PictureInput.tsx): a picture of any size and shape, the picture
3
+ * input's preview and buttons, the gallery's two panes and the document list. Nothing moves under the pointer: a hovered
4
+ * picture gets a ring, never a new size, so a table's rows and the gallery's grid stay where they are.
5
+ */
6
+ @layer nexus.components {
7
+ /* A picture: its height from --nx-picture-h, its width from the shape (--nx-picture-ratio, width / height). */
8
+ .nx-picture {
9
+ position: relative;
10
+ display: inline-grid;
11
+ flex-shrink: 0;
12
+ place-items: center;
13
+ height: var(--nx-picture-h, 28px);
14
+ aspect-ratio: var(--nx-picture-ratio, 1);
15
+ padding: 0;
16
+ overflow: hidden;
17
+ border: 0;
18
+ border-radius: var(--nx-radius-sm);
19
+ background: color-mix(in oklab, var(--nx-text) 7%, var(--nx-surface));
20
+ color: var(--nx-text-subtle);
21
+ font: inherit;
22
+ vertical-align: middle;
23
+ }
24
+
25
+ .nx-picture img {
26
+ position: absolute;
27
+ inset: 0;
28
+ width: 100%;
29
+ height: 100%;
30
+ object-fit: cover;
31
+ }
32
+
33
+ .nx-picture--empty .nx-icon {
34
+ width: min(50%, 2rem);
35
+ height: auto;
36
+ }
37
+
38
+ .nx-picture--round {
39
+ border-radius: 50%;
40
+ }
41
+
42
+ /* No picture, a name: its initials, on the name's avatar tone (Nexus.Avatar.css), in the text colour. */
43
+ .nx-picture--initials {
44
+ color: var(--nx-text);
45
+ font-size: max(8px, calc(var(--nx-picture-h, 28px) * 0.4));
46
+ font-weight: 600;
47
+ line-height: 1;
48
+ }
49
+
50
+ /* Not read yet: the tint alone, so a row with a picture never flashes the no-picture icon first. */
51
+ .nx-picture--loading .nx-icon {
52
+ visibility: hidden;
53
+ }
54
+
55
+ button.nx-picture {
56
+ cursor: pointer;
57
+ }
58
+
59
+ button.nx-picture:hover {
60
+ box-shadow: 0 0 0 1px var(--nx-border-strong);
61
+ }
62
+
63
+ :is(button.nx-picture, .nx-files__name):focus-visible {
64
+ outline: none;
65
+ box-shadow: var(--nx-focus-ring);
66
+ }
67
+
68
+ /* The picture input: the preview beside its buttons, which wrap under it when the form is narrow. */
69
+ .nx-picture-input {
70
+ min-width: 0;
71
+ }
72
+
73
+ .nx-picture-input__body {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ align-items: center;
77
+ gap: var(--nx-space-3) var(--nx-space-4);
78
+ margin-top: var(--nx-space-1);
79
+ }
80
+
81
+ .nx-picture-input__actions {
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ gap: var(--nx-space-2);
85
+ }
86
+
87
+ /* The gallery and the document list. */
88
+ .nx-files {
89
+ display: grid;
90
+ gap: var(--nx-space-4);
91
+ min-width: 0;
92
+ }
93
+
94
+ .nx-files__bar {
95
+ display: flex;
96
+ flex-wrap: wrap;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ gap: var(--nx-space-2) var(--nx-space-3);
100
+ }
101
+
102
+ .nx-files__note {
103
+ margin: 0;
104
+ color: var(--nx-text-muted);
105
+ font-size: var(--nx-text-sm);
106
+ }
107
+
108
+ .nx-files__note--error {
109
+ color: var(--nx-danger-text);
110
+ }
111
+
112
+ .nx-files__empty {
113
+ display: grid;
114
+ place-items: center;
115
+ gap: var(--nx-space-2);
116
+ padding: var(--nx-space-8) var(--nx-space-4);
117
+ border-radius: var(--nx-radius-lg);
118
+ background: color-mix(in oklab, var(--nx-text) 4%, var(--nx-surface));
119
+ color: var(--nx-text-muted);
120
+ text-align: center;
121
+ }
122
+
123
+ .nx-files__empty > .nx-icon {
124
+ width: 2rem;
125
+ height: 2rem;
126
+ }
127
+
128
+ .nx-files__empty p {
129
+ margin: 0;
130
+ }
131
+
132
+ .nx-files__grid,
133
+ .nx-files__list {
134
+ margin: 0;
135
+ padding: 0;
136
+ list-style: none;
137
+ }
138
+
139
+ /*
140
+ * Pictures in two panes (the 2026-06-20 gallery): the main picture large with its buttons and the count, a hairline,
141
+ * then the grid. The gallery is the size container: under 560px of its own width the main picture sits small beside
142
+ * its buttons, over the grid. In its window (a flush popup's body) it has a height of its own, so the grid scrolls and
143
+ * nothing else does; in a page or a tab it is as tall as its pictures.
144
+ */
145
+ .nx-files--panes {
146
+ container-type: inline-size;
147
+ width: 100%;
148
+ }
149
+
150
+ .nx-files__frame {
151
+ display: grid;
152
+ grid-template-columns: auto minmax(0, 1fr);
153
+ min-height: 0;
154
+ }
155
+
156
+ .nx-files__side {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: var(--nx-space-4);
160
+ box-sizing: content-box;
161
+ width: 15rem;
162
+ min-height: 0;
163
+ padding-inline-end: var(--nx-space-5);
164
+ border-inline-end: 1px solid var(--nx-border);
165
+ }
166
+
167
+ /* The main picture: about 15rem on its long side, a rounded square for every shape (only an avatar is round). */
168
+ .nx-files__cover {
169
+ --nx-picture-h: calc(15rem / max(1, var(--nx-picture-ratio, 1)));
170
+
171
+ align-self: center;
172
+ border-radius: var(--nx-radius-lg);
173
+ }
174
+
175
+ .nx-files__cover.nx-picture--empty .nx-icon {
176
+ width: 3rem;
177
+ }
178
+
179
+ .nx-files__about {
180
+ display: grid;
181
+ align-content: start;
182
+ gap: var(--nx-space-3);
183
+ min-width: 0;
184
+ }
185
+
186
+ .nx-files__side-actions {
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ gap: var(--nx-space-2);
190
+ }
191
+
192
+ .nx-files__side-actions > .nx-btn {
193
+ flex: 1 1 0;
194
+ }
195
+
196
+ .nx-files__pane {
197
+ min-width: 0;
198
+ min-height: 0;
199
+ padding-inline-start: var(--nx-space-5);
200
+ }
201
+
202
+ /*
203
+ * The window: edge to edge, the panes padded inside, a height of its own (the whole window once it fills it). What is
204
+ * not the panes there - the list that could not be read, with Try again - has the window's own padding.
205
+ */
206
+ .nx-popup--flush .nx-popup__body > .nx-files:not(.nx-files--panes) {
207
+ padding: var(--nx-space-5);
208
+ }
209
+
210
+ @media (max-width: 600px) {
211
+ .nx-popup--flush .nx-popup__body > .nx-files:not(.nx-files--panes) {
212
+ padding: var(--nx-space-4);
213
+ }
214
+ }
215
+
216
+ .nx-popup--flush .nx-popup__body > .nx-files--panes {
217
+ height: min(36rem, calc(100dvh - 10rem));
218
+ }
219
+
220
+ .nx-popup:is(.nx-popup--full, .nx-popup--maximized) .nx-popup__body > .nx-files--panes {
221
+ height: 100%;
222
+ }
223
+
224
+ .nx-popup--flush .nx-popup__body > .nx-files--panes .nx-files__frame {
225
+ height: 100%;
226
+ }
227
+
228
+ .nx-popup--flush .nx-popup__body > .nx-files--panes :is(.nx-files__side, .nx-files__pane) {
229
+ padding: var(--nx-space-5);
230
+ overflow-y: auto;
231
+ overscroll-behavior: contain;
232
+ }
233
+
234
+ /* At least two pictures a row; about 11rem each where there is room. */
235
+ .nx-files__grid {
236
+ display: grid;
237
+ grid-template-columns: repeat(auto-fill, minmax(min(11rem, calc(50% - var(--nx-space-3) / 2)), 1fr));
238
+ gap: var(--nx-space-3);
239
+ }
240
+
241
+ .nx-files__tile {
242
+ position: relative;
243
+ min-width: 0;
244
+ }
245
+
246
+ /* A picture in the grid: a rounded square (or the page's shape), the picture covering it. */
247
+ .nx-files__thumb,
248
+ .nx-table__bone.nx-files__bone {
249
+ position: relative;
250
+ display: block;
251
+ width: 100%;
252
+ height: auto;
253
+ aspect-ratio: var(--nx-picture-ratio, 1);
254
+ overflow: hidden;
255
+ border-radius: var(--nx-radius-lg);
256
+ background: color-mix(in oklab, var(--nx-text) 7%, var(--nx-surface));
257
+ }
258
+
259
+ .nx-files__thumb img {
260
+ position: absolute;
261
+ inset: 0;
262
+ width: 100%;
263
+ height: 100%;
264
+ object-fit: cover;
265
+ }
266
+
267
+ .nx-files__tile:hover .nx-files__thumb {
268
+ box-shadow: 0 0 0 1px var(--nx-border-strong);
269
+ }
270
+
271
+ /* The main picture: a ring in the accent, and the Main tag in its corner. */
272
+ .nx-files__tile[data-primary] .nx-files__thumb {
273
+ box-shadow: 0 0 0 2px var(--nx-primary);
274
+ }
275
+
276
+ .nx-files__mark {
277
+ position: absolute;
278
+ top: var(--nx-space-2);
279
+ left: var(--nx-space-2);
280
+ box-shadow: var(--nx-shadow-sm);
281
+ }
282
+
283
+ /*
284
+ * The picture's buttons, inside it and centred, on a popover's panel. Shown on hover and on a keyboard's focus in the
285
+ * picture; where a finger may press (a touch screen, a laptop's too) always, so no button waits behind a hover.
286
+ * Hidden, they stay in the Tab order and to a screen reader, but take no press: a tap meant for the picture never
287
+ * runs Make main or Edit unseen.
288
+ */
289
+ .nx-files__tools {
290
+ position: absolute;
291
+ inset: 0;
292
+ display: flex;
293
+ flex-wrap: wrap;
294
+ justify-content: center;
295
+ gap: 2px;
296
+ width: fit-content;
297
+ max-width: calc(100% - var(--nx-space-2));
298
+ height: fit-content;
299
+ margin: auto;
300
+ padding: 3px;
301
+ border: 1px solid var(--nx-border);
302
+ border-radius: var(--nx-radius);
303
+ background: light-dark(var(--nx-surface), var(--nx-surface-2));
304
+ box-shadow: var(--nx-shadow);
305
+ opacity: 0;
306
+ pointer-events: none;
307
+ transition: opacity var(--nx-duration) ease-out;
308
+ }
309
+
310
+ .nx-files__tile:is(:hover, :has(:focus-visible)) .nx-files__tools {
311
+ opacity: 1;
312
+ pointer-events: auto;
313
+ }
314
+
315
+ @media (hover: none), (any-hover: none), (any-pointer: coarse) {
316
+ .nx-files__tools {
317
+ opacity: 1;
318
+ pointer-events: auto;
319
+ }
320
+ }
321
+
322
+ @container (max-width: 559.98px) {
323
+ .nx-files__frame {
324
+ display: flex;
325
+ flex-direction: column;
326
+ }
327
+
328
+ .nx-files__side {
329
+ flex-direction: row;
330
+ align-items: flex-start;
331
+ flex-shrink: 0;
332
+ gap: var(--nx-space-3);
333
+ box-sizing: border-box;
334
+ width: auto;
335
+ padding: 0 0 var(--nx-space-4);
336
+ border-inline-end: 0;
337
+ border-bottom: 1px solid var(--nx-border);
338
+ }
339
+
340
+ .nx-files__cover {
341
+ --nx-picture-h: calc(7rem / max(1, var(--nx-picture-ratio, 1)));
342
+
343
+ align-self: flex-start;
344
+ }
345
+
346
+ .nx-files__cover.nx-picture--empty .nx-icon {
347
+ width: 2rem;
348
+ }
349
+
350
+ .nx-files__about {
351
+ flex: 1 1 0;
352
+ }
353
+
354
+ .nx-files__pane {
355
+ flex: 1 1 auto;
356
+ padding: var(--nx-space-4) 0 0;
357
+ }
358
+
359
+ .nx-popup--flush .nx-popup__body > .nx-files--panes .nx-files__side {
360
+ flex-shrink: 0;
361
+ padding: var(--nx-space-4);
362
+ overflow: visible;
363
+ }
364
+
365
+ .nx-popup--flush .nx-popup__body > .nx-files--panes .nx-files__pane {
366
+ padding: var(--nx-space-4);
367
+ }
368
+ }
369
+
370
+ [data-nx-motion="reduce"] .nx-files__tools {
371
+ transition: none;
372
+ }
373
+
374
+ /* The document list: a row per file, its buttons at the end. */
375
+ .nx-files__actions {
376
+ display: inline-flex;
377
+ gap: 2px;
378
+ }
379
+
380
+ .nx-files__row {
381
+ display: grid;
382
+ grid-template-columns: auto minmax(0, 1fr) auto;
383
+ align-items: center;
384
+ gap: var(--nx-space-3);
385
+ padding: var(--nx-space-2) 0;
386
+ }
387
+
388
+ .nx-files__row + .nx-files__row {
389
+ border-top: 1px solid var(--nx-border);
390
+ }
391
+
392
+ .nx-files__row-text {
393
+ display: grid;
394
+ min-width: 0;
395
+ }
396
+
397
+ .nx-files__name {
398
+ overflow: hidden;
399
+ border-radius: var(--nx-radius-sm);
400
+ color: var(--nx-text);
401
+ font-weight: 500;
402
+ text-decoration: none;
403
+ text-overflow: ellipsis;
404
+ white-space: nowrap;
405
+ }
406
+
407
+ .nx-files__name:hover {
408
+ text-decoration: underline;
409
+ }
410
+ }
@@ -180,10 +180,13 @@
180
180
  flex: 1;
181
181
  }
182
182
 
183
- /* One line kept even when empty: "Loading…", "Saving…" and "Unsaved changes" come and go, and the bar never jumps. */
183
+ /*
184
+ * One line kept even when empty: "Loading…", "Saving…" and "Unsaved changes" come and go, and the bar never jumps.
185
+ * The room kept beside the buttons is 14rem; a bar whose words are fewer asks less (`--nx-form-status-basis`).
186
+ */
184
187
  .nx-form-status {
185
188
  display: flex;
186
- flex: 1 1 14rem;
189
+ flex: 1 1 var(--nx-form-status-basis, 14rem);
187
190
  align-items: flex-start;
188
191
  gap: 6px;
189
192
  min-width: 0;
@@ -22,10 +22,14 @@
22
22
  @import "./Nexus.Input.css";
23
23
  @import "./Nexus.Editor.css";
24
24
  @import "./Nexus.Excel.css";
25
+ @import "./Nexus.Files.css";
25
26
  @import "./Nexus.Form.css";
26
27
  @import "./Nexus.Markdown.css";
27
28
  @import "./Nexus.Menu.css";
28
29
  @import "./Nexus.Popup.css";
30
+ @import "./Nexus.Print.css";
31
+ @import "./Nexus.QrCode.css";
32
+ @import "./Nexus.Record.css";
29
33
  @import "./Nexus.RichText.css";
30
34
  @import "./Nexus.Rows.css";
31
35
  @import "./Nexus.Slider.css";
@@ -139,11 +139,36 @@
139
139
  font-variant-numeric: tabular-nums;
140
140
  }
141
141
 
142
+ /*
143
+ * A value worked out from other inputs (a form's `compute`): an <output> where an input would be, its text placed as an
144
+ * input's is (in the middle of the box, low in an inbox one) and in the text colour, a long one cut with an ellipsis.
145
+ * Never typed in, so no I-beam; the read-only tint around it comes from the field.
146
+ */
147
+ .nx-field__control--output {
148
+ display: flex;
149
+ align-items: center;
150
+ overflow: hidden;
151
+ white-space: nowrap;
152
+ cursor: default;
153
+ }
154
+
155
+ .nx-field__output-text {
156
+ min-width: 0;
157
+ overflow: hidden;
158
+ text-overflow: ellipsis;
159
+ }
160
+
142
161
  .nx-field__control::placeholder {
143
162
  color: var(--nx-text-subtle);
144
163
  opacity: 1;
145
164
  }
146
165
 
166
+ /* A read-only field's placeholder is all it shows ("None"), so it reads as text: --nx-text-subtle is under 3:1 on the
167
+ read-only box in light themes, muted text at least 4.5:1. */
168
+ .nx-field[data-readonly] .nx-field__control::placeholder {
169
+ color: var(--nx-text-muted);
170
+ }
171
+
147
172
  /* Browser extras that the field replaces with its own buttons. */
148
173
  .nx-field__control::-webkit-search-cancel-button,
149
174
  .nx-field__control::-webkit-search-decoration {
@@ -338,6 +363,12 @@
338
363
  animation: nx-field-nudge 320ms ease-in-out;
339
364
  }
340
365
 
366
+ /* A box pointed at with the same nudge: the one thing to set first, when the cursor is sent to it (a CRUD's Add pressed
367
+ while its narrowing box has nothing picked). */
368
+ .nx-field__box[data-nudge] {
369
+ animation: nx-field-nudge 320ms ease-in-out;
370
+ }
371
+
341
372
  @keyframes nx-field-nudge {
342
373
  20%,
343
374
  60% {
@@ -577,6 +608,16 @@
577
608
  resize: vertical;
578
609
  }
579
610
 
611
+ /* Code (`TextArea code`): the editor's monospace, lines kept as typed, a long one scrolled sideways in the box. */
612
+ .nx-field--multiline .nx-field__control--code {
613
+ overflow-x: auto;
614
+ font-family: var(--nx-font-mono);
615
+ font-size: calc(var(--nx-field-font) - 0.5px);
616
+ white-space: pre;
617
+ overflow-wrap: normal;
618
+ tab-size: 2;
619
+ }
620
+
580
621
  /* Buttons and the read-only lock sit at the top end, centered on the first line (line height 1.4). */
581
622
  .nx-field--multiline .nx-field__actions {
582
623
  align-items: flex-start;
@@ -788,6 +829,7 @@
788
829
  }
789
830
 
790
831
  [data-nx-motion="reduce"] .nx-field__lock[data-nudge],
832
+ [data-nx-motion="reduce"] .nx-field__box[data-nudge],
791
833
  [data-nx-motion="reduce"] .nx-field-notice:popover-open {
792
834
  animation: none;
793
835
  }
@@ -15,6 +15,8 @@
15
15
  min-width: 180px;
16
16
  max-width: min(360px, calc(100% - 16px));
17
17
  outline: none;
18
+ /* A row's menu sits inside the table's actions cell, which does not wrap; an item's second line has to. */
19
+ white-space: normal;
18
20
  }
19
21
 
20
22
  /* The items scroll inside the panel (`.nx-popover__scroll`): a block, so a long menu scrolls instead of squeezing
@@ -60,12 +62,19 @@
60
62
  background: var(--nx-surface-hover);
61
63
  }
62
64
 
65
+ /*
66
+ * A switched-off item fades its icon, check, label and shortcut, not its second line: that line is why it is off
67
+ * (a locked record's reason, what an action waits for), and at half strength it measured 2.0 to 3.1:1 in every theme.
68
+ */
63
69
  .nx-menu__item[aria-disabled="true"] {
64
70
  background: none;
65
- opacity: 0.5;
66
71
  cursor: not-allowed;
67
72
  }
68
73
 
74
+ .nx-menu__item[aria-disabled="true"] :is(.nx-menu__icon, .nx-menu__check, .nx-menu__label, .nx-menu__shortcut) {
75
+ opacity: 0.5;
76
+ }
77
+
69
78
  /* The icon column: the same width on every item, empty on items without an icon. */
70
79
  .nx-menu__icon {
71
80
  display: grid;
@@ -351,12 +351,18 @@
351
351
  }
352
352
 
353
353
  /* Body: scrolls when the content is taller than the window allows. */
354
+ /*
355
+ * Focus scrolls a field's control to the body's edge: `scroll-padding` keeps room past it for what the field draws
356
+ * outside, an outlined field's raised label above (0.6em of the xs text, and the 2px outline) and the box's border and
357
+ * 3px focus ring below. A field first in the body has its padding for that.
358
+ */
354
359
  .nx-popup__body {
355
360
  flex: 1 1 auto;
356
361
  min-height: 0;
357
362
  padding: var(--nx-space-4) var(--nx-space-5) var(--nx-space-5);
358
363
  overflow: auto;
359
364
  overscroll-behavior: contain;
365
+ scroll-padding: calc(var(--nx-text-xs) * 0.6 + 2px) 4px 4px;
360
366
  }
361
367
 
362
368
  .nx-popup__head + .nx-popup__body:empty {
@@ -403,6 +409,15 @@
403
409
  line-height: 1.5;
404
410
  }
405
411
 
412
+ /* What a question is about, in full (`details`): read as content, so in the normal text colour, under the message. */
413
+ .nx-message__details {
414
+ color: var(--nx-text);
415
+ }
416
+
417
+ .nx-message__text + .nx-message__details {
418
+ margin-top: var(--nx-space-4);
419
+ }
420
+
406
421
  .nx-message__form {
407
422
  display: grid;
408
423
  gap: var(--nx-space-4);
@@ -628,8 +643,15 @@
628
643
  vertical-align: 0;
629
644
  }
630
645
 
631
- .nx-dock__title {
646
+ /* The title, and under it what the window is about when it says (its description): the record, the place. */
647
+ .nx-dock__text {
648
+ display: flex;
632
649
  flex: 1 1 auto;
650
+ flex-direction: column;
651
+ min-width: 0;
652
+ }
653
+
654
+ .nx-dock__title {
633
655
  min-width: 0;
634
656
  overflow: hidden;
635
657
  font-size: var(--nx-text-md);
@@ -639,6 +661,21 @@
639
661
  white-space: nowrap;
640
662
  }
641
663
 
664
+ .nx-dock__description {
665
+ min-width: 0;
666
+ overflow: hidden;
667
+ color: var(--nx-text-muted);
668
+ font-size: var(--nx-text-xs);
669
+ line-height: 1.3;
670
+ text-overflow: ellipsis;
671
+ white-space: nowrap;
672
+ }
673
+
674
+ /* Muted text on the hover tint moves toward the text color, so it keeps 4.5:1 in every theme. */
675
+ .nx-dock__window:hover .nx-dock__description {
676
+ color: color-mix(in oklab, var(--nx-text-muted), var(--nx-text) 35%);
677
+ }
678
+
642
679
  .nx-dock__restore {
643
680
  flex-shrink: 0;
644
681
  width: 18px;
@@ -711,6 +748,45 @@
711
748
  .nx-popup__foot {
712
749
  padding: var(--nx-space-3) var(--nx-space-4);
713
750
  }
751
+
752
+ /*
753
+ * A phone has no room for the description after the title: it takes the lines under the title it needs, in full,
754
+ * and the bar grows with it. The icon and the window buttons keep the bar's first line; the title stays one line.
755
+ */
756
+ .nx-popup--window .nx-popup__head {
757
+ align-items: flex-start;
758
+ height: auto;
759
+ min-height: var(--nx-popup-bar);
760
+ }
761
+
762
+ .nx-popup--window .nx-popup__icon {
763
+ align-self: flex-start;
764
+ height: var(--nx-popup-bar);
765
+ }
766
+
767
+ .nx-popup--window .nx-popup__heading {
768
+ flex-wrap: wrap;
769
+ row-gap: 0;
770
+ }
771
+
772
+ .nx-popup--window .nx-popup__title {
773
+ flex-basis: 100%;
774
+ }
775
+
776
+ .nx-popup--window .nx-popup__description:not(:empty) {
777
+ flex-basis: 100%;
778
+ margin-top: calc(var(--nx-space-1) * -1);
779
+ padding-bottom: var(--nx-space-2);
780
+ overflow: visible;
781
+ line-height: 1.45;
782
+ white-space: normal;
783
+ overflow-wrap: anywhere;
784
+ }
785
+
786
+ .nx-popup--window .nx-popup__controls {
787
+ align-self: flex-start;
788
+ height: var(--nx-popup-bar);
789
+ }
714
790
  }
715
791
 
716
792
  [data-nx-motion="reduce"] .nx-popup[open],