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.
- package/CHANGELOG.md +1564 -0
- package/README.md +1705 -1123
- package/dist/Client.Index.d.ts +14 -0
- package/dist/Client.Index.js +14 -0
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Forms/ApiForm.js +19 -2
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +576 -243
- package/dist/Components/Forms/CrudActions.d.ts +136 -0
- package/dist/Components/Forms/CrudActions.js +184 -0
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +30 -2
- package/dist/Components/Forms/CrudParts.js +55 -5
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +114 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
- package/dist/Components/Forms/CrudWorkspace.js +169 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +4 -4
- package/dist/Components/Forms/Form.js +123 -17
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +195 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +5 -5
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +10 -11
- package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +70 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +1 -1
- package/dist/Components/Inputs/RowsInput.js +18 -3
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +29 -29
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +53 -2
- package/dist/Components/Inputs/TreeView.js +147 -13
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +13 -6
- package/dist/Components/Layouts/Popup.js +37 -9
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Viewers/DataTable.js +150 -48
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +21 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
- package/dist/Components/Viewers/OwnerFiles.js +589 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +53 -6
- package/dist/Helpers/CrudColumns.js +98 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +81 -2
- package/dist/Helpers/CrudHelpers.js +195 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudRowForms.d.ts +15 -0
- package/dist/Helpers/CrudRowForms.js +43 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +78 -0
- package/dist/Helpers/FileAdapter.js +102 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +90 -5
- package/dist/Helpers/FormStore.js +318 -22
- package/dist/Helpers/InputConditions.d.ts +50 -0
- package/dist/Helpers/InputConditions.js +168 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +385 -0
- package/dist/Helpers/InputNarrowing.d.ts +18 -0
- package/dist/Helpers/InputNarrowing.js +67 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
- package/dist/Helpers/NexusDriveAdapter.js +207 -0
- package/dist/Helpers/NumberHelpers.d.ts +95 -0
- package/dist/Helpers/NumberHelpers.js +126 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +37 -2
- package/dist/Helpers/RowsStore.js +128 -10
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +23 -2
- package/dist/Helpers/TableHelpers.js +51 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +21 -1
- package/dist/Helpers/TableStore.js +64 -7
- package/dist/Helpers/TreeStore.d.ts +81 -0
- package/dist/Helpers/TreeStore.js +365 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +465 -5
- package/dist/Interfaces/FileInterfaces.d.ts +162 -0
- package/dist/Interfaces/FileInterfaces.js +4 -0
- package/dist/Interfaces/FormInterfaces.d.ts +391 -30
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +15 -3
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +74 -1
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +37 -5
- package/dist/Services/JoinSources.js +90 -14
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +16 -0
- package/dist/Shared.Index.js +16 -0
- package/package.json +7 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +10 -4
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +383 -9
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Files.css +410 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +4 -0
- package/src/Styles/Nexus.Input.css +42 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +77 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +67 -5
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +31 -1
|
@@ -6,8 +6,18 @@ import type { TableJoinSource, TableJoinedRecord } from "../Interfaces/TableInte
|
|
|
6
6
|
* A join's source as the table calls it. An endpoint becomes one that POSTs the keys to it as an array and reads the
|
|
7
7
|
* records it answers - a Nexus controller's `findListing` - made once per endpoint, so every table and picker naming it
|
|
8
8
|
* shares one source and so one cache. A source of the page's own is used as it is.
|
|
9
|
+
*
|
|
10
|
+
* `key` is the field the records answered are filed under, `id` unless told: one whose values are another field (a
|
|
11
|
+
* person's account, which their company row carries beside its own key) is a source of its own, kept apart.
|
|
12
|
+
*/
|
|
13
|
+
export declare function joinSource(source: ApiEndpoint | TableJoinSource, backend?: CrudBackend, key?: string): TableJoinSource;
|
|
14
|
+
/**
|
|
15
|
+
* The record a `findListing` answered for a key, or one the page remembered there (`TableJoins.remember`), at once and
|
|
16
|
+
* with no call, while it is fresh: `undefined` while it is not in, and once it was marked old (a save on that controller,
|
|
17
|
+
* a Reload), so a picker asks again and shows the record as it is now. A picker over that controller's records names a
|
|
18
|
+
* value with it (`peekOption`), and so does a CRUD's save, which takes an old one too (`aged`) over no name at all.
|
|
9
19
|
*/
|
|
10
|
-
export declare function
|
|
20
|
+
export declare function peekFound(findListing: ApiEndpoint, key: string, aged?: boolean): TableJoinedRecord | undefined;
|
|
11
21
|
/** The controller a picker offers records from: any with an `activeListing` and a `findListing` (`MODULES.controller(...)`). */
|
|
12
22
|
export interface PickerController {
|
|
13
23
|
activeListing: ApiEndpoint;
|
|
@@ -19,14 +29,33 @@ export interface CrudPickerOptions {
|
|
|
19
29
|
labelKey: string;
|
|
20
30
|
/** A field shown under it, such as the parent's name: `"regionName"` under a sub-region. */
|
|
21
31
|
descriptionKey?: string;
|
|
22
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The field that holds the value. Default `id`: the records' own key, which is what the controller's `findListing`
|
|
34
|
+
* takes. Another field - a person's account (`userId`), which their company row carries beside its own key - needs
|
|
35
|
+
* `find` for the values the kept list does not offer.
|
|
36
|
+
*/
|
|
23
37
|
valueKey?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Where a value the kept list does not offer is looked up when `valueKey` is not `id`: an endpoint that takes such
|
|
40
|
+
* values, POSTed as an array as `findListing` takes keys, and answers the records, each read by its `valueKey` field;
|
|
41
|
+
* or a source of the page's own. Its answers are kept apart from `findListing`'s and marked old with them, after a save
|
|
42
|
+
* on the controller. Without it such a value is never looked up - `findListing` could not answer it - so a form names it
|
|
43
|
+
* from the record it holds (`selectedOptions`), or it shows as not found.
|
|
44
|
+
*/
|
|
45
|
+
find?: ApiEndpoint | TableJoinSource;
|
|
46
|
+
/**
|
|
47
|
+
* The query parameter the controller's `activeListing` narrows by on the server, for a picker another input narrows
|
|
48
|
+
* (`narrowedBy`): `"subscriptionGroupId"` asks `...?subscriptionGroupId={the group}` and keeps each answer apart. Without
|
|
49
|
+
* it, the whole list is read once and narrowed in the browser by the records' own field (`narrowedBy.key`).
|
|
50
|
+
*/
|
|
51
|
+
narrowQuery?: string;
|
|
24
52
|
backend?: CrudBackend;
|
|
25
53
|
}
|
|
26
54
|
/** The dropdown params `crudPicker` gives: spread them into a dropdown's input. */
|
|
27
55
|
export interface CrudPickerParams {
|
|
28
56
|
loadOptions: (request: DropdownPageRequest) => Promise<DropdownPage<TableJoinedRecord>>;
|
|
29
57
|
resolveOptions: (values: OptionValue[]) => Promise<readonly TableJoinedRecord[]>;
|
|
58
|
+
peekOption: (key: string, aged?: boolean) => TableJoinedRecord | undefined;
|
|
30
59
|
valueKey: string;
|
|
31
60
|
labelKey: string;
|
|
32
61
|
descriptionKey?: string;
|
|
@@ -37,12 +66,15 @@ export interface CrudPickerParams {
|
|
|
37
66
|
* for every form on the page (the owner's caching rule: a second dropdown, or an Add row, never asks again), searched
|
|
38
67
|
* and paged in the browser. A value the form already holds that the list does not offer - a parent since trashed - is
|
|
39
68
|
* found through `findListing`, sharing the calls and answers of the tables' joins, and shows tagged, never offered again.
|
|
69
|
+
* A value that is not the records' key (`valueKey`) is found through the picker's own `find` instead.
|
|
40
70
|
*
|
|
41
|
-
* @example { type:
|
|
71
|
+
* @example { type: EInputTypes.Dropdown, name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
72
|
+
* @example crudPicker(COMPANY_USERS, { labelKey: "fullname", valueKey: "userId", find: COMPANY_USERS_BY_ACCOUNT })
|
|
42
73
|
*/
|
|
43
|
-
export declare function crudPicker(controller: PickerController, { labelKey, descriptionKey, valueKey, backend }: CrudPickerOptions): CrudPickerParams;
|
|
74
|
+
export declare function crudPicker(controller: PickerController, { labelKey, descriptionKey, valueKey, find, narrowQuery, backend }: CrudPickerOptions): CrudPickerParams;
|
|
44
75
|
/**
|
|
45
76
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
46
|
-
* `findListing` named, and the list its picker offers. Drawn as they are until the
|
|
77
|
+
* `findListing` named and its pickers' own finds answered, and the list its picker offers. Drawn as they are until the
|
|
78
|
+
* next ask brings the new ones.
|
|
47
79
|
*/
|
|
48
80
|
export declare function expireLookups(controller: Partial<PickerController>): void;
|
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
import { isApiEndpoint } from "../Helpers/ApiRoutes.js";
|
|
2
2
|
import { getCrudBackend } from "../Helpers/CrudBackend.js";
|
|
3
3
|
import { filterItems } from "../Helpers/DropdownHelpers.js";
|
|
4
|
-
import { joinSourceName, tableJoins } from "../Helpers/TableJoins.js";
|
|
4
|
+
import { JOIN_LIFETIME, joinSourceName, tableJoins } from "../Helpers/TableJoins.js";
|
|
5
5
|
import { api } from "./BrowserApi.js";
|
|
6
6
|
/*
|
|
7
7
|
* The browser's half of a join: the call that looks records up, and a picker that shares what it looked up. The cache
|
|
8
8
|
* itself (`tableJoins`) knows nothing of the API; this is where an endpoint becomes a source it can call.
|
|
9
9
|
*/
|
|
10
10
|
const made = new Map();
|
|
11
|
+
/** The sources of the pickers' own finds (`crudPicker` `find`), by the name of the `findListing` of their controller. */
|
|
12
|
+
const finds = new Map();
|
|
13
|
+
/** The lists a picker asked the server for, one per narrowing value (`narrowQuery`), by the name of their whole list. */
|
|
14
|
+
const narrowedLists = new Map();
|
|
15
|
+
/** A field with no value: what `narrow.orUnset` adds to a narrowed list. */
|
|
16
|
+
const unset = (value) => value === null || value === undefined || value === "";
|
|
11
17
|
/**
|
|
12
18
|
* A join's source as the table calls it. An endpoint becomes one that POSTs the keys to it as an array and reads the
|
|
13
19
|
* records it answers - a Nexus controller's `findListing` - made once per endpoint, so every table and picker naming it
|
|
14
20
|
* shares one source and so one cache. A source of the page's own is used as it is.
|
|
21
|
+
*
|
|
22
|
+
* `key` is the field the records answered are filed under, `id` unless told: one whose values are another field (a
|
|
23
|
+
* person's account, which their company row carries beside its own key) is a source of its own, kept apart.
|
|
15
24
|
*/
|
|
16
|
-
export function joinSource(source, backend = getCrudBackend()) {
|
|
25
|
+
export function joinSource(source, backend = getCrudBackend(), key = "id") {
|
|
17
26
|
if (!isApiEndpoint(source))
|
|
18
27
|
return source;
|
|
19
|
-
const name = joinSourceName(source)
|
|
28
|
+
const name = key === "id" ? joinSourceName(source) : `${joinSourceName(source)} by ${key}`;
|
|
20
29
|
let found = made.get(name);
|
|
21
30
|
if (!found) {
|
|
22
31
|
found = {
|
|
23
32
|
name,
|
|
33
|
+
...(key !== "id" && { key }),
|
|
24
34
|
async load(keys, signal) {
|
|
25
35
|
const answer = await api.send(source, { body: keys, signal, toast: false });
|
|
26
36
|
if (!answer.ok)
|
|
@@ -32,30 +42,87 @@ export function joinSource(source, backend = getCrudBackend()) {
|
|
|
32
42
|
}
|
|
33
43
|
return found;
|
|
34
44
|
}
|
|
45
|
+
/** What a source answered for a key, fresh, or - `aged` - also once it was marked old. */
|
|
46
|
+
function peekSource(name, key, aged) {
|
|
47
|
+
if (!aged)
|
|
48
|
+
return tableJoins.peekFresh(name, key);
|
|
49
|
+
const state = tableJoins.peek(name, key);
|
|
50
|
+
return state.status === "ready" ? state.record : undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The record a `findListing` answered for a key, or one the page remembered there (`TableJoins.remember`), at once and
|
|
54
|
+
* with no call, while it is fresh: `undefined` while it is not in, and once it was marked old (a save on that controller,
|
|
55
|
+
* a Reload), so a picker asks again and shows the record as it is now. A picker over that controller's records names a
|
|
56
|
+
* value with it (`peekOption`), and so does a CRUD's save, which takes an old one too (`aged`) over no name at all.
|
|
57
|
+
*/
|
|
58
|
+
export function peekFound(findListing, key, aged = false) {
|
|
59
|
+
return peekSource(joinSourceName(findListing), key, aged);
|
|
60
|
+
}
|
|
35
61
|
/**
|
|
36
62
|
* A dropdown of a controller's usable records, for picking a parent in a form: the `activeListing` read once and kept
|
|
37
63
|
* for every form on the page (the owner's caching rule: a second dropdown, or an Add row, never asks again), searched
|
|
38
64
|
* and paged in the browser. A value the form already holds that the list does not offer - a parent since trashed - is
|
|
39
65
|
* found through `findListing`, sharing the calls and answers of the tables' joins, and shows tagged, never offered again.
|
|
66
|
+
* A value that is not the records' key (`valueKey`) is found through the picker's own `find` instead.
|
|
40
67
|
*
|
|
41
|
-
* @example { type:
|
|
68
|
+
* @example { type: EInputTypes.Dropdown, name: "regionId", label: "Region", required: true, ...crudPicker(REGIONS, { labelKey: "regionName" }) }
|
|
69
|
+
* @example crudPicker(COMPANY_USERS, { labelKey: "fullname", valueKey: "userId", find: COMPANY_USERS_BY_ACCOUNT })
|
|
42
70
|
*/
|
|
43
|
-
export function crudPicker(controller, { labelKey, descriptionKey, valueKey = "id", backend = getCrudBackend() }) {
|
|
71
|
+
export function crudPicker(controller, { labelKey, descriptionKey, valueKey = "id", find, narrowQuery, backend = getCrudBackend() }) {
|
|
44
72
|
const listName = joinSourceName(controller.activeListing);
|
|
73
|
+
// Where a value the kept list does not offer is looked up: `findListing` while the values are the records' keys, the
|
|
74
|
+
// picker's own find for another field, and nowhere without one.
|
|
75
|
+
const lookup = find ? joinSource(find, backend, valueKey) : valueKey === "id" ? joinSource(controller.findListing, backend) : null;
|
|
76
|
+
if (find && lookup) {
|
|
77
|
+
// Marked old with the controller's own lookups, after a save there (`expireLookups`).
|
|
78
|
+
const beside = joinSourceName(controller.findListing);
|
|
79
|
+
const kept = finds.get(beside) ?? new Set();
|
|
80
|
+
kept.add(lookup.name);
|
|
81
|
+
finds.set(beside, kept);
|
|
82
|
+
}
|
|
45
83
|
// The words a person reads, not the key: a Nexus key is an encrypted token, and a search would find letters in it.
|
|
46
84
|
const searchKeys = descriptionKey ? [labelKey, descriptionKey] : [labelKey];
|
|
47
|
-
const loadList = (signal) => api.send(controller.activeListing, { signal, toast: false }).then(answer => {
|
|
85
|
+
const loadList = (signal, query) => api.send(controller.activeListing, { signal, toast: false, ...(query && { query }) }).then(answer => {
|
|
48
86
|
if (!answer.ok)
|
|
49
87
|
throw new Error(answer.message ?? "The list could not be loaded.");
|
|
50
88
|
return backend.readList(answer.result);
|
|
51
89
|
});
|
|
90
|
+
/** The options narrowed by another input's value (`narrowedBy`): asked of the server by `narrowQuery`, else the kept list's. */
|
|
91
|
+
const narrowedList = async ({ key, value, orUnset }) => {
|
|
92
|
+
// The server narrows by a value; which records it adds for `orUnset` is its endpoint's rule.
|
|
93
|
+
if (narrowQuery && value !== null) {
|
|
94
|
+
const name = `${listName}?${narrowQuery}=${String(value)}`;
|
|
95
|
+
const kept = narrowedLists.get(listName) ?? new Set();
|
|
96
|
+
kept.add(name);
|
|
97
|
+
narrowedLists.set(listName, kept);
|
|
98
|
+
return tableJoins.list(name, signal => loadList(signal, { [narrowQuery]: String(value) }));
|
|
99
|
+
}
|
|
100
|
+
const all = await tableJoins.list(listName, loadList);
|
|
101
|
+
return all.filter(row => (value !== null && String(row[key]) === String(value)) || (orUnset && unset(row[key])));
|
|
102
|
+
};
|
|
52
103
|
return {
|
|
53
|
-
async loadOptions({ search, skip, pageSize }) {
|
|
54
|
-
const all = await tableJoins.list(listName, loadList);
|
|
104
|
+
async loadOptions({ search, skip, pageSize, narrow }) {
|
|
105
|
+
const all = narrow ? await narrowedList(narrow) : await tableJoins.list(listName, loadList);
|
|
55
106
|
const matches = search ? filterItems(all, search, searchKeys) : all;
|
|
56
107
|
return { items: matches.slice(skip, skip + pageSize), total: matches.length, hasMore: skip + pageSize < matches.length };
|
|
57
108
|
},
|
|
58
|
-
|
|
109
|
+
// A saved value the kept list offers is named from it, with no call (a form opened on a tree's country); only one it
|
|
110
|
+
// does not offer - a parent since trashed - is looked up.
|
|
111
|
+
async resolveOptions(values) {
|
|
112
|
+
const keys = values.map(String);
|
|
113
|
+
const kept = tableJoins.peekList(listName) ?? [];
|
|
114
|
+
const found = kept.filter(row => keys.includes(String(row[valueKey])));
|
|
115
|
+
const missing = keys.filter(key => !found.some(row => String(row[valueKey]) === key));
|
|
116
|
+
if (missing.length === 0 || !lookup)
|
|
117
|
+
return found;
|
|
118
|
+
return [...found, ...(await tableJoins.records(lookup, missing))];
|
|
119
|
+
},
|
|
120
|
+
// The same at once, from what the page holds: a box named on its first draw, a saved record named with no call. Only
|
|
121
|
+
// what is fresh, unless an old record is asked for: a save names a value with one rather than with nothing.
|
|
122
|
+
peekOption(key, aged = false) {
|
|
123
|
+
const kept = tableJoins.peekList(listName, aged ? Infinity : JOIN_LIFETIME)?.find(row => String(row[valueKey]) === key);
|
|
124
|
+
return kept ?? (lookup ? peekSource(lookup.name, key, aged) : undefined);
|
|
125
|
+
},
|
|
59
126
|
valueKey,
|
|
60
127
|
labelKey,
|
|
61
128
|
...(descriptionKey && { descriptionKey }),
|
|
@@ -64,11 +131,20 @@ export function crudPicker(controller, { labelKey, descriptionKey, valueKey = "i
|
|
|
64
131
|
}
|
|
65
132
|
/**
|
|
66
133
|
* Marks what the page looked up from a controller as old, after a write there may have changed it: the records its
|
|
67
|
-
* `findListing` named, and the list its picker offers. Drawn as they are until the
|
|
134
|
+
* `findListing` named and its pickers' own finds answered, and the list its picker offers. Drawn as they are until the
|
|
135
|
+
* next ask brings the new ones.
|
|
68
136
|
*/
|
|
69
137
|
export function expireLookups(controller) {
|
|
70
|
-
if (controller.findListing)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
138
|
+
if (controller.findListing) {
|
|
139
|
+
const name = joinSourceName(controller.findListing);
|
|
140
|
+
tableJoins.expire(name);
|
|
141
|
+
for (const other of finds.get(name) ?? [])
|
|
142
|
+
tableJoins.expire(other);
|
|
143
|
+
}
|
|
144
|
+
if (controller.activeListing) {
|
|
145
|
+
const name = joinSourceName(controller.activeListing);
|
|
146
|
+
tableJoins.expireList(name);
|
|
147
|
+
for (const narrowed of narrowedLists.get(name) ?? [])
|
|
148
|
+
tableJoins.expireList(narrowed);
|
|
149
|
+
}
|
|
74
150
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports an ES module by its full address, as the browser does, never through the app's bundler: a module a server
|
|
3
|
+
* serves at a versioned address (the image editor from the Nexus Drive).
|
|
4
|
+
*
|
|
5
|
+
* @example const editor = await importModule<ImageEditorModule>("https://drive.example/Assets/File/nexus-image-editor.3f2a.js");
|
|
6
|
+
*/
|
|
7
|
+
export declare function importModule<TModule>(url: string): Promise<TModule>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The one dynamic `import()` of an address known only at run time, kept alone in this file on purpose.
|
|
3
|
+
*
|
|
4
|
+
* The package build rewrites "./Button.tsx" to "./Button.js" (`rewriteRelativeImportExtensions`), and for an `import()`
|
|
5
|
+
* of a variable it does so at run time: it wraps the address in a helper call, `import(helper(url))`. The bundler-ignore
|
|
6
|
+
* comments are then inside a call, so Next's Turbopack no longer leaves the import alone: it compiles it to "Cannot find
|
|
7
|
+
* module as expression is too dynamic", and the image editor never loads in an app's production build. This file imports
|
|
8
|
+
* nothing, so it is built a second time without that rewrite (`tsconfig.build.module-import.json`, after the main build,
|
|
9
|
+
* in `npm run build`), and the published file keeps the plain `import(/* webpackIgnore: true *\/ ... url)` that webpack,
|
|
10
|
+
* Turbopack and Vite all leave to the browser. `scripts/test-dist-loader.mts` (in `npm test`) fails if it ever does not.
|
|
11
|
+
*
|
|
12
|
+
* Add no import to this file: the second build would leave a "./x.ts" in it unrewritten.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Imports an ES module by its full address, as the browser does, never through the app's bundler: a module a server
|
|
16
|
+
* serves at a versioned address (the image editor from the Nexus Drive).
|
|
17
|
+
*
|
|
18
|
+
* @example const editor = await importModule<ImageEditorModule>("https://drive.example/Assets/File/nexus-image-editor.3f2a.js");
|
|
19
|
+
*/
|
|
20
|
+
export function importModule(url) {
|
|
21
|
+
return import(/* webpackIgnore: true */ /* turbopackIgnore: true */ /* @vite-ignore */ url);
|
|
22
|
+
}
|
package/dist/Shared.Index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./Components/Documents/Button.tsx";
|
|
2
|
+
export * from "./Components/Documents/PrintDocument.tsx";
|
|
2
3
|
export * from "./Components/Inputs/Field.tsx";
|
|
3
4
|
export * from "./Components/Layouts/ThemeScript.tsx";
|
|
4
5
|
export * from "./Components/Viewers/MarkdownView.tsx";
|
|
6
|
+
export * from "./Components/Viewers/QrCodeImage.tsx";
|
|
5
7
|
export * from "./Components/Viewers/RichTextView.tsx";
|
|
6
8
|
export * from "./Helpers/ApiClient.ts";
|
|
7
9
|
export * from "./Helpers/ApiFormHelpers.ts";
|
|
@@ -17,21 +19,33 @@ export * from "./Helpers/ClassHelpers.ts";
|
|
|
17
19
|
export * from "./Helpers/CodeHelpers.ts";
|
|
18
20
|
export * from "./Helpers/CrudBackend.ts";
|
|
19
21
|
export * from "./Helpers/CrudColumns.ts";
|
|
22
|
+
export * from "./Helpers/CrudDetailsTabs.ts";
|
|
20
23
|
export * from "./Helpers/CrudHelpers.ts";
|
|
21
24
|
export * from "./Helpers/DateHelpers.ts";
|
|
22
25
|
export * from "./Helpers/DateTimeHelpers.ts";
|
|
23
26
|
export * from "./Helpers/DropdownHelpers.ts";
|
|
24
27
|
export * from "./Helpers/ExcelBackend.ts";
|
|
25
28
|
export * from "./Helpers/ExcelHelpers.ts";
|
|
29
|
+
export * from "./Helpers/FileAdapter.ts";
|
|
30
|
+
export * from "./Helpers/FilePrimaries.ts";
|
|
31
|
+
export * from "./Helpers/NexusDriveAdapter.ts";
|
|
32
|
+
export * from "./Helpers/FormConfirm.ts";
|
|
26
33
|
export * from "./Helpers/FormDrafts.ts";
|
|
27
34
|
export * from "./Helpers/FormStore.ts";
|
|
28
35
|
export * from "./Helpers/InputParamsHelpers.ts";
|
|
36
|
+
export * from "./Helpers/InputConditions.ts";
|
|
37
|
+
export * from "./Helpers/InputNarrowing.ts";
|
|
38
|
+
export * from "./Helpers/CrudNarrowPick.ts";
|
|
39
|
+
export * from "./Helpers/CrudScopes.ts";
|
|
40
|
+
export * from "./Helpers/CrudRowForms.ts";
|
|
29
41
|
export * from "./Helpers/MarkdownEditing.ts";
|
|
30
42
|
export * from "./Helpers/MarkdownHelpers.ts";
|
|
31
43
|
export * from "./Helpers/MessageBuilder.ts";
|
|
32
44
|
export * from "./Helpers/NepaliCalendar.ts";
|
|
33
45
|
export * from "./Helpers/NumberHelpers.ts";
|
|
34
46
|
export * from "./Helpers/Permissions.ts";
|
|
47
|
+
export * from "./Helpers/QrCode.ts";
|
|
48
|
+
export * from "./Helpers/RecordCards.ts";
|
|
35
49
|
export * from "./Helpers/RequestCache.ts";
|
|
36
50
|
export * from "./Helpers/RichTextHelpers.ts";
|
|
37
51
|
export * from "./Helpers/RowsStore.ts";
|
|
@@ -48,10 +62,12 @@ export * from "./Interfaces/ChatInterfaces.ts";
|
|
|
48
62
|
export * from "./Interfaces/CrudInterfaces.ts";
|
|
49
63
|
export * from "./Interfaces/DateInterfaces.ts";
|
|
50
64
|
export * from "./Interfaces/ExcelInterfaces.ts";
|
|
65
|
+
export * from "./Interfaces/FileInterfaces.ts";
|
|
51
66
|
export * from "./Interfaces/FormInterfaces.ts";
|
|
52
67
|
export * from "./Interfaces/InputInterfaces.ts";
|
|
53
68
|
export * from "./Interfaces/MessageInterfaces.ts";
|
|
54
69
|
export * from "./Interfaces/PreferenceInterfaces.ts";
|
|
70
|
+
export * from "./Interfaces/RecordInterfaces.ts";
|
|
55
71
|
export * from "./Interfaces/TableInterfaces.ts";
|
|
56
72
|
export * from "./Interfaces/ThemeInterfaces.ts";
|
|
57
73
|
export * from "./Interfaces/TypeInterfaces.ts";
|
package/dist/Shared.Index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Works everywhere: server components, client components, and plain TypeScript.
|
|
2
2
|
// Components
|
|
3
3
|
export * from "./Components/Documents/Button.js";
|
|
4
|
+
export * from "./Components/Documents/PrintDocument.js";
|
|
4
5
|
export * from "./Components/Inputs/Field.js";
|
|
5
6
|
export * from "./Components/Layouts/ThemeScript.js";
|
|
6
7
|
export * from "./Components/Viewers/MarkdownView.js";
|
|
8
|
+
export * from "./Components/Viewers/QrCodeImage.js";
|
|
7
9
|
export * from "./Components/Viewers/RichTextView.js";
|
|
8
10
|
// Helpers
|
|
9
11
|
export * from "./Helpers/ApiClient.js";
|
|
@@ -20,21 +22,33 @@ export * from "./Helpers/ClassHelpers.js";
|
|
|
20
22
|
export * from "./Helpers/CodeHelpers.js";
|
|
21
23
|
export * from "./Helpers/CrudBackend.js";
|
|
22
24
|
export * from "./Helpers/CrudColumns.js";
|
|
25
|
+
export * from "./Helpers/CrudDetailsTabs.js";
|
|
23
26
|
export * from "./Helpers/CrudHelpers.js";
|
|
24
27
|
export * from "./Helpers/DateHelpers.js";
|
|
25
28
|
export * from "./Helpers/DateTimeHelpers.js";
|
|
26
29
|
export * from "./Helpers/DropdownHelpers.js";
|
|
27
30
|
export * from "./Helpers/ExcelBackend.js";
|
|
28
31
|
export * from "./Helpers/ExcelHelpers.js";
|
|
32
|
+
export * from "./Helpers/FileAdapter.js";
|
|
33
|
+
export * from "./Helpers/FilePrimaries.js";
|
|
34
|
+
export * from "./Helpers/NexusDriveAdapter.js";
|
|
35
|
+
export * from "./Helpers/FormConfirm.js";
|
|
29
36
|
export * from "./Helpers/FormDrafts.js";
|
|
30
37
|
export * from "./Helpers/FormStore.js";
|
|
31
38
|
export * from "./Helpers/InputParamsHelpers.js";
|
|
39
|
+
export * from "./Helpers/InputConditions.js";
|
|
40
|
+
export * from "./Helpers/InputNarrowing.js";
|
|
41
|
+
export * from "./Helpers/CrudNarrowPick.js";
|
|
42
|
+
export * from "./Helpers/CrudScopes.js";
|
|
43
|
+
export * from "./Helpers/CrudRowForms.js";
|
|
32
44
|
export * from "./Helpers/MarkdownEditing.js";
|
|
33
45
|
export * from "./Helpers/MarkdownHelpers.js";
|
|
34
46
|
export * from "./Helpers/MessageBuilder.js";
|
|
35
47
|
export * from "./Helpers/NepaliCalendar.js";
|
|
36
48
|
export * from "./Helpers/NumberHelpers.js";
|
|
37
49
|
export * from "./Helpers/Permissions.js";
|
|
50
|
+
export * from "./Helpers/QrCode.js";
|
|
51
|
+
export * from "./Helpers/RecordCards.js";
|
|
38
52
|
export * from "./Helpers/RequestCache.js";
|
|
39
53
|
export * from "./Helpers/RichTextHelpers.js";
|
|
40
54
|
export * from "./Helpers/RowsStore.js";
|
|
@@ -52,10 +66,12 @@ export * from "./Interfaces/ChatInterfaces.js";
|
|
|
52
66
|
export * from "./Interfaces/CrudInterfaces.js";
|
|
53
67
|
export * from "./Interfaces/DateInterfaces.js";
|
|
54
68
|
export * from "./Interfaces/ExcelInterfaces.js";
|
|
69
|
+
export * from "./Interfaces/FileInterfaces.js";
|
|
55
70
|
export * from "./Interfaces/FormInterfaces.js";
|
|
56
71
|
export * from "./Interfaces/InputInterfaces.js";
|
|
57
72
|
export * from "./Interfaces/MessageInterfaces.js";
|
|
58
73
|
export * from "./Interfaces/PreferenceInterfaces.js";
|
|
74
|
+
export * from "./Interfaces/RecordInterfaces.js";
|
|
59
75
|
export * from "./Interfaces/TableInterfaces.js";
|
|
60
76
|
export * from "./Interfaces/ThemeInterfaces.js";
|
|
61
77
|
export * from "./Interfaces/TypeInterfaces.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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",
|
|
@@ -44,23 +44,28 @@
|
|
|
44
44
|
"CHANGELOG.md"
|
|
45
45
|
],
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.build.module-import.json",
|
|
48
48
|
"typecheck": "tsc -p tsconfig.json",
|
|
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
52
|
"test:proxy": "node scripts/test-api-proxy.mts",
|
|
53
53
|
"test:chat": "node scripts/test-chat.mts",
|
|
54
|
+
"test:crud-tabs": "node scripts/test-crud-details-tabs.mts",
|
|
55
|
+
"test:crud-profile": "node scripts/test-crud-profile-fields.mts",
|
|
54
56
|
"test:codes": "node scripts/test-code-helpers.mts",
|
|
55
57
|
"test:dates": "node scripts/test-date-helpers.mts",
|
|
56
58
|
"test:datetimes": "node scripts/test-datetime-helpers.mts",
|
|
57
59
|
"test:drag": "node scripts/test-drag-helpers.mts",
|
|
58
60
|
"test:excel": "node scripts/test-excel.mts",
|
|
61
|
+
"test:files": "node scripts/test-files.mts",
|
|
59
62
|
"test:dropdown": "node scripts/test-dropdown-helpers.mts",
|
|
60
63
|
"test:forms": "node scripts/test-form-store.mts",
|
|
64
|
+
"test:form-confirm": "node scripts/test-form-confirm.mts",
|
|
61
65
|
"test:markdown": "node scripts/test-markdown.mts",
|
|
62
66
|
"test:numbers": "node scripts/test-number-helpers.mts",
|
|
63
67
|
"test:popups": "node scripts/test-popup-rules.mts",
|
|
68
|
+
"test:qr": "node scripts/test-qr-code.mts",
|
|
64
69
|
"test:richtext": "node scripts/test-richtext.mts",
|
|
65
70
|
"test:rows": "node scripts/test-rows-store.mts",
|
|
66
71
|
"test:table": "node scripts/test-table.mts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* A person's avatar, wherever one is drawn: the chat, a dropdown's avatar column, the admin bar.
|
|
2
|
+
* A person's avatar, wherever one is drawn: the chat, a dropdown's avatar column, the admin bar. Its tones also back an
|
|
3
|
+
* owner's initials in a picture with none (`.nx-picture--initials`, Nexus.Files.css), so a name has one tone everywhere.
|
|
3
4
|
*/
|
|
4
5
|
@layer nexus.components {
|
|
5
6
|
.nx-avatar {
|
|
@@ -29,19 +30,19 @@
|
|
|
29
30
|
--nx-avatar-size: 44px;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
.nx-avatar[data-tone="primary"] {
|
|
33
|
+
:is(.nx-avatar, .nx-picture)[data-tone="primary"] {
|
|
33
34
|
background: var(--nx-primary-soft);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
.nx-avatar[data-tone="info"] {
|
|
37
|
+
:is(.nx-avatar, .nx-picture)[data-tone="info"] {
|
|
37
38
|
background: var(--nx-info-soft);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
.nx-avatar[data-tone="success"] {
|
|
41
|
+
:is(.nx-avatar, .nx-picture)[data-tone="success"] {
|
|
41
42
|
background: var(--nx-success-soft);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
.nx-avatar[data-tone="warning"] {
|
|
45
|
+
:is(.nx-avatar, .nx-picture)[data-tone="warning"] {
|
|
45
46
|
background: var(--nx-warning-soft);
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
vertical-align: 0;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
.nx-btn:hover:not(:disabled) {
|
|
42
|
+
.nx-btn:hover:not(:disabled, [aria-disabled="true"]) {
|
|
43
43
|
background: var(--nx-btn-bg-hover);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.nx-btn:active:not(:disabled) {
|
|
46
|
+
.nx-btn:active:not(:disabled, [aria-disabled="true"]) {
|
|
47
47
|
transform: translateY(1px);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -52,12 +52,18 @@
|
|
|
52
52
|
box-shadow: var(--nx-focus-ring);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.
|
|
55
|
+
/* Unavailable. One marked `aria-disabled` looks the same and stays in the Tab order, so a press can say why; its ring
|
|
56
|
+
is drawn strong enough to read through the dimming. */
|
|
57
|
+
.nx-btn:is(:disabled, [aria-disabled="true"]) {
|
|
56
58
|
cursor: not-allowed;
|
|
57
59
|
opacity: 0.55;
|
|
58
60
|
box-shadow: none;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
.nx-btn[aria-disabled="true"]:focus-visible {
|
|
64
|
+
box-shadow: 0 0 0 3px var(--nx-primary);
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
.nx-btn[aria-busy="true"] {
|
|
62
68
|
cursor: progress;
|
|
63
69
|
opacity: 0.8;
|
|
@@ -120,7 +126,7 @@
|
|
|
120
126
|
.nx-btn--hover-success,
|
|
121
127
|
.nx-btn--hover-warning,
|
|
122
128
|
.nx-btn--hover-info
|
|
123
|
-
):is(:hover, :focus-visible):not(:disabled) {
|
|
129
|
+
):is(:hover, :focus-visible):not(:disabled, [aria-disabled="true"]) {
|
|
124
130
|
--nx-btn-bg: var(--nx-btn-tone-soft);
|
|
125
131
|
--nx-btn-bg-hover: var(--nx-btn-tone-soft);
|
|
126
132
|
--nx-btn-border: var(--nx-btn-tone);
|
|
@@ -335,6 +335,22 @@
|
|
|
335
335
|
border-color: var(--nx-danger);
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
/*
|
|
339
|
+
* Tabs (`RadioGroup design="tabs"`): the options on a `TabButtons` track that wraps (`nx-tabs--wrap`), each chosen
|
|
340
|
+
* option raised; the chosen option's description on a line under the track, the others' in their tooltips. An
|
|
341
|
+
* invalid group edges the track in the danger colour.
|
|
342
|
+
*/
|
|
343
|
+
.nx-choices--tabs[data-invalid] .nx-tabs {
|
|
344
|
+
--nx-tabs-track-border: var(--nx-danger);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.nx-choices__note {
|
|
348
|
+
margin: var(--nx-space-2) 0 0;
|
|
349
|
+
color: var(--nx-text-muted);
|
|
350
|
+
font-size: var(--nx-field-label-font);
|
|
351
|
+
line-height: 1.4;
|
|
352
|
+
}
|
|
353
|
+
|
|
338
354
|
[data-nx-motion="reduce"] .nx-choice__control,
|
|
339
355
|
[data-nx-motion="reduce"] .nx-choice__control::before,
|
|
340
356
|
[data-nx-motion="reduce"] .nx-choices--cards .nx-choice {
|