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
@@ -19,6 +19,28 @@
19
19
  flex: 1 1 auto;
20
20
  }
21
21
 
22
+ /*
23
+ * A list narrowed to one record picked in another (`narrow`): whose records these are, in the table's caption, kept
24
+ * while nothing is picked so the rows never move. Positioned, so the words kept for screen readers stay in it.
25
+ */
26
+ .nx-crud__scope {
27
+ position: relative;
28
+ margin: 0;
29
+ }
30
+
31
+ .nx-crud__scope-name {
32
+ color: var(--nx-text);
33
+ font-weight: 600;
34
+ }
35
+
36
+ /* A tree of records (`CrudTree`): the picked record's buttons, beside its name in the page's panel, wrapping as one group. */
37
+ .nx-crud-tree__actions {
38
+ display: flex;
39
+ flex-wrap: wrap;
40
+ align-items: center;
41
+ gap: var(--nx-space-2);
42
+ }
43
+
22
44
  /* Details: label over value, two to a line where there is room. */
23
45
  .nx-crud-details {
24
46
  display: flex;
@@ -44,6 +66,31 @@
44
66
  grid-column: 1 / -1;
45
67
  }
46
68
 
69
+ /*
70
+ * Details in sections (`column.group`): each run of fields under its heading in the small muted heading style, the
71
+ * heading nearer its fields than the section before it.
72
+ */
73
+ .nx-crud-details__section {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: var(--nx-space-2);
77
+ min-width: 0;
78
+ }
79
+
80
+ .nx-crud-details__section + .nx-crud-details__section {
81
+ margin-top: var(--nx-space-3);
82
+ }
83
+
84
+ .nx-crud-details__heading {
85
+ margin: 0;
86
+ color: var(--nx-text-muted);
87
+ font-size: var(--nx-text-xs);
88
+ font-weight: 600;
89
+ line-height: 1.3;
90
+ letter-spacing: 0.04em;
91
+ text-transform: uppercase;
92
+ }
93
+
47
94
  .nx-crud-details__label {
48
95
  color: var(--nx-text-muted);
49
96
  font-size: var(--nx-text-sm);
@@ -124,18 +171,27 @@
124
171
  flex-direction: column;
125
172
  }
126
173
 
174
+ /*
175
+ * Under the fields, at every width (`details.asidePlace: "below"`, a record's workspace by default): the fields take
176
+ * the whole width, the panel the whole width under them, as tall as what it holds; what scrolls is the box around
177
+ * both (the workspace's panel, the popup's body), never a column of its own.
178
+ */
179
+ .nx-crud-aside--below > .nx-crud-aside__side {
180
+ max-height: none;
181
+ }
182
+
127
183
  /*
128
184
  * Side by side, where there is room for both. The popup's body stops scrolling and each column takes its own, so a
129
185
  * long form never pushes the panel off the screen. The body is a flex row here, which is what gives its one child a
130
186
  * height to work from: no percentage height anywhere, so it holds at every popup size.
131
187
  */
132
188
  @media (min-width: 1000px) {
133
- .nx-popup__body:has(> .nx-crud-aside) {
189
+ .nx-popup__body:has(> .nx-crud-aside:not(.nx-crud-aside--below)) {
134
190
  display: flex;
135
191
  overflow: hidden;
136
192
  }
137
193
 
138
- .nx-crud-aside {
194
+ .nx-crud-aside:not(.nx-crud-aside--below) {
139
195
  flex: 1 1 auto;
140
196
  min-width: 0;
141
197
  /* The panel takes a share of the width, not half of it: a form has columns to fit, and code lines are short. */
@@ -146,23 +202,341 @@
146
202
  * A details popup's panel (`details.aside`) is what the popup is for, and the details beside it are a short list
147
203
  * of fields, so the shares turn round: the fields take a column of their own width and the panel the rest.
148
204
  */
149
- .nx-crud-aside.nx-crud-aside--details {
205
+ .nx-crud-aside--details:not(.nx-crud-aside--below) {
150
206
  grid-template-columns: minmax(0, var(--nx-crud-details-width, clamp(16rem, 30%, 26rem))) minmax(0, 1fr);
151
207
  }
152
208
 
153
- .nx-crud-aside__main {
209
+ /*
210
+ * Each column scrolls on its own, and a scroll box clips at its padding edge: a field first in it would lose what it
211
+ * draws past its box. Room inside the edges, paid back by a negative margin so nothing moves: above, an outlined
212
+ * field's raised label (lifted 0.6em of the xs text over the top border) and its 2px focus outline; on every side,
213
+ * the 3px focus ring (`--nx-focus-ring`). Focus scrolls a field's control to an edge, so `scroll-padding` keeps the
214
+ * same room past the control: the label above, the box's 1px border and the ring below (the popup's body too).
215
+ */
216
+ .nx-crud-aside:not(.nx-crud-aside--below) > :is(.nx-crud-aside__main, .nx-crud-aside__side) {
154
217
  overflow: auto;
155
218
  overscroll-behavior: contain;
156
- /* Focus rings reach past an input's edge; without this the scroll box cuts them off. */
157
- padding: 2px;
158
- margin: -2px;
219
+ padding: calc(var(--nx-text-xs) * 0.6 + 2px) 3px 3px;
220
+ margin: calc(var(--nx-text-xs) * -0.6 - 2px) -3px -3px;
221
+ scroll-padding: calc(var(--nx-text-xs) * 0.6 + 2px) 4px 4px;
159
222
  }
160
223
 
161
224
  /* A panel taller than the popup scrolls here, whatever it holds; one that fills the height scrolls inside itself. */
162
225
  .nx-crud-aside__side {
163
226
  max-height: none;
164
- overflow: auto;
165
- overscroll-behavior: contain;
227
+ }
228
+ }
229
+
230
+ /*
231
+ * A record's workspace in its details popup (`details.tabs`, `details.profile`): who the record is and its tabs in a
232
+ * column on the left, the chosen tab on the right. The popup's body stops scrolling and only the tab scrolls, inside
233
+ * itself, so the profile, the tabs and the footer stay where they are and a switch never changes the popup's size. The
234
+ * workspace is the container its column and the tabs' `auto` design follow: below 720px (a phone) the profile is one
235
+ * row and the tabs one strip, above the tab.
236
+ */
237
+ .nx-popup__body:has(> .nx-crud-workspace) {
238
+ display: flex;
239
+ overflow: hidden;
240
+ }
241
+
242
+ .nx-crud-workspace {
243
+ container-type: inline-size;
244
+ flex: 1 1 auto;
245
+ min-width: 0;
246
+ /* A popup of a set width (`lg`, `xl`) gets a height of its own, so no tab changes it; `full` gives its own. */
247
+ height: min(40rem, calc(100dvh - 10rem));
248
+ }
249
+
250
+ .nx-popup:is(.nx-popup--full, .nx-popup--maximized) .nx-crud-workspace {
251
+ height: auto;
252
+ }
253
+
254
+ .nx-crud-workspace__frame {
255
+ --nx-crud-profile-picture: 10rem;
256
+
257
+ display: grid;
258
+ grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
259
+ height: 100%;
260
+ }
261
+
262
+ /* Neither a profile nor more than one tab for this person: the tab alone, the width of the popup. */
263
+ .nx-crud-workspace__frame--alone {
264
+ grid-template-columns: minmax(0, 1fr);
265
+ }
266
+
267
+ .nx-crud-workspace__side {
268
+ display: flex;
269
+ flex-direction: column;
270
+ min-height: 0;
271
+ border-inline-end: 1px solid var(--nx-border);
272
+ background: color-mix(in oklab, var(--nx-text) 3%, transparent);
273
+ }
274
+
275
+ .nx-crud-workspace__profile {
276
+ flex-shrink: 0;
277
+ padding: var(--nx-space-5) var(--nx-space-4);
278
+ }
279
+
280
+ .nx-crud-workspace__profile + .nx-crud-workspace__nav {
281
+ border-top: 1px solid var(--nx-border);
282
+ }
283
+
284
+ .nx-crud-workspace__nav {
285
+ flex: 1 1 auto;
286
+ min-height: 0;
287
+ padding: var(--nx-space-3);
288
+ overflow-y: auto;
289
+ overscroll-behavior: contain;
290
+ }
291
+
292
+ /*
293
+ * Its padding is the room for a field first in it; `scroll-padding` the room round one the focus scrolls to (as the
294
+ * aside's). The side padding is named, so a tab's footer can reach across it.
295
+ */
296
+ .nx-crud-workspace__panel {
297
+ --nx-crud-panel-pad-x: var(--nx-space-5);
298
+
299
+ min-width: 0;
300
+ min-height: 0;
301
+ padding: var(--nx-space-4) var(--nx-crud-panel-pad-x) var(--nx-space-5);
302
+ overflow: auto;
303
+ overscroll-behavior: contain;
304
+ scroll-padding: calc(var(--nx-text-xs) * 0.6 + 2px) 4px 4px;
305
+ }
306
+
307
+ /*
308
+ * A tab's footer (`CrudTabFooter`): one bar, its words at the start and its buttons at the end, with the popup
309
+ * footer's room and hairline. On a phone the words and the buttons wrap as whole groups. Where it is not in a
310
+ * workspace's panel, this is all it is: a plain bar where it stands.
311
+ */
312
+ .nx-crud-tab-footer {
313
+ display: flex;
314
+ flex-wrap: wrap;
315
+ align-items: center;
316
+ justify-content: flex-end;
317
+ gap: var(--nx-space-2);
318
+ padding-block: var(--nx-space-3);
319
+ border-top: 1px solid var(--nx-border);
320
+ }
321
+
322
+ .nx-crud-tab-footer > p {
323
+ flex: 1 1 auto;
324
+ min-width: 0;
325
+ margin: 0;
326
+ }
327
+
328
+ /* A form's bar fills the rest, so its status line sits beside its buttons (as in the popup's footer). */
329
+ .nx-crud-tab-footer > .nx-form-buttons {
330
+ flex: 1 1 auto;
331
+ }
332
+
333
+ /*
334
+ * In a workspace's panel it stays in view: it sticks to the bottom of the panel while the tab scrolls and sits in its
335
+ * place at the end, across the panel's whole width (past its side padding) on the panel's own surface, never a box of
336
+ * its own (no shadow, no frame). The panel keeps no padding under it, so it meets the panel's foot at the end as
337
+ * while it sticks. What the focus scrolls into view (a field, a caret) clears it: the panel keeps the footer's height
338
+ * as `CrudTabFooter` measures it (`scroll-padding`). Above what a tab's content raises (a field's label, a table's
339
+ * sticky header: 5 at most).
340
+ */
341
+ .nx-crud-workspace__panel:has(.nx-crud-tab-footer) {
342
+ padding-bottom: 0;
343
+ scroll-padding-bottom: calc(var(--nx-crud-tab-footer-size, 0px) + 4px);
344
+ }
345
+
346
+ /* While the focus is in the footer, which is always in view, that room goes: its Save never scrolls the tab. */
347
+ .nx-crud-workspace__panel:has(.nx-crud-tab-footer:focus-within) {
348
+ scroll-padding-bottom: 4px;
349
+ }
350
+
351
+ .nx-crud-workspace__panel .nx-crud-tab-footer {
352
+ position: sticky;
353
+ bottom: 0;
354
+ z-index: 6;
355
+ margin-inline: calc(-1 * var(--nx-crud-panel-pad-x));
356
+ padding-inline: var(--nx-crud-panel-pad-x);
357
+ background: var(--nx-surface);
358
+ }
359
+
360
+ /* The profile: the picture over the name, the line under it and the tags, in the middle of the column. */
361
+ .nx-crud-profile {
362
+ display: flex;
363
+ flex-direction: column;
364
+ align-items: center;
365
+ gap: var(--nx-space-3);
366
+ min-width: 0;
367
+ text-align: center;
368
+ }
369
+
370
+ .nx-crud-profile__picture {
371
+ display: flex;
372
+ flex-shrink: 0;
373
+ }
374
+
375
+ .nx-crud-profile__text {
376
+ display: grid;
377
+ justify-items: center;
378
+ gap: 2px;
379
+ min-width: 0;
380
+ max-width: 100%;
381
+ }
382
+
383
+ .nx-crud-profile__title {
384
+ margin: 0;
385
+ font-size: var(--nx-text-lg);
386
+ font-weight: 600;
387
+ line-height: 1.35;
388
+ overflow-wrap: anywhere;
389
+ }
390
+
391
+ .nx-crud-profile__subtitle {
392
+ margin: 0;
393
+ color: var(--nx-text-muted);
394
+ font-size: var(--nx-text-sm);
395
+ overflow-wrap: anywhere;
396
+ }
397
+
398
+ .nx-crud-profile__tags {
399
+ display: flex;
400
+ flex-wrap: wrap;
401
+ justify-content: center;
402
+ gap: 4px;
403
+ margin: 4px 0 0;
404
+ }
405
+
406
+ /*
407
+ * A few of the record's fields under the tags (`profile.fields`): a small grid in the middle of the column, a label
408
+ * (muted) and its value (the text colour) a line, each pair on the grid's two tracks so the values line up. A long
409
+ * value wraps inside the column, never past it, and a column's tag follows its value, under it when the line is full.
410
+ */
411
+ .nx-crud-profile__fields {
412
+ display: grid;
413
+ grid-template-columns: auto minmax(0, 1fr);
414
+ gap: var(--nx-space-1) var(--nx-space-3);
415
+ max-width: 100%;
416
+ margin: var(--nx-space-2) 0 0;
417
+ font-size: var(--nx-text-sm);
418
+ line-height: 1.5;
419
+ text-align: start;
420
+ }
421
+
422
+ .nx-crud-profile__field {
423
+ display: grid;
424
+ grid-column: 1 / -1;
425
+ grid-template-columns: subgrid;
426
+ align-items: baseline;
427
+ min-width: 0;
428
+ }
429
+
430
+ .nx-crud-profile__label {
431
+ color: var(--nx-text-muted);
432
+ overflow-wrap: break-word;
433
+ }
434
+
435
+ .nx-crud-profile__value {
436
+ display: flex;
437
+ flex-wrap: wrap;
438
+ align-items: baseline;
439
+ gap: 2px var(--nx-space-2);
440
+ min-width: 0;
441
+ margin: 0;
442
+ color: var(--nx-text);
443
+ overflow-wrap: anywhere;
444
+ }
445
+
446
+ /* The gap places the tag: a word's space after the value, or the start of the next line. */
447
+ .nx-crud-profile__value > .nx-table__tag--after {
448
+ margin-inline-start: 0;
449
+ }
450
+
451
+ /* A field only the full record has, held while it is read: a short value's width on the value's baseline. */
452
+ .nx-table__bone.nx-crud-profile__bone {
453
+ width: 6rem;
454
+ }
455
+
456
+ @container (max-width: 719.98px) {
457
+ .nx-crud-workspace__frame {
458
+ --nx-crud-profile-picture: 3rem;
459
+
460
+ display: flex;
461
+ flex-direction: column;
462
+ }
463
+
464
+ .nx-crud-workspace__side {
465
+ flex-shrink: 0;
466
+ border-inline-end: 0;
467
+ border-bottom: 1px solid var(--nx-border);
468
+ }
469
+
470
+ .nx-crud-workspace__profile {
471
+ padding: var(--nx-space-3) var(--nx-space-4);
472
+ }
473
+
474
+ .nx-crud-workspace__profile + .nx-crud-workspace__nav {
475
+ border-top: 0;
476
+ }
477
+
478
+ .nx-crud-profile {
479
+ flex-direction: row;
480
+ text-align: start;
481
+ }
482
+
483
+ .nx-crud-profile__text {
484
+ justify-items: start;
485
+ }
486
+
487
+ .nx-crud-profile__tags {
488
+ justify-content: flex-start;
489
+ }
490
+
491
+ /*
492
+ * The fields follow the tags as one wrapped line of "label value" pairs: beside the tags when all of them fit there,
493
+ * else from the next line, so they take the lines their words need, not a line a field.
494
+ */
495
+ .nx-crud-profile__text:has(> .nx-crud-profile__fields) {
496
+ display: flex;
497
+ flex-wrap: wrap;
498
+ align-items: baseline;
499
+ gap: 2px var(--nx-space-3);
500
+ }
501
+
502
+ .nx-crud-profile__text:has(> .nx-crud-profile__fields) > :is(.nx-crud-profile__title, .nx-crud-profile__subtitle) {
503
+ flex-basis: 100%;
504
+ }
505
+
506
+ .nx-crud-profile__fields {
507
+ display: flex;
508
+ flex-wrap: wrap;
509
+ gap: 2px var(--nx-space-3);
510
+ min-width: 0;
511
+ margin-top: 4px;
512
+ }
513
+
514
+ /* A pair longer than the line wraps as words do, its label kept with the start of its value. */
515
+ .nx-crud-profile__field {
516
+ display: block;
517
+ min-width: 0;
518
+ }
519
+
520
+ .nx-crud-profile__label,
521
+ .nx-crud-profile__value {
522
+ display: inline;
523
+ }
524
+
525
+ .nx-crud-profile__value > .nx-table__tag--after {
526
+ margin-inline-start: var(--nx-space-2);
527
+ }
528
+
529
+ .nx-crud-workspace__nav {
530
+ flex: 0 0 auto;
531
+ padding: 0 var(--nx-space-2) var(--nx-space-1);
532
+ overflow: visible;
533
+ }
534
+
535
+ .nx-crud-workspace__panel {
536
+ --nx-crud-panel-pad-x: var(--nx-space-4);
537
+
538
+ flex: 1 1 auto;
539
+ padding: var(--nx-space-3) var(--nx-crud-panel-pad-x) var(--nx-space-4);
166
540
  }
167
541
  }
168
542
  }
@@ -385,7 +385,10 @@
385
385
  user-select: none;
386
386
  }
387
387
 
388
+ /* The list's gutters, not the row's own 8px gap: a subgrid with a gap of its own sets its cells a pixel past each side
389
+ of their headings, so a figure at the end of its column ended a pixel past its heading. */
388
390
  .nx-dropdown-list--columns > .nx-dropdown-option {
391
+ column-gap: normal;
389
392
  padding-inline: 0;
390
393
  }
391
394
 
@@ -550,6 +553,11 @@
550
553
  letter-spacing: 0.03em;
551
554
  }
552
555
 
556
+ /* A price beside a name: figures in even digits, so a column of them lines up at the end. */
557
+ .nx-dropdown-cell--money {
558
+ font-variant-numeric: tabular-nums;
559
+ }
560
+
553
561
  .nx-dropdown-cell--media {
554
562
  display: grid;
555
563
  place-items: center;