nexus-shared 1.1.20 → 2.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/LICENSE +21 -0
- package/README.md +903 -0
- package/dist/Client.Index.d.ts +44 -0
- package/dist/Client.Index.js +49 -0
- package/dist/Components/Documents/Button.d.ts +29 -0
- package/dist/Components/Documents/Button.js +11 -0
- package/dist/Components/Documents/Menu.d.ts +102 -0
- package/dist/Components/Documents/Menu.js +238 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +127 -0
- package/dist/Components/Documents/TabButtons.d.ts +62 -0
- package/dist/Components/Documents/TabButtons.js +19 -0
- package/dist/Components/Forms/ApiForm.d.ts +21 -0
- package/dist/Components/Forms/ApiForm.js +99 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +556 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +319 -0
- package/dist/Components/Forms/SubmitForm.d.ts +65 -0
- package/dist/Components/Forms/SubmitForm.js +198 -0
- package/dist/Components/Inputs/Calendar.d.ts +69 -0
- package/dist/Components/Inputs/Calendar.js +258 -0
- package/dist/Components/Inputs/CheckBox.d.ts +53 -0
- package/dist/Components/Inputs/CheckBox.js +87 -0
- package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
- package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
- package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
- package/dist/Components/Inputs/ChoiceField.js +169 -0
- package/dist/Components/Inputs/DatePicker.d.ts +54 -0
- package/dist/Components/Inputs/DatePicker.js +246 -0
- package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
- package/dist/Components/Inputs/DateRangePicker.js +283 -0
- package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
- package/dist/Components/Inputs/DateTimePicker.js +399 -0
- package/dist/Components/Inputs/Dropdown.d.ts +117 -0
- package/dist/Components/Inputs/Dropdown.js +839 -0
- package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
- package/dist/Components/Inputs/DropdownParts.js +102 -0
- package/dist/Components/Inputs/EditorShell.d.ts +45 -0
- package/dist/Components/Inputs/EditorShell.js +214 -0
- package/dist/Components/Inputs/Field.d.ts +53 -0
- package/dist/Components/Inputs/Field.js +16 -0
- package/dist/Components/Inputs/GroupForm.d.ts +20 -0
- package/dist/Components/Inputs/GroupForm.js +170 -0
- package/dist/Components/Inputs/InputField.d.ts +59 -0
- package/dist/Components/Inputs/InputField.js +169 -0
- package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
- package/dist/Components/Inputs/InputRenderer.js +99 -0
- package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
- package/dist/Components/Inputs/MarkdownEditor.js +289 -0
- package/dist/Components/Inputs/NumberBox.d.ts +53 -0
- package/dist/Components/Inputs/NumberBox.js +228 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
- package/dist/Components/Inputs/RadioGroup.js +43 -0
- package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
- package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
- package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
- package/dist/Components/Inputs/RichTextEditor.js +1170 -0
- package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
- package/dist/Components/Inputs/RichTextParts.js +157 -0
- package/dist/Components/Inputs/RowsInput.d.ts +104 -0
- package/dist/Components/Inputs/RowsInput.js +259 -0
- package/dist/Components/Inputs/Slider.d.ts +87 -0
- package/dist/Components/Inputs/Slider.js +216 -0
- package/dist/Components/Inputs/TabularForm.d.ts +23 -0
- package/dist/Components/Inputs/TabularForm.js +283 -0
- package/dist/Components/Inputs/TextArea.d.ts +43 -0
- package/dist/Components/Inputs/TextArea.js +31 -0
- package/dist/Components/Inputs/TextBox.d.ts +52 -0
- package/dist/Components/Inputs/TextBox.js +58 -0
- package/dist/Components/Inputs/TextField.d.ts +31 -0
- package/dist/Components/Inputs/TextField.js +35 -0
- package/dist/Components/Inputs/TimePanel.d.ts +34 -0
- package/dist/Components/Inputs/TimePanel.js +288 -0
- package/dist/Components/Inputs/TimePicker.d.ts +54 -0
- package/dist/Components/Inputs/TimePicker.js +239 -0
- package/dist/Components/Inputs/TreeView.d.ts +160 -0
- package/dist/Components/Inputs/TreeView.js +947 -0
- package/dist/Components/Layouts/MessageBox.d.ts +71 -0
- package/dist/Components/Layouts/MessageBox.js +104 -0
- package/dist/Components/Layouts/Popup.d.ts +92 -0
- package/dist/Components/Layouts/Popup.js +376 -0
- package/dist/Components/Layouts/PopupContext.d.ts +2 -0
- package/dist/Components/Layouts/PopupContext.js +4 -0
- package/dist/Components/Layouts/PopupDock.d.ts +22 -0
- package/dist/Components/Layouts/PopupDock.js +121 -0
- package/dist/Components/Layouts/PopupHost.d.ts +56 -0
- package/dist/Components/Layouts/PopupHost.js +163 -0
- package/dist/Components/Layouts/PopupRules.d.ts +30 -0
- package/dist/Components/Layouts/PopupRules.js +63 -0
- package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
- package/dist/Components/Layouts/ThemePicker.js +22 -0
- package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
- package/dist/Components/Layouts/ThemeScript.js +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
- package/dist/Components/Layouts/Toaster.d.ts +76 -0
- package/dist/Components/Layouts/Toaster.js +172 -0
- package/dist/Components/Viewers/DataTable.d.ts +25 -0
- package/dist/Components/Viewers/DataTable.js +542 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
- package/dist/Components/Viewers/DataTableParts.js +145 -0
- package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
- package/dist/Components/Viewers/MarkdownView.js +79 -0
- package/dist/Components/Viewers/RichTextView.d.ts +18 -0
- package/dist/Components/Viewers/RichTextView.js +17 -0
- package/dist/Helpers/AnimationHelpers.d.ts +6 -0
- package/dist/Helpers/AnimationHelpers.js +35 -0
- package/dist/Helpers/ApiClient.d.ts +74 -0
- package/dist/Helpers/ApiClient.js +197 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
- package/dist/Helpers/ApiFormHelpers.js +56 -0
- package/dist/Helpers/ApiModules.d.ts +135 -0
- package/dist/Helpers/ApiModules.js +194 -0
- package/dist/Helpers/ApiResponses.d.ts +41 -0
- package/dist/Helpers/ApiResponses.js +159 -0
- package/dist/Helpers/ApiRoutes.d.ts +150 -0
- package/dist/Helpers/ApiRoutes.js +191 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +56 -0
- package/dist/Helpers/CrudBackend.js +119 -0
- package/dist/Helpers/CrudHelpers.d.ts +76 -0
- package/dist/Helpers/CrudHelpers.js +202 -0
- package/dist/Helpers/DateHelpers.d.ts +88 -0
- package/dist/Helpers/DateHelpers.js +363 -0
- package/dist/Helpers/DatePreferences.d.ts +14 -0
- package/dist/Helpers/DatePreferences.js +94 -0
- package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
- package/dist/Helpers/DateTimeHelpers.js +286 -0
- package/dist/Helpers/DragHelpers.d.ts +129 -0
- package/dist/Helpers/DragHelpers.js +281 -0
- package/dist/Helpers/DropdownHelpers.d.ts +56 -0
- package/dist/Helpers/DropdownHelpers.js +180 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +231 -0
- package/dist/Helpers/FormStore.js +734 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
- package/dist/Helpers/InputParamsHelpers.js +388 -0
- package/dist/Helpers/MarkdownEditing.d.ts +27 -0
- package/dist/Helpers/MarkdownEditing.js +250 -0
- package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
- package/dist/Helpers/MarkdownHelpers.js +775 -0
- package/dist/Helpers/MessageBuilder.d.ts +74 -0
- package/dist/Helpers/MessageBuilder.js +254 -0
- package/dist/Helpers/NepaliCalendar.d.ts +25 -0
- package/dist/Helpers/NepaliCalendar.js +107 -0
- package/dist/Helpers/NumberHelpers.d.ts +71 -0
- package/dist/Helpers/NumberHelpers.js +202 -0
- package/dist/Helpers/Permissions.d.ts +62 -0
- package/dist/Helpers/Permissions.js +133 -0
- package/dist/Helpers/PopoverHelpers.d.ts +43 -0
- package/dist/Helpers/PopoverHelpers.js +91 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +160 -0
- package/dist/Helpers/RowsStore.js +584 -0
- package/dist/Helpers/TableColumns.d.ts +40 -0
- package/dist/Helpers/TableColumns.js +254 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +102 -0
- package/dist/Helpers/TableHelpers.js +281 -0
- package/dist/Helpers/TableStore.d.ts +142 -0
- package/dist/Helpers/TableStore.js +530 -0
- package/dist/Helpers/ThemeHelpers.d.ts +15 -0
- package/dist/Helpers/ThemeHelpers.js +16 -0
- package/dist/Helpers/TimeHelpers.d.ts +73 -0
- package/dist/Helpers/TimeHelpers.js +253 -0
- package/dist/Helpers/TreeStore.d.ts +188 -0
- package/dist/Helpers/TreeStore.js +577 -0
- package/dist/Helpers/ValidationHelpers.d.ts +124 -0
- package/dist/Helpers/ValidationHelpers.js +301 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
- package/dist/Interfaces/ApiInterfaces.js +24 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
- package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/FormInterfaces.d.ts +352 -0
- package/dist/Interfaces/FormInterfaces.js +1 -0
- package/dist/Interfaces/InputInterfaces.d.ts +141 -0
- package/dist/Interfaces/InputInterfaces.js +1 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
- package/dist/Interfaces/MessageInterfaces.js +5 -0
- package/dist/Interfaces/TableInterfaces.d.ts +291 -0
- package/dist/Interfaces/TableInterfaces.js +1 -0
- package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
- package/dist/Interfaces/ThemeInterfaces.js +17 -0
- package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
- package/dist/Interfaces/TypeInterfaces.js +1 -0
- package/dist/Server.Index.d.ts +2 -0
- package/dist/Server.Index.js +5 -0
- package/dist/Services/ApiProxy.d.ts +32 -0
- package/dist/Services/ApiProxy.js +111 -0
- package/dist/Services/BrowserApi.d.ts +78 -0
- package/dist/Services/BrowserApi.js +111 -0
- package/dist/Services/ServerApi.d.ts +49 -0
- package/dist/Services/ServerApi.js +75 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +43 -0
- package/dist/Shared.Index.js +47 -0
- package/package.json +82 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +151 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +84 -0
- package/src/Styles/Nexus.DateTime.css +81 -0
- package/src/Styles/Nexus.Dropdown.css +774 -0
- package/src/Styles/Nexus.Editor.css +297 -0
- package/src/Styles/Nexus.Form.css +298 -0
- package/src/Styles/Nexus.Index.css +34 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +249 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +566 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +364 -0
- package/src/Styles/Nexus.Table.css +1014 -0
- package/src/Styles/Nexus.Theme.Picker.css +159 -0
- package/src/Styles/Nexus.Themes.css +194 -0
- package/src/Styles/Nexus.Time.css +147 -0
- package/src/Styles/Nexus.Toast.css +265 -0
- package/src/Styles/Nexus.Tokens.css +72 -0
- package/src/Styles/Nexus.Tree.css +435 -0
- package/src/api-services/authentication-service.tsx +0 -23
- package/src/api-services/preference-service.tsx +0 -5
- package/src/api-services/system-service.tsx +0 -34
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -142
- package/src/components/documents/icon-box.tsx +0 -93
- package/src/components/documents/page-title.tsx +0 -7
- package/src/components/documents/tab-button.tsx +0 -172
- package/src/components/documents/tag.tsx +0 -30
- package/src/components/index.js +0 -0
- package/src/components/inputs/checkbox-input.tsx +0 -66
- package/src/components/inputs/input-box.tsx +0 -48
- package/src/components/inputs/input-element.tsx +0 -65
- package/src/components/inputs/input-form.tsx +0 -164
- package/src/components/inputs/input.tsx +0 -181
- package/src/components/inputs/number-input.tsx +0 -108
- package/src/components/inputs/radiobox-input.tsx +0 -53
- package/src/components/inputs/textarea-input.tsx +0 -47
- package/src/components/inputs/textbox-input.tsx +0 -45
- package/src/components/layouts/global-dialogbox.tsx +0 -431
- package/src/components/layouts/global-layout.tsx +0 -59
- package/src/components/layouts/layout-helpers.tsx +0 -20
- package/src/components/layouts/panels/user-panel.tsx +0 -112
- package/src/components/layouts/utility-menu.tsx +0 -49
- package/src/components/panels/theme-panel.tsx +0 -46
- package/src/helpers/bitwise-helpers.tsx +0 -11
- package/src/helpers/browser-helpers.tsx +0 -156
- package/src/helpers/datasource-helpers.tsx +0 -99
- package/src/helpers/element-helpers.tsx +0 -57
- package/src/helpers/input-helpers.tsx +0 -74
- package/src/helpers/string-helpers.tsx +0 -28
- package/src/helpers/utility-helpers.tsx +0 -45
- package/src/helpers/validation-helpers.tsx +0 -302
- package/src/index.ts +0 -26
- package/src/interface.ts +0 -19
- package/src/interfaces/auth-token-interfaces.tsx +0 -6
- package/src/interfaces/browser-interfaces.tsx +0 -36
- package/src/interfaces/button-interfaces.tsx +0 -63
- package/src/interfaces/datasource-interfaces.tsx +0 -22
- package/src/interfaces/datatable-interfaces.tsx +0 -25
- package/src/interfaces/dialogbox-interfaces.tsx +0 -5
- package/src/interfaces/exception-interfaces.tsx +0 -99
- package/src/interfaces/http-interfaces.tsx +0 -129
- package/src/interfaces/icon-interfaces.tsx +0 -129
- package/src/interfaces/input-interfaces.tsx +0 -410
- package/src/interfaces/layout-interfaces.tsx +0 -191
- package/src/interfaces/menu-interfaces.tsx +0 -49
- package/src/interfaces/message-interfaces.tsx +0 -32
- package/src/interfaces/permission-interfaces.tsx +0 -9
- package/src/interfaces/storage-interfaces.tsx +0 -5
- package/src/interfaces/system-interfaces.tsx +0 -22
- package/src/interfaces/theme-interfaces.tsx +0 -123
- package/src/interfaces/type-interfaces.tsx +0 -28
- package/src/interfaces/user-interfaces.tsx +0 -47
- package/src/nexus-client.tsx +0 -21
- package/src/nexus.environments.tsx +0 -66
- package/src/proxy-api/proxy-backend.tsx +0 -60
- package/src/proxy-api/proxy-constants.tsx +0 -28
- package/src/proxy-api/proxy-helpers.tsx +0 -91
- package/src/proxy-api-client.tsx +0 -1
- package/src/proxy-api-server.tsx +0 -2
- package/src/services/http-client-services.tsx +0 -75
- package/src/services/http-services.tsx +0 -158
- package/src/services/loader-service.tsx +0 -185
- package/src/services/localstorage-service.tsx +0 -119
- package/src/services/message-services.tsx +0 -383
- package/src/services/theme-service.tsx +0 -161
- package/src/services/user-services.tsx +0 -10
- package/src/sso-config/auth-token-validation.ts +0 -16
- package/src/sso-config/callback-route.tsx +0 -35
- package/src/sso-config/cookie-encryption.ts +0 -51
- package/src/sso-config/cookie-helpers.tsx +0 -102
- package/src/sso-config/forgot-password-route.tsx +0 -36
- package/src/sso-config/oauth-callback-state.ts +0 -61
- package/src/sso-config/pkce-helpers.ts +0 -9
- package/src/sso-config/provider-complete-route.tsx +0 -18
- package/src/sso-config/redirect-context-actions.tsx +0 -45
- package/src/sso-config/redirect-context-helpers.tsx +0 -159
- package/src/sso-config/redirect-context-persist.tsx +0 -18
- package/src/sso-config/redirect-context-route.tsx +0 -45
- package/src/sso-config/refresh-route.tsx +0 -23
- package/src/sso-config/sign-in-route.tsx +0 -74
- package/src/sso-config/sign-out-route.tsx +0 -57
- package/src/sso-config/sign-up-route.tsx +0 -57
- package/src/sso-config/sso-backend-flow.ts +0 -38
- package/src/sso-config/sso-complete-route.tsx +0 -50
- package/src/sso-config/sso-config.ts +0 -59
- package/src/sso-config/sso-interfaces.tsx +0 -99
- package/src/sso-config/sso-response-helpers.tsx +0 -20
- package/src/sso-server.tsx +0 -21
- package/src/styles/nexus.animation.css +0 -269
- package/src/styles/nexus.core.css +0 -119
- package/src/styles/nexus.dialog.css +0 -144
- package/src/styles/nexus.icon.css +0 -51
- package/src/styles/nexus.input.css +0 -207
- package/src/styles/nexus.loader.css +0 -11
- package/src/styles/nexus.logic.css +0 -47
- package/src/styles/nexus.utility.css +0 -347
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The model behind TreeView: nodes built from a flat list of items, checked counts kept up to date as boxes change,
|
|
3
|
+
* locks, and the list of visible rows. It has no React in it, so a tree of 100,000 items is plain objects and
|
|
4
|
+
* arrays, and every operation is tested without a browser (scripts/test-tree-store.mts).
|
|
5
|
+
*
|
|
6
|
+
* Checking a box, the frequent change, updates counts along one path. Adding, renaming, and removing items count
|
|
7
|
+
* the whole tree again, which takes a few milliseconds at 100,000 items.
|
|
8
|
+
*/
|
|
9
|
+
export const TREE_DEFAULT_KEYS = { value: "id", parent: "parentId", label: "label" };
|
|
10
|
+
const read = (item, key) => item[key];
|
|
11
|
+
const defaultDisabled = (item) => read(item, "disabled") === true || read(item, "isDisabled") === true;
|
|
12
|
+
const defaultChecked = (item) => read(item, "checked") === true || read(item, "isChecked") === true;
|
|
13
|
+
/** The text key of a value, or `null` for no value. */
|
|
14
|
+
export function treeKey(value) {
|
|
15
|
+
if (value === null || value === undefined || value === "")
|
|
16
|
+
return null;
|
|
17
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : null;
|
|
18
|
+
}
|
|
19
|
+
let nextUid = 1;
|
|
20
|
+
/**
|
|
21
|
+
* A tree built from a flat list. Items whose parent is missing become top-level items, and a loop of parents is
|
|
22
|
+
* broken at the first item of the loop. Siblings keep the order of the list; a repeated value keeps its first item.
|
|
23
|
+
*/
|
|
24
|
+
export class TreeStore {
|
|
25
|
+
valueKey;
|
|
26
|
+
parentKey;
|
|
27
|
+
labelKey;
|
|
28
|
+
cascade;
|
|
29
|
+
valueMode;
|
|
30
|
+
isDisabled;
|
|
31
|
+
isChecked;
|
|
32
|
+
roots = [];
|
|
33
|
+
/** The visible rows in order: top-level items, and the children of every expanded item whose parents are expanded. A new array after each change. */
|
|
34
|
+
rows = [];
|
|
35
|
+
byKey = new Map();
|
|
36
|
+
/** Without `cascade`: checked nodes, nodes that are not locked, and the checked ones among them. */
|
|
37
|
+
checkedNodes = 0;
|
|
38
|
+
openNodes = 0;
|
|
39
|
+
openCheckedNodes = 0;
|
|
40
|
+
indexCache = null;
|
|
41
|
+
constructor(items = [], options = {}, value) {
|
|
42
|
+
this.valueKey = options.valueKey ?? TREE_DEFAULT_KEYS.value;
|
|
43
|
+
this.parentKey = options.parentKey ?? TREE_DEFAULT_KEYS.parent;
|
|
44
|
+
this.labelKey = options.labelKey ?? TREE_DEFAULT_KEYS.label;
|
|
45
|
+
this.cascade = options.cascade ?? true;
|
|
46
|
+
this.valueMode = options.valueMode ?? "leaves";
|
|
47
|
+
this.isDisabled = options.isItemDisabled ?? defaultDisabled;
|
|
48
|
+
this.isChecked = options.isItemChecked ?? defaultChecked;
|
|
49
|
+
this.setItems(items, value);
|
|
50
|
+
}
|
|
51
|
+
/** Items in the tree, drafts included. */
|
|
52
|
+
get size() {
|
|
53
|
+
return this.byKey.size;
|
|
54
|
+
}
|
|
55
|
+
/** The node of a value. */
|
|
56
|
+
get(value) {
|
|
57
|
+
const key = treeKey(value);
|
|
58
|
+
return key === null ? undefined : this.byKey.get(key);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Replaces the items. Expanded items stay expanded. The checked items come from `value` when given, otherwise from
|
|
62
|
+
* the items' own flags.
|
|
63
|
+
*/
|
|
64
|
+
setItems(items, value) {
|
|
65
|
+
const previous = this.byKey;
|
|
66
|
+
const byKey = new Map();
|
|
67
|
+
const ordered = [];
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
const key = treeKey(read(item, this.valueKey));
|
|
70
|
+
if (key === null || byKey.has(key))
|
|
71
|
+
continue;
|
|
72
|
+
const node = this.createNode(item, key);
|
|
73
|
+
node.expanded = previous.get(key)?.expanded ?? false;
|
|
74
|
+
byKey.set(key, node);
|
|
75
|
+
ordered.push(node);
|
|
76
|
+
}
|
|
77
|
+
const roots = [];
|
|
78
|
+
for (const node of ordered) {
|
|
79
|
+
const parentKey = treeKey(read(node.item, this.parentKey));
|
|
80
|
+
const parent = parentKey === null ? undefined : byKey.get(parentKey);
|
|
81
|
+
if (parent && parent !== node) {
|
|
82
|
+
node.parent = parent;
|
|
83
|
+
parent.children.push(node);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
roots.push(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Items in a loop of parents are reached from no top-level item: cut each loop at its first item in the list.
|
|
90
|
+
const reached = new Set(preOrder(roots));
|
|
91
|
+
if (reached.size < ordered.length) {
|
|
92
|
+
const position = new Map(ordered.map((node, index) => [node, index]));
|
|
93
|
+
for (const node of ordered) {
|
|
94
|
+
if (reached.has(node))
|
|
95
|
+
continue;
|
|
96
|
+
const parent = node.parent;
|
|
97
|
+
parent.children.splice(parent.children.indexOf(node), 1);
|
|
98
|
+
node.parent = null;
|
|
99
|
+
const at = roots.findIndex(root => position.get(root) > position.get(node));
|
|
100
|
+
roots.splice(at < 0 ? roots.length : at, 0, node);
|
|
101
|
+
for (const cut of preOrder([node]))
|
|
102
|
+
reached.add(cut);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.byKey = byKey;
|
|
106
|
+
this.roots = roots;
|
|
107
|
+
const checked = value ? this.keysOf(value) : null;
|
|
108
|
+
const all = preOrder(roots);
|
|
109
|
+
for (const node of all) {
|
|
110
|
+
node.depth = node.parent ? node.parent.depth + 1 : 0;
|
|
111
|
+
node.checked = checked ? checked.has(node.key) || this.checkedByParent(node, checked) : this.isChecked(node.item);
|
|
112
|
+
}
|
|
113
|
+
this.recount(all);
|
|
114
|
+
this.rows = visibleRows(roots);
|
|
115
|
+
}
|
|
116
|
+
/** Every item in tree order, without drafts. */
|
|
117
|
+
items() {
|
|
118
|
+
return preOrder(this.roots)
|
|
119
|
+
.filter(node => !node.draft)
|
|
120
|
+
.map(node => node.item);
|
|
121
|
+
}
|
|
122
|
+
/** A node and everything under it, in tree order. */
|
|
123
|
+
subtree(node) {
|
|
124
|
+
return preOrder([node]);
|
|
125
|
+
}
|
|
126
|
+
/** The node's parents, the top-level one first. */
|
|
127
|
+
path(node) {
|
|
128
|
+
const list = [];
|
|
129
|
+
for (let parent = node.parent; parent; parent = parent.parent)
|
|
130
|
+
list.unshift(parent);
|
|
131
|
+
return list;
|
|
132
|
+
}
|
|
133
|
+
// Checking
|
|
134
|
+
/** The state of a node's box. */
|
|
135
|
+
state(node) {
|
|
136
|
+
if (!this.cascade || node.children.length === 0)
|
|
137
|
+
return node.checked ? "checked" : "unchecked";
|
|
138
|
+
if (node.checkedLeaves === 0)
|
|
139
|
+
return "unchecked";
|
|
140
|
+
return node.checkedLeaves === node.leaves ? "checked" : "mixed";
|
|
141
|
+
}
|
|
142
|
+
/** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. */
|
|
143
|
+
counts() {
|
|
144
|
+
if (!this.cascade)
|
|
145
|
+
return { checked: this.checkedNodes, total: this.byKey.size };
|
|
146
|
+
let checked = 0;
|
|
147
|
+
let total = 0;
|
|
148
|
+
for (const root of this.roots) {
|
|
149
|
+
checked += root.checkedLeaves;
|
|
150
|
+
total += root.leaves;
|
|
151
|
+
}
|
|
152
|
+
return { checked, total };
|
|
153
|
+
}
|
|
154
|
+
/** The checked values, in tree order, following `valueMode`. */
|
|
155
|
+
value() {
|
|
156
|
+
return this.checkedInOrder().map(node => node.value);
|
|
157
|
+
}
|
|
158
|
+
/** The items of the value, in the same order. */
|
|
159
|
+
valueItems() {
|
|
160
|
+
return this.checkedInOrder().map(node => node.item);
|
|
161
|
+
}
|
|
162
|
+
/** Checks exactly these values. Locks do not apply: this is the saved state, not a user's change. Returns whether anything changed. */
|
|
163
|
+
setValue(values) {
|
|
164
|
+
const keys = this.keysOf(values);
|
|
165
|
+
let changed = false;
|
|
166
|
+
const all = preOrder(this.roots);
|
|
167
|
+
for (const node of all) {
|
|
168
|
+
if (this.cascade && node.children.length > 0)
|
|
169
|
+
continue;
|
|
170
|
+
const checked = keys.has(node.key) || this.checkedByParent(node, keys);
|
|
171
|
+
if (node.checked !== checked) {
|
|
172
|
+
node.checked = checked;
|
|
173
|
+
changed = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (changed)
|
|
177
|
+
this.recount(all);
|
|
178
|
+
return changed;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* What a click on the node's box does. With `cascade` it checks every item under the node that is not locked, or
|
|
182
|
+
* unchecks them when they are all checked already. Returns whether anything changed; nothing does on a locked node.
|
|
183
|
+
*/
|
|
184
|
+
toggle(node) {
|
|
185
|
+
if (node.locked)
|
|
186
|
+
return false;
|
|
187
|
+
if (!this.cascade)
|
|
188
|
+
return this.setNodeChecked(node, !node.checked);
|
|
189
|
+
return this.setLeaves(node, node.openChecked < node.openLeaves);
|
|
190
|
+
}
|
|
191
|
+
/** Checks or unchecks a node; with `cascade`, every item under it that is not locked. Returns whether anything changed. */
|
|
192
|
+
setChecked(node, checked) {
|
|
193
|
+
if (node.locked)
|
|
194
|
+
return false;
|
|
195
|
+
return this.cascade ? this.setLeaves(node, checked) : this.setNodeChecked(node, checked);
|
|
196
|
+
}
|
|
197
|
+
/** The state of a "select all" box, over the items that are not locked; `null` when every item is locked. */
|
|
198
|
+
allState() {
|
|
199
|
+
let open = this.openNodes;
|
|
200
|
+
let checked = this.openCheckedNodes;
|
|
201
|
+
if (this.cascade) {
|
|
202
|
+
open = 0;
|
|
203
|
+
checked = 0;
|
|
204
|
+
for (const root of this.roots) {
|
|
205
|
+
open += root.openLeaves;
|
|
206
|
+
checked += root.openChecked;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (open === 0)
|
|
210
|
+
return null;
|
|
211
|
+
return checked === 0 ? "unchecked" : checked === open ? "checked" : "mixed";
|
|
212
|
+
}
|
|
213
|
+
/** What a click on a "select all" box does: checks every item that is not locked, or unchecks them all when all are checked. */
|
|
214
|
+
toggleAll() {
|
|
215
|
+
const state = this.allState();
|
|
216
|
+
return state !== null && this.checkAll(state !== "checked");
|
|
217
|
+
}
|
|
218
|
+
/** Checks or unchecks every item that is not locked. Returns whether anything changed. */
|
|
219
|
+
checkAll(checked) {
|
|
220
|
+
let changed = false;
|
|
221
|
+
if (this.cascade) {
|
|
222
|
+
for (const root of this.roots)
|
|
223
|
+
changed = this.setChecked(root, checked) || changed;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
for (const node of preOrder(this.roots))
|
|
227
|
+
changed = (!node.locked && this.setNodeChecked(node, checked)) || changed;
|
|
228
|
+
}
|
|
229
|
+
return changed;
|
|
230
|
+
}
|
|
231
|
+
// Expanding
|
|
232
|
+
/** Whether every parent of the node is expanded, so the node is a row. */
|
|
233
|
+
isVisible(node) {
|
|
234
|
+
for (let parent = node.parent; parent; parent = parent.parent)
|
|
235
|
+
if (!parent.expanded)
|
|
236
|
+
return false;
|
|
237
|
+
return this.byKey.get(node.key) === node;
|
|
238
|
+
}
|
|
239
|
+
/** The node's row index, or -1 when it is not a row. */
|
|
240
|
+
indexOf(node) {
|
|
241
|
+
if (this.indexCache?.rows !== this.rows) {
|
|
242
|
+
const map = new Map();
|
|
243
|
+
this.rows.forEach((row, index) => map.set(row, index));
|
|
244
|
+
this.indexCache = { rows: this.rows, map };
|
|
245
|
+
}
|
|
246
|
+
return this.indexCache.map.get(node) ?? -1;
|
|
247
|
+
}
|
|
248
|
+
/** Expands a node. Returns the rows it added, or `null` when no rows changed. */
|
|
249
|
+
expand(node) {
|
|
250
|
+
if (node.expanded || node.children.length === 0)
|
|
251
|
+
return null;
|
|
252
|
+
node.expanded = true;
|
|
253
|
+
const index = this.isVisible(node) ? this.indexOf(node) : -1;
|
|
254
|
+
if (index < 0)
|
|
255
|
+
return null;
|
|
256
|
+
const added = visibleRows(node.children);
|
|
257
|
+
const before = this.rows;
|
|
258
|
+
this.rows = before.slice(0, index + 1).concat(added, before.slice(index + 1));
|
|
259
|
+
return { at: index + 1, count: added.length, before };
|
|
260
|
+
}
|
|
261
|
+
/** Collapses a node. Returns the rows it removed, or `null` when no rows changed. */
|
|
262
|
+
collapse(node) {
|
|
263
|
+
if (!node.expanded)
|
|
264
|
+
return null;
|
|
265
|
+
node.expanded = false;
|
|
266
|
+
const index = this.isVisible(node) ? this.indexOf(node) : -1;
|
|
267
|
+
if (index < 0)
|
|
268
|
+
return null;
|
|
269
|
+
const before = this.rows;
|
|
270
|
+
let end = index + 1;
|
|
271
|
+
while (end < before.length && before[end].depth > node.depth)
|
|
272
|
+
end++;
|
|
273
|
+
if (end === index + 1)
|
|
274
|
+
return null;
|
|
275
|
+
this.rows = before.slice(0, index + 1).concat(before.slice(end));
|
|
276
|
+
return { at: index + 1, count: end - index - 1, before };
|
|
277
|
+
}
|
|
278
|
+
/** Expands every item, or only the first `levels` levels (1 expands the top-level items). */
|
|
279
|
+
expandAll(levels = Infinity) {
|
|
280
|
+
for (const node of this.byKey.values())
|
|
281
|
+
node.expanded = node.children.length > 0 && node.depth < levels;
|
|
282
|
+
this.rows = visibleRows(this.roots);
|
|
283
|
+
}
|
|
284
|
+
collapseAll() {
|
|
285
|
+
for (const node of this.byKey.values())
|
|
286
|
+
node.expanded = false;
|
|
287
|
+
this.rows = visibleRows(this.roots);
|
|
288
|
+
}
|
|
289
|
+
/** Expands these items and their parents, so the items and their children show. */
|
|
290
|
+
expandValues(values) {
|
|
291
|
+
for (const key of this.keysOf(values)) {
|
|
292
|
+
const node = this.byKey.get(key);
|
|
293
|
+
if (!node)
|
|
294
|
+
continue;
|
|
295
|
+
if (node.children.length > 0)
|
|
296
|
+
node.expanded = true;
|
|
297
|
+
for (const parent of this.path(node))
|
|
298
|
+
parent.expanded = true;
|
|
299
|
+
}
|
|
300
|
+
this.rows = visibleRows(this.roots);
|
|
301
|
+
}
|
|
302
|
+
/** The values of the expanded items, in tree order. */
|
|
303
|
+
expandedValues() {
|
|
304
|
+
return preOrder(this.roots)
|
|
305
|
+
.filter(node => node.expanded && node.children.length > 0)
|
|
306
|
+
.map(node => node.value);
|
|
307
|
+
}
|
|
308
|
+
/** Expands the parents of a node so it becomes a row. Returns whether rows changed. */
|
|
309
|
+
reveal(node) {
|
|
310
|
+
const closed = this.path(node).filter(parent => !parent.expanded);
|
|
311
|
+
if (closed.length === 0)
|
|
312
|
+
return false;
|
|
313
|
+
for (const parent of closed)
|
|
314
|
+
parent.expanded = true;
|
|
315
|
+
this.rows = visibleRows(this.roots);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
// Changing items
|
|
319
|
+
/**
|
|
320
|
+
* Adds an item under `parent` (at the top level for `null`), as its last child unless `index` is given, and expands
|
|
321
|
+
* the parent. A `draft` is not part of the value or the items until `update` saves it. Throws on a value in use.
|
|
322
|
+
*/
|
|
323
|
+
insert(parent, item, options = {}) {
|
|
324
|
+
const key = treeKey(read(item, this.valueKey));
|
|
325
|
+
if (key === null)
|
|
326
|
+
throw new Error("TreeStore.insert: the item has no value.");
|
|
327
|
+
if (this.byKey.has(key))
|
|
328
|
+
throw new Error(`TreeStore.insert: the value "${key}" is in use.`);
|
|
329
|
+
const node = this.createNode(item, key);
|
|
330
|
+
node.draft = options.draft ?? false;
|
|
331
|
+
node.parent = parent;
|
|
332
|
+
node.depth = parent ? parent.depth + 1 : 0;
|
|
333
|
+
// An item added under a checked leaf starts checked, so its parent stays checked.
|
|
334
|
+
node.checked = Boolean(this.cascade && parent && parent.children.length === 0 && parent.checked);
|
|
335
|
+
const siblings = parent ? parent.children : this.roots;
|
|
336
|
+
siblings.splice(Math.max(0, Math.min(options.index ?? siblings.length, siblings.length)), 0, node);
|
|
337
|
+
this.byKey.set(key, node);
|
|
338
|
+
if (parent)
|
|
339
|
+
parent.expanded = true;
|
|
340
|
+
this.recount(preOrder(this.roots));
|
|
341
|
+
this.rows = visibleRows(this.roots);
|
|
342
|
+
return node;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Replaces a node's item, e.g. after a rename, or once a draft is saved and the server gave it an id. The value can
|
|
346
|
+
* change; the parent cannot. Throws on a value another item uses.
|
|
347
|
+
*/
|
|
348
|
+
update(node, item, options = {}) {
|
|
349
|
+
const key = treeKey(read(item, this.valueKey));
|
|
350
|
+
if (key === null)
|
|
351
|
+
throw new Error("TreeStore.update: the item has no value.");
|
|
352
|
+
const other = this.byKey.get(key);
|
|
353
|
+
if (other && other !== node)
|
|
354
|
+
throw new Error(`TreeStore.update: the value "${key}" is in use.`);
|
|
355
|
+
this.byKey.delete(node.key);
|
|
356
|
+
node.key = key;
|
|
357
|
+
node.value = read(item, this.valueKey);
|
|
358
|
+
node.item = item;
|
|
359
|
+
node.label = labelOf(item, this.labelKey);
|
|
360
|
+
node.draft = options.draft ?? false;
|
|
361
|
+
this.byKey.set(key, node);
|
|
362
|
+
const disabled = this.isDisabled(item);
|
|
363
|
+
if (disabled !== node.disabled) {
|
|
364
|
+
node.disabled = disabled;
|
|
365
|
+
this.recount(preOrder(this.roots));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Removes a node and everything under it. Returns the removed nodes in tree order. */
|
|
369
|
+
remove(node) {
|
|
370
|
+
const parent = node.parent;
|
|
371
|
+
const siblings = parent ? parent.children : this.roots;
|
|
372
|
+
const index = siblings.indexOf(node);
|
|
373
|
+
if (index < 0)
|
|
374
|
+
return [];
|
|
375
|
+
const removed = preOrder([node]);
|
|
376
|
+
// A parent that loses its last child becomes a leaf, checked when all of its items were.
|
|
377
|
+
if (parent && this.cascade && siblings.length === 1)
|
|
378
|
+
parent.checked = this.state(parent) === "checked";
|
|
379
|
+
siblings.splice(index, 1);
|
|
380
|
+
if (parent && parent.children.length === 0)
|
|
381
|
+
parent.expanded = false;
|
|
382
|
+
for (const item of removed)
|
|
383
|
+
this.byKey.delete(item.key);
|
|
384
|
+
node.parent = null;
|
|
385
|
+
this.recount(preOrder(this.roots));
|
|
386
|
+
this.rows = visibleRows(this.roots);
|
|
387
|
+
return removed;
|
|
388
|
+
}
|
|
389
|
+
// Internals
|
|
390
|
+
createNode(item, key) {
|
|
391
|
+
return {
|
|
392
|
+
uid: nextUid++,
|
|
393
|
+
key,
|
|
394
|
+
value: read(item, this.valueKey),
|
|
395
|
+
item,
|
|
396
|
+
label: labelOf(item, this.labelKey),
|
|
397
|
+
parent: null,
|
|
398
|
+
children: [],
|
|
399
|
+
depth: 0,
|
|
400
|
+
position: 0,
|
|
401
|
+
disabled: this.isDisabled(item),
|
|
402
|
+
disabledPath: false,
|
|
403
|
+
locked: false,
|
|
404
|
+
expanded: false,
|
|
405
|
+
checked: false,
|
|
406
|
+
leaves: 1,
|
|
407
|
+
checkedLeaves: 0,
|
|
408
|
+
openLeaves: 0,
|
|
409
|
+
openChecked: 0,
|
|
410
|
+
draft: false,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
keysOf(values) {
|
|
414
|
+
const keys = new Set();
|
|
415
|
+
for (const value of values) {
|
|
416
|
+
const key = treeKey(value);
|
|
417
|
+
if (key !== null)
|
|
418
|
+
keys.add(key);
|
|
419
|
+
}
|
|
420
|
+
return keys;
|
|
421
|
+
}
|
|
422
|
+
/** With `valueMode: "checked"`, a parent in the value stands for all of its items. */
|
|
423
|
+
checkedByParent(node, keys) {
|
|
424
|
+
if (!this.cascade || this.valueMode !== "checked")
|
|
425
|
+
return false;
|
|
426
|
+
for (let parent = node.parent; parent; parent = parent.parent)
|
|
427
|
+
if (keys.has(parent.key))
|
|
428
|
+
return true;
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
/** Locks and counts for every node, given in tree order: parents pass locks down, children pass counts up. */
|
|
432
|
+
recount(ordered) {
|
|
433
|
+
this.roots.forEach((root, index) => (root.position = index));
|
|
434
|
+
for (const node of ordered) {
|
|
435
|
+
node.children.forEach((child, index) => (child.position = index));
|
|
436
|
+
node.disabledPath = node.disabled || Boolean(node.parent?.disabledPath);
|
|
437
|
+
node.locked = node.disabledPath;
|
|
438
|
+
}
|
|
439
|
+
let checkedNodes = 0;
|
|
440
|
+
let openNodes = 0;
|
|
441
|
+
let openCheckedNodes = 0;
|
|
442
|
+
for (let index = ordered.length - 1; index >= 0; index--) {
|
|
443
|
+
const node = ordered[index];
|
|
444
|
+
if (node.children.length === 0) {
|
|
445
|
+
node.leaves = 1;
|
|
446
|
+
node.checkedLeaves = node.checked ? 1 : 0;
|
|
447
|
+
node.openLeaves = node.locked ? 0 : 1;
|
|
448
|
+
node.openChecked = node.locked ? 0 : node.checkedLeaves;
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
let leaves = 0;
|
|
452
|
+
let checked = 0;
|
|
453
|
+
let openLeaves = 0;
|
|
454
|
+
let openChecked = 0;
|
|
455
|
+
let allLocked = true;
|
|
456
|
+
for (const child of node.children) {
|
|
457
|
+
leaves += child.leaves;
|
|
458
|
+
checked += child.checkedLeaves;
|
|
459
|
+
openLeaves += child.openLeaves;
|
|
460
|
+
openChecked += child.openChecked;
|
|
461
|
+
allLocked &&= child.locked;
|
|
462
|
+
}
|
|
463
|
+
node.leaves = leaves;
|
|
464
|
+
node.checkedLeaves = checked;
|
|
465
|
+
node.openLeaves = openLeaves;
|
|
466
|
+
node.openChecked = openChecked;
|
|
467
|
+
// A parent's box follows its items only with cascade; without it, a parent stays free.
|
|
468
|
+
if (this.cascade)
|
|
469
|
+
node.locked ||= allLocked;
|
|
470
|
+
}
|
|
471
|
+
if (node.checked)
|
|
472
|
+
checkedNodes++;
|
|
473
|
+
if (!node.locked) {
|
|
474
|
+
openNodes++;
|
|
475
|
+
if (node.checked)
|
|
476
|
+
openCheckedNodes++;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
this.checkedNodes = checkedNodes;
|
|
480
|
+
this.openNodes = openNodes;
|
|
481
|
+
this.openCheckedNodes = openCheckedNodes;
|
|
482
|
+
}
|
|
483
|
+
/** Sets every open leaf under a node (the node itself for a leaf), updating counts up the path of each leaf that changes. */
|
|
484
|
+
setLeaves(node, checked) {
|
|
485
|
+
let changed = false;
|
|
486
|
+
const stack = [node];
|
|
487
|
+
while (stack.length > 0) {
|
|
488
|
+
const current = stack.pop();
|
|
489
|
+
// A subtree that is locked, or already all checked or all unchecked, has nothing to change.
|
|
490
|
+
if (current.locked || current.openChecked === (checked ? current.openLeaves : 0))
|
|
491
|
+
continue;
|
|
492
|
+
if (current.children.length > 0) {
|
|
493
|
+
for (const child of current.children)
|
|
494
|
+
stack.push(child);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
current.checked = checked;
|
|
498
|
+
const step = checked ? 1 : -1;
|
|
499
|
+
for (let path = current; path; path = path.parent) {
|
|
500
|
+
path.checkedLeaves += step;
|
|
501
|
+
path.openChecked += step;
|
|
502
|
+
}
|
|
503
|
+
changed = true;
|
|
504
|
+
}
|
|
505
|
+
return changed;
|
|
506
|
+
}
|
|
507
|
+
setNodeChecked(node, checked) {
|
|
508
|
+
if (node.checked === checked)
|
|
509
|
+
return false;
|
|
510
|
+
node.checked = checked;
|
|
511
|
+
const step = checked ? 1 : -1;
|
|
512
|
+
this.checkedNodes += step;
|
|
513
|
+
if (!node.locked)
|
|
514
|
+
this.openCheckedNodes += step;
|
|
515
|
+
if (node.children.length === 0) {
|
|
516
|
+
for (let path = node; path; path = path.parent) {
|
|
517
|
+
path.checkedLeaves += step;
|
|
518
|
+
if (!node.locked)
|
|
519
|
+
path.openChecked += step;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
checkedInOrder() {
|
|
525
|
+
const list = [];
|
|
526
|
+
const mode = this.cascade ? this.valueMode : null;
|
|
527
|
+
const stack = [...this.roots].reverse();
|
|
528
|
+
while (stack.length > 0) {
|
|
529
|
+
const node = stack.pop();
|
|
530
|
+
if (node.draft)
|
|
531
|
+
continue;
|
|
532
|
+
if (mode === null) {
|
|
533
|
+
if (node.checked)
|
|
534
|
+
list.push(node);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// A subtree without a checked leaf adds nothing.
|
|
538
|
+
if (node.checkedLeaves === 0)
|
|
539
|
+
continue;
|
|
540
|
+
if (node.children.length === 0 || mode === "any" || (mode === "checked" && node.checkedLeaves === node.leaves))
|
|
541
|
+
list.push(node);
|
|
542
|
+
}
|
|
543
|
+
for (let index = node.children.length - 1; index >= 0; index--)
|
|
544
|
+
stack.push(node.children[index]);
|
|
545
|
+
}
|
|
546
|
+
return list;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/** Nodes in tree order: each node, then its children. No recursion, so even a chain 100,000 levels deep works. */
|
|
550
|
+
export function preOrder(nodes) {
|
|
551
|
+
const list = [];
|
|
552
|
+
const stack = [...nodes].reverse();
|
|
553
|
+
while (stack.length > 0) {
|
|
554
|
+
const node = stack.pop();
|
|
555
|
+
list.push(node);
|
|
556
|
+
for (let index = node.children.length - 1; index >= 0; index--)
|
|
557
|
+
stack.push(node.children[index]);
|
|
558
|
+
}
|
|
559
|
+
return list;
|
|
560
|
+
}
|
|
561
|
+
/** The rows these nodes show: each node, then the rows of its children when it is expanded. */
|
|
562
|
+
function visibleRows(nodes) {
|
|
563
|
+
const list = [];
|
|
564
|
+
const stack = [...nodes].reverse();
|
|
565
|
+
while (stack.length > 0) {
|
|
566
|
+
const node = stack.pop();
|
|
567
|
+
list.push(node);
|
|
568
|
+
if (node.expanded)
|
|
569
|
+
for (let index = node.children.length - 1; index >= 0; index--)
|
|
570
|
+
stack.push(node.children[index]);
|
|
571
|
+
}
|
|
572
|
+
return list;
|
|
573
|
+
}
|
|
574
|
+
function labelOf(item, key) {
|
|
575
|
+
const label = read(item, key);
|
|
576
|
+
return label === null || label === undefined ? "" : String(label);
|
|
577
|
+
}
|