@hypericon/mico 0.3.10 → 0.3.12
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/README.md +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -1
- package/dist/components/pagination/Pagination.js +2 -2
- package/dist/components/select/Select.d.ts +44 -0
- package/dist/components/select/Select.js +114 -0
- package/dist/components/select/SelectOptionsPanel.d.ts +21 -0
- package/dist/components/select/SelectOptionsPanel.js +174 -0
- package/dist/components/select/constants.d.ts +2 -0
- package/dist/components/select/constants.js +5 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +19 -0
- package/dist/components/select/select-options-manager.d.ts +39 -0
- package/dist/components/select/select-options-manager.js +67 -0
- package/dist/components/tooltip/tooltip-function.d.ts +0 -0
- package/dist/components/tooltip/tooltip-function.js +204 -0
- package/dist/components/tree/Tree.d.ts +76 -0
- package/dist/components/tree/Tree.js +210 -0
- package/dist/components/tree/index.d.ts +3 -0
- package/dist/components/tree/index.js +18 -0
- package/dist/components/tree/tree-node.interface.d.ts +7 -0
- package/dist/components/tree/tree-node.interface.js +2 -0
- package/dist/components/tree/tree-utils.d.ts +11 -0
- package/dist/components/tree/tree-utils.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/components/index.js
CHANGED
|
@@ -37,7 +37,7 @@ exports.Pagination = {
|
|
|
37
37
|
}, [
|
|
38
38
|
pagination_settings_1.PAGINATION_DEFAULT_SETTINGS.iconComponents.first
|
|
39
39
|
? (0, mithril_1.default)(pagination_settings_1.PAGINATION_DEFAULT_SETTINGS.iconComponents.first)
|
|
40
|
-
: (0, mithril_1.default)(icon_1.Icon, { fas: "
|
|
40
|
+
: (0, mithril_1.default)(icon_1.Icon, { fas: "angles-left" }),
|
|
41
41
|
]) : [],
|
|
42
42
|
(attrs.prev !== undefined) ? (0, mithril_1.default)("button.mico-pagination-button", {
|
|
43
43
|
disabled: attrs.isAtStart,
|
|
@@ -64,7 +64,7 @@ exports.Pagination = {
|
|
|
64
64
|
}, [
|
|
65
65
|
pagination_settings_1.PAGINATION_DEFAULT_SETTINGS.iconComponents.last
|
|
66
66
|
? (0, mithril_1.default)(pagination_settings_1.PAGINATION_DEFAULT_SETTINGS.iconComponents.last)
|
|
67
|
-
: (0, mithril_1.default)(icon_1.Icon, { fas: "
|
|
67
|
+
: (0, mithril_1.default)(icon_1.Icon, { fas: "angles-right" }),
|
|
68
68
|
]) : [],
|
|
69
69
|
(layout === "middle" || layout === "left") ? (0, mithril_1.default)("", { style: "flex: 1;" }) : [],
|
|
70
70
|
]);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import m from "mithril";
|
|
2
|
+
type SelectOption = m.Vnode<{
|
|
3
|
+
/** The "value" of the option */
|
|
4
|
+
value: string;
|
|
5
|
+
/** Optionally disable selection of the option */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** The displayed value as text, to enable the user to select the value by label with the keyboard */
|
|
8
|
+
text?: string;
|
|
9
|
+
}, any>;
|
|
10
|
+
/**
|
|
11
|
+
* A dropdown select box that takes Mithril Vnodes as options
|
|
12
|
+
*/
|
|
13
|
+
export declare const Select: m.Component<{
|
|
14
|
+
/**
|
|
15
|
+
* The list of vnodes to display as options. Attrs:
|
|
16
|
+
* - `value` (required)
|
|
17
|
+
* - `disabled` (optional)
|
|
18
|
+
* - `text` (optional)
|
|
19
|
+
*/
|
|
20
|
+
options: SelectOption[];
|
|
21
|
+
/** The selected value to highlight */
|
|
22
|
+
value: string;
|
|
23
|
+
/** Function called with the option value when an option is selected */
|
|
24
|
+
oninput?: (value: string) => any;
|
|
25
|
+
onfocus?: (ev: FocusEvent) => any;
|
|
26
|
+
onblur?: (ev: FocusEvent) => any;
|
|
27
|
+
/** Space-separated CSS classes to apply to the component */
|
|
28
|
+
class?: string;
|
|
29
|
+
/** CSS styles to apply to the component */
|
|
30
|
+
style?: string;
|
|
31
|
+
placeholder?: string | m.Vnode<any, any>;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
multiple?: boolean;
|
|
34
|
+
size?: number;
|
|
35
|
+
/** Optionally specify an ID in the parent, otherwise one is created internally */
|
|
36
|
+
id?: string;
|
|
37
|
+
}, {
|
|
38
|
+
id: string;
|
|
39
|
+
/** The last position of the select within the viewport */
|
|
40
|
+
lastViewportRect: DOMRect;
|
|
41
|
+
/** The current value of the disabled attr */
|
|
42
|
+
disabled: boolean | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Select = void 0;
|
|
7
|
+
const mithril_1 = __importDefault(require("mithril"));
|
|
8
|
+
const select_options_manager_1 = require("./select-options-manager");
|
|
9
|
+
const optsPanelMgr = new select_options_manager_1.SelectOptionsManager();
|
|
10
|
+
/**
|
|
11
|
+
* A dropdown select box that takes Mithril Vnodes as options
|
|
12
|
+
*/
|
|
13
|
+
exports.Select = {
|
|
14
|
+
oninit({ attrs }) {
|
|
15
|
+
this.id = attrs.id || `select-${Math.random().toString().replace(".", "")}`;
|
|
16
|
+
this.lastViewportRect = new DOMRect();
|
|
17
|
+
this.disabled = this.disabled;
|
|
18
|
+
},
|
|
19
|
+
onupdate({ dom }) {
|
|
20
|
+
var _a;
|
|
21
|
+
// Store the last known position of the component within the viewport
|
|
22
|
+
// to determine if the options should open upwards or downwards
|
|
23
|
+
const rect = dom.getBoundingClientRect();
|
|
24
|
+
rect.y += (((_a = dom.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.pageYOffset) || 0);
|
|
25
|
+
// const top = dom.getBoundingClientRect().top + (dom.ownerDocument.defaultView?.pageYOffset || 0);
|
|
26
|
+
this.lastViewportRect = rect;
|
|
27
|
+
optsPanelMgr.move(this.id, this.lastViewportRect);
|
|
28
|
+
},
|
|
29
|
+
view({ attrs }) {
|
|
30
|
+
var _a;
|
|
31
|
+
this.disabled = attrs.disabled;
|
|
32
|
+
const select = (value) => {
|
|
33
|
+
var _a;
|
|
34
|
+
if (this.disabled)
|
|
35
|
+
return;
|
|
36
|
+
(_a = attrs.oninput) === null || _a === void 0 ? void 0 : _a.call(attrs, value);
|
|
37
|
+
// this.isOpen = false;
|
|
38
|
+
// attrs.onclose?.();
|
|
39
|
+
optsPanelMgr.dismiss();
|
|
40
|
+
};
|
|
41
|
+
const open = (options, value) => {
|
|
42
|
+
if (this.disabled)
|
|
43
|
+
return;
|
|
44
|
+
optsPanelMgr.open({
|
|
45
|
+
id: this.id,
|
|
46
|
+
options: options,
|
|
47
|
+
selectRect: this.lastViewportRect,
|
|
48
|
+
selectedIndex: options.findIndex(o => o.attrs.value === (value || attrs.value)),
|
|
49
|
+
select: (value) => {
|
|
50
|
+
select(value);
|
|
51
|
+
},
|
|
52
|
+
onDismiss: () => {
|
|
53
|
+
var _a;
|
|
54
|
+
(_a = document.getElementById(this.id)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const dismiss = () => {
|
|
59
|
+
// this.isOpen = false;
|
|
60
|
+
optsPanelMgr.dismiss();
|
|
61
|
+
// attrs.onclose?.();
|
|
62
|
+
};
|
|
63
|
+
// The main "select box" div
|
|
64
|
+
return (0, mithril_1.default)(".input.no-select", {
|
|
65
|
+
id: this.id,
|
|
66
|
+
tabindex: 0,
|
|
67
|
+
style: `height: 2rem; ${attrs.style || ""} padding-top: 0; padding-bottom: 0; position: relative;`,
|
|
68
|
+
class: `${attrs.class || ""} flex items-center ${attrs.disabled ? "disabled" : ""} ${attrs.disabled ? "cursor-auto" : "cursor-pointer"}`,
|
|
69
|
+
onclick: (ev) => {
|
|
70
|
+
// ev.stopPropagation();
|
|
71
|
+
if (!optsPanelMgr.isOpen(this.id))
|
|
72
|
+
open(attrs.options, attrs.value);
|
|
73
|
+
// ? this.dismiss()
|
|
74
|
+
// :
|
|
75
|
+
},
|
|
76
|
+
onkeydown: (ev) => {
|
|
77
|
+
if (ev.code === "Enter") {
|
|
78
|
+
open(attrs.options, attrs.value);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
onfocus: (ev) => { var _a; return (_a = attrs.onfocus) === null || _a === void 0 ? void 0 : _a.call(attrs, ev); },
|
|
82
|
+
onblur: (ev) => {
|
|
83
|
+
var _a;
|
|
84
|
+
if (!optsPanelMgr.isOpen(this.id)) {
|
|
85
|
+
(_a = attrs.onblur) === null || _a === void 0 ? void 0 : _a.call(attrs, ev);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, [
|
|
89
|
+
// The selected option within the select box div
|
|
90
|
+
(0, mithril_1.default)(".flex-1.overflow-x-hidden", {}, [
|
|
91
|
+
(0, mithril_1.default)(".overflow-x-hidden", {
|
|
92
|
+
key: attrs.value,
|
|
93
|
+
}, [
|
|
94
|
+
(_a = attrs.options.find(o => o.attrs.value === attrs.value)) !== null && _a !== void 0 ? _a : ((attrs.placeholder && typeof attrs.placeholder === "string")
|
|
95
|
+
? (0, mithril_1.default)("span.hint", attrs.placeholder)
|
|
96
|
+
: attrs.placeholder),
|
|
97
|
+
]),
|
|
98
|
+
]),
|
|
99
|
+
// Open/close down/up arrow in the input box
|
|
100
|
+
(0, mithril_1.default)(".ml-1", {
|
|
101
|
+
class: this.disabled ? "text-gray-500" : "",
|
|
102
|
+
}, [
|
|
103
|
+
// optsPanelSvc.isOpen(this.id)
|
|
104
|
+
// ? m(".h-full.flex.items-center", { key: "up" }, m("i.fas.fa-caret-up"))
|
|
105
|
+
// : m(".h-full.flex.items-center", { key: "down" }, m("i.fas.fa-caret-down")),
|
|
106
|
+
(0, mithril_1.default)(".h-full.flex.items-center", [
|
|
107
|
+
optsPanelMgr.isOpen(this.id)
|
|
108
|
+
? (0, mithril_1.default)(`i.fas.fa-caret-up`)
|
|
109
|
+
: (0, mithril_1.default)(`i.fas.fa-caret-down`),
|
|
110
|
+
]),
|
|
111
|
+
]),
|
|
112
|
+
]);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import m from "mithril";
|
|
3
|
+
import { Subscription } from "rxjs";
|
|
4
|
+
export declare const SelectOptionsPanel: m.Component<{}, {
|
|
5
|
+
subs: Subscription;
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
/** If the options panel should open upwards */
|
|
8
|
+
isReversed: boolean;
|
|
9
|
+
/** If the select input is narrow and near the right viewport edge, anchor the options panel on the right edge */
|
|
10
|
+
isAnchoredRight: boolean;
|
|
11
|
+
highlightedIndex: number;
|
|
12
|
+
ensureVisible: (index: number, up: boolean) => void;
|
|
13
|
+
/** A current string "searched for" when the user types characters when the panel is open */
|
|
14
|
+
textSearch: string;
|
|
15
|
+
/** A timer used to clear the text search */
|
|
16
|
+
keyIdleTimer: NodeJS.Timeout | undefined;
|
|
17
|
+
/** On pressing a letter key, update the text search and possibly select a new option */
|
|
18
|
+
handleTextSearch: (ev: KeyboardEvent) => void;
|
|
19
|
+
/** Clear the text search string and idle timer */
|
|
20
|
+
clearTextSearch: () => void;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SelectOptionsPanel = void 0;
|
|
7
|
+
const mithril_1 = __importDefault(require("mithril"));
|
|
8
|
+
const rxjs_1 = require("rxjs");
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const select_options_manager_1 = require("./select-options-manager");
|
|
11
|
+
// const logger = getLogger("SelectOptionsPanel");
|
|
12
|
+
const DEBUG = false;
|
|
13
|
+
const optsPanelMgr = new select_options_manager_1.SelectOptionsManager();
|
|
14
|
+
const MIN_OPTIONS_PANEL_WIDTH = 200;
|
|
15
|
+
const OPTIONS_PANEL_ID = "select-options-panel";
|
|
16
|
+
const TEXT_IDLE_TIMEOUT = 1000;
|
|
17
|
+
exports.SelectOptionsPanel = {
|
|
18
|
+
oninit() {
|
|
19
|
+
this.subs = new rxjs_1.Subscription();
|
|
20
|
+
this.isOpen = false;
|
|
21
|
+
this.isReversed = false;
|
|
22
|
+
this.isAnchoredRight = false;
|
|
23
|
+
this.highlightedIndex = 0;
|
|
24
|
+
// this.highlightIndex = (index) => {
|
|
25
|
+
// this.highlightedIndex = index;
|
|
26
|
+
// this.ensureVisible(index, false);
|
|
27
|
+
// };
|
|
28
|
+
this.ensureVisible = (index, up) => {
|
|
29
|
+
const panel = document.getElementById(OPTIONS_PANEL_ID);
|
|
30
|
+
const container = panel === null || panel === void 0 ? void 0 : panel.children.item(0);
|
|
31
|
+
const optionElems = container === null || container === void 0 ? void 0 : container.children;
|
|
32
|
+
const highlightedElem = optionElems === null || optionElems === void 0 ? void 0 : optionElems.item(index);
|
|
33
|
+
if (up) {
|
|
34
|
+
if (panel.getBoundingClientRect().top > highlightedElem.getBoundingClientRect().top) {
|
|
35
|
+
highlightedElem === null || highlightedElem === void 0 ? void 0 : highlightedElem.scrollIntoView(true);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (panel.getBoundingClientRect().bottom < highlightedElem.getBoundingClientRect().bottom) {
|
|
40
|
+
highlightedElem === null || highlightedElem === void 0 ? void 0 : highlightedElem.scrollIntoView(false);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// const highlightedRect = highlightedElem?.getBoundingClientRect();
|
|
44
|
+
// if (highlightedRect) {
|
|
45
|
+
// logger.debug("ensure visible rect:", highlightedRect);
|
|
46
|
+
// panel?.scr
|
|
47
|
+
// }
|
|
48
|
+
};
|
|
49
|
+
this.subs.add(optsPanelMgr.panel$.subscribe(panelDef => {
|
|
50
|
+
if (panelDef) {
|
|
51
|
+
// If the available space is less than the approximate size of the options panel, open upwards instead
|
|
52
|
+
this.isReversed = (window.innerHeight - panelDef.selectRect.bottom) < Math.min((panelDef.options.length * constants_1.OPTION_APPROX_HEIGHT), constants_1.MAX_OPTION_PANEL_HEIGHT);
|
|
53
|
+
// TODO: calculate is anchored right
|
|
54
|
+
if (!this.isOpen) {
|
|
55
|
+
DEBUG && console.log("opening select options panel");
|
|
56
|
+
const index = panelDef.selectedIndex;
|
|
57
|
+
this.highlightedIndex = (index !== undefined && index !== -1) ? index : 0;
|
|
58
|
+
this.isOpen = true;
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
this.ensureVisible(this.highlightedIndex, false);
|
|
61
|
+
}, 100);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.isOpen = false;
|
|
66
|
+
}
|
|
67
|
+
mithril_1.default.redraw();
|
|
68
|
+
}));
|
|
69
|
+
this.textSearch = "";
|
|
70
|
+
this.handleTextSearch = (ev) => {
|
|
71
|
+
if (optsPanelMgr.panel === undefined)
|
|
72
|
+
return;
|
|
73
|
+
const key = ev.key.toLowerCase();
|
|
74
|
+
this.textSearch += key;
|
|
75
|
+
// If the idle timeout elapses, clear the current text search
|
|
76
|
+
if (this.keyIdleTimer) {
|
|
77
|
+
clearTimeout(this.keyIdleTimer);
|
|
78
|
+
}
|
|
79
|
+
this.keyIdleTimer = setTimeout(() => {
|
|
80
|
+
this.clearTextSearch();
|
|
81
|
+
}, TEXT_IDLE_TIMEOUT);
|
|
82
|
+
// Find the index of the option whose text search string starts with the current search term
|
|
83
|
+
const index = optsPanelMgr.panel.options.findIndex(o => {
|
|
84
|
+
return o.attrs.text && o.attrs.text.toLowerCase().startsWith(this.textSearch);
|
|
85
|
+
});
|
|
86
|
+
if (index >= 0 && index <= optsPanelMgr.panel.options.length - 1) {
|
|
87
|
+
const up = this.highlightedIndex > index;
|
|
88
|
+
this.highlightedIndex = index;
|
|
89
|
+
this.ensureVisible(this.highlightedIndex, up);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.clearTextSearch = () => {
|
|
93
|
+
if (this.keyIdleTimer) {
|
|
94
|
+
clearTimeout(this.keyIdleTimer);
|
|
95
|
+
}
|
|
96
|
+
this.keyIdleTimer = undefined;
|
|
97
|
+
this.textSearch = "";
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
onremove() {
|
|
101
|
+
this.subs.unsubscribe();
|
|
102
|
+
},
|
|
103
|
+
onupdate() {
|
|
104
|
+
var _a;
|
|
105
|
+
(_a = document.getElementById(OPTIONS_PANEL_ID)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
106
|
+
},
|
|
107
|
+
view() {
|
|
108
|
+
if (optsPanelMgr.panel === undefined || !this.isOpen)
|
|
109
|
+
return;
|
|
110
|
+
const panel = optsPanelMgr.panel;
|
|
111
|
+
const rect = panel.selectRect;
|
|
112
|
+
const width = Math.max(rect.width, MIN_OPTIONS_PANEL_WIDTH);
|
|
113
|
+
DEBUG && console.log("Drawing select options panel for select rect:", rect);
|
|
114
|
+
return (0, mithril_1.default)(".bg-white.text-dark.dark:bg-black.dark:text-white.shadow-lg.border.border-grey-500", {
|
|
115
|
+
id: OPTIONS_PANEL_ID,
|
|
116
|
+
tabindex: -1,
|
|
117
|
+
style: `position: fixed; left: ${rect.left}px; width: ${width}px; ${this.isReversed ? `bottom: ${document.documentElement.clientHeight - rect.top}px;` : `top: ${rect.bottom}px;`}
|
|
118
|
+
min-width: ${MIN_OPTIONS_PANEL_WIDTH}px; max-height: ${constants_1.MAX_OPTION_PANEL_HEIGHT}px; overflow-y: auto; z-index: 100; outline: none;`,
|
|
119
|
+
onkeydown: (ev) => {
|
|
120
|
+
if (ev.code === "ArrowUp") {
|
|
121
|
+
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
122
|
+
this.clearTextSearch();
|
|
123
|
+
this.ensureVisible(this.highlightedIndex, true);
|
|
124
|
+
ev.preventDefault(); // Navigating items in the list scrolls the page
|
|
125
|
+
}
|
|
126
|
+
else if (ev.code === "ArrowDown") {
|
|
127
|
+
this.highlightedIndex = Math.min(this.highlightedIndex + 1, panel.options.length - 1);
|
|
128
|
+
this.clearTextSearch();
|
|
129
|
+
this.ensureVisible(this.highlightedIndex, false);
|
|
130
|
+
ev.preventDefault(); // Navigating items in the list scrolls the page
|
|
131
|
+
}
|
|
132
|
+
else if (ev.code === "Enter") {
|
|
133
|
+
optsPanelMgr.selectIndex(this.highlightedIndex);
|
|
134
|
+
}
|
|
135
|
+
else if (ev.code === "Escape") {
|
|
136
|
+
optsPanelMgr.dismiss();
|
|
137
|
+
ev.stopPropagation();
|
|
138
|
+
}
|
|
139
|
+
else if (/^[a-z]$/i.test(ev.key)) { // a-z
|
|
140
|
+
this.handleTextSearch(ev);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
onfocusout: (ev) => {
|
|
144
|
+
optsPanelMgr.dismiss();
|
|
145
|
+
},
|
|
146
|
+
}, [
|
|
147
|
+
// The container of the option buttons, may be larger than the displayed panel
|
|
148
|
+
(0, mithril_1.default)("", {
|
|
149
|
+
style: ``,
|
|
150
|
+
class: `flex flex-col items-stretch`,
|
|
151
|
+
}, [
|
|
152
|
+
...optsPanelMgr.panel.options.map((optionVnode, optionIndex) => {
|
|
153
|
+
const value = optionVnode.attrs.value;
|
|
154
|
+
const disabled = optionVnode.attrs.disabled === true;
|
|
155
|
+
// An option value button
|
|
156
|
+
return (0, mithril_1.default)(`button`, {
|
|
157
|
+
key: value,
|
|
158
|
+
disabled,
|
|
159
|
+
class: `px-2 py-1 flex items-start ${this.highlightedIndex === optionIndex ? "bg-gray-200 dark:bg-black-light" : ""}`,
|
|
160
|
+
onclick: (ev) => {
|
|
161
|
+
ev.stopPropagation();
|
|
162
|
+
if (disabled)
|
|
163
|
+
return;
|
|
164
|
+
// this.select(value);
|
|
165
|
+
optsPanelMgr.selectIndex(optionIndex);
|
|
166
|
+
},
|
|
167
|
+
onmousedown: (ev) => ev.preventDefault(),
|
|
168
|
+
onmouseover: (ev) => this.highlightedIndex = optionIndex,
|
|
169
|
+
}, optionVnode);
|
|
170
|
+
}),
|
|
171
|
+
]),
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Select"), exports);
|
|
18
|
+
__exportStar(require("./SelectOptionsPanel"), exports);
|
|
19
|
+
// export * from "./select-options-panel-manager";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import m from "mithril";
|
|
2
|
+
export type SelectOptionsPanelDef = {
|
|
3
|
+
/** */
|
|
4
|
+
id: string;
|
|
5
|
+
/** The bounding client rect of the select input element within the viewport */
|
|
6
|
+
selectRect: DOMRect;
|
|
7
|
+
/** The list of vnodes to display as options */
|
|
8
|
+
options: m.Vnode<{
|
|
9
|
+
/** The "value" of the option */
|
|
10
|
+
value: string;
|
|
11
|
+
/** Optionally disable selection of the option */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** The displayed value as text, to enable the user to select the value by label with the keyboard */
|
|
14
|
+
text?: string;
|
|
15
|
+
}, any>[];
|
|
16
|
+
/** */
|
|
17
|
+
selectedIndex: number;
|
|
18
|
+
/** Function to notify the select input of the slected value in the options panel */
|
|
19
|
+
select: (value: string) => void;
|
|
20
|
+
/** Function called after the options panel has been dismissed */
|
|
21
|
+
onDismiss?: () => void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Manager providing the link between custom select box input components
|
|
25
|
+
* and the layout-level select options panel
|
|
26
|
+
*/
|
|
27
|
+
export declare class SelectOptionsManager {
|
|
28
|
+
private _panel;
|
|
29
|
+
panel$: import("rxjs").Observable<SelectOptionsPanelDef | undefined>;
|
|
30
|
+
get panel(): SelectOptionsPanelDef | undefined;
|
|
31
|
+
private lastPanel;
|
|
32
|
+
constructor();
|
|
33
|
+
open(optionsPanel: SelectOptionsPanelDef): void;
|
|
34
|
+
move(id: string, selectRect: DOMRect): void;
|
|
35
|
+
isOpen(selectId: string): boolean;
|
|
36
|
+
selectIndex(index: number): void;
|
|
37
|
+
selectValue(value: any): void;
|
|
38
|
+
dismiss(): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SelectOptionsManager = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const REOPEN_TIMEOUT = 100;
|
|
6
|
+
/**
|
|
7
|
+
* Manager providing the link between custom select box input components
|
|
8
|
+
* and the layout-level select options panel
|
|
9
|
+
*/
|
|
10
|
+
class SelectOptionsManager {
|
|
11
|
+
get panel() { return this._panel.value; }
|
|
12
|
+
constructor() {
|
|
13
|
+
this._panel = new rxjs_1.BehaviorSubject(undefined);
|
|
14
|
+
this.panel$ = this._panel.asObservable();
|
|
15
|
+
this.lastPanel = {
|
|
16
|
+
id: "",
|
|
17
|
+
timestamp: 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
open(optionsPanel) {
|
|
21
|
+
// Don't reopen the same panel within the reopen timeout
|
|
22
|
+
if (this.lastPanel.id === optionsPanel.id) {
|
|
23
|
+
if (this.lastPanel.timestamp + REOPEN_TIMEOUT > Date.now())
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this._panel.next(optionsPanel);
|
|
27
|
+
}
|
|
28
|
+
move(id, selectRect) {
|
|
29
|
+
var _a;
|
|
30
|
+
if (id && this.panel && id === ((_a = this.panel) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
31
|
+
const panel = this.panel;
|
|
32
|
+
if (panel.selectRect.top !== selectRect.top || panel.selectRect.left !== selectRect.left) {
|
|
33
|
+
panel.selectRect = selectRect;
|
|
34
|
+
this._panel.next(panel);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
isOpen(selectId) {
|
|
39
|
+
var _a;
|
|
40
|
+
return ((_a = this.panel) === null || _a === void 0 ? void 0 : _a.id) === selectId;
|
|
41
|
+
}
|
|
42
|
+
selectIndex(index) {
|
|
43
|
+
if (this.panel) {
|
|
44
|
+
if (this.panel.options[index].attrs.disabled) {
|
|
45
|
+
// Noop
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.panel.select(this.panel.options[index].attrs.value);
|
|
49
|
+
}
|
|
50
|
+
this.dismiss();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
selectValue(value) {
|
|
54
|
+
if (this.panel) {
|
|
55
|
+
this.panel.select(value);
|
|
56
|
+
this.dismiss();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
dismiss() {
|
|
60
|
+
var _a, _b, _c, _d;
|
|
61
|
+
(_b = (_a = this.panel) === null || _a === void 0 ? void 0 : _a.onDismiss) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
62
|
+
this.lastPanel.id = (_d = (_c = this.panel) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : "";
|
|
63
|
+
this.lastPanel.timestamp = Date.now();
|
|
64
|
+
this._panel.next(undefined);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.SelectOptionsManager = SelectOptionsManager;
|
|
File without changes
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import { container } from "tsyringe";
|
|
3
|
+
// import { getLogger } from "~common/services/get-logger";
|
|
4
|
+
// import { Logger, LogLevel } from "~common/services/logger.service";
|
|
5
|
+
// import { MobileModeService } from "~common/services/mobile-mode.service";
|
|
6
|
+
// const mobileMode = container.resolve(MobileModeService);
|
|
7
|
+
// export interface TooltipOpts {
|
|
8
|
+
// /** Specify the position of the tooltip */
|
|
9
|
+
// pos: TooltipPos,
|
|
10
|
+
// /** Override the default delay in milliseconds */
|
|
11
|
+
// delay: number,
|
|
12
|
+
// /** Increase the maximum width and height of the tooltip */
|
|
13
|
+
// jumbo: boolean,
|
|
14
|
+
// /** The tooltip should be attached to the document instead of the moused-over element */
|
|
15
|
+
// attachToBody: boolean,
|
|
16
|
+
// }
|
|
17
|
+
// export type TooltipPos = "cursor" | "top" | "bottom" | "left" | "right" | "follow";
|
|
18
|
+
// const DEFAULT_POS: TooltipPos = "cursor";
|
|
19
|
+
// const DEFAULT_DELAY = 500;
|
|
20
|
+
// const TOOLTIP_CLASS = "hypertable-tooltip";
|
|
21
|
+
// const MAX_WIDTH_PX = 250;
|
|
22
|
+
// const MAX_HEIGHT_PX = 100;
|
|
23
|
+
// const MAX_WIDTH_JUMBO_PX = 400;
|
|
24
|
+
// const MAX_HEIGHT_JUMBO_PX = 300;
|
|
25
|
+
// const logger = getLogger("Tooltip");
|
|
26
|
+
// // logger.logLevel = LogLevel.Debug;
|
|
27
|
+
// /**
|
|
28
|
+
// * Display a tooltip using the given mouse event.
|
|
29
|
+
// * Call this function from an element's "mouseenter" event.
|
|
30
|
+
// *
|
|
31
|
+
// * e.g.:
|
|
32
|
+
// * ```
|
|
33
|
+
// * onmouseenter: (ev: MouseEvent) => tooltip(ev, "Howdy!"),
|
|
34
|
+
// * ```
|
|
35
|
+
// * @param ev
|
|
36
|
+
// * @param tooltip Nothing is displayed if the tooltip text is empty
|
|
37
|
+
// * @param opts
|
|
38
|
+
// * @returns
|
|
39
|
+
// */
|
|
40
|
+
// export function tooltip(ev: MouseEvent, tooltip: string, opts?: Partial<TooltipOpts>) {
|
|
41
|
+
// // Tooltips don't work properly in mobile mode
|
|
42
|
+
// if (mobileMode.isMob) return;
|
|
43
|
+
// // If there is no tooltip, don't bother
|
|
44
|
+
// if (!tooltip) return;
|
|
45
|
+
// const target = ev.currentTarget as HTMLElement | null;
|
|
46
|
+
// const eventTarget = ev.target as HTMLElement | null;
|
|
47
|
+
// logger.debug(`target is ${target?.tagName}`);
|
|
48
|
+
// logger.debug(`event target is ${eventTarget?.tagName}`);
|
|
49
|
+
// if (!target) {
|
|
50
|
+
// return logger.debug("No target; returning from tooltip()");
|
|
51
|
+
// }
|
|
52
|
+
// // if (target?.classList.contains(TOOLTIP_CLASS) || eventTarget?.classList.contains(TOOLTIP_CLASS)) {
|
|
53
|
+
// // return logger.debug("Target is tooltip div; returning from tooltip()");
|
|
54
|
+
// // }
|
|
55
|
+
// // This helps avoid re-adding tooltips when mousing over the tooltip itself
|
|
56
|
+
// let hasTooltip = false;
|
|
57
|
+
// target.childNodes.forEach((child, key, parent) => {
|
|
58
|
+
// if ((child as HTMLElement)?.classList?.contains(TOOLTIP_CLASS)) {
|
|
59
|
+
// hasTooltip = true;
|
|
60
|
+
// }
|
|
61
|
+
// });
|
|
62
|
+
// if (hasTooltip) {
|
|
63
|
+
// return logger.debug("Target already has tooltip; returning from tooltip()");
|
|
64
|
+
// }
|
|
65
|
+
// const mousePos = { x: ev.x, y: ev.y };
|
|
66
|
+
// const latestPosFn = (ev: MouseEvent) => {
|
|
67
|
+
// mousePos.x = ev.x;
|
|
68
|
+
// mousePos.y = ev.y;
|
|
69
|
+
// };
|
|
70
|
+
// target.addEventListener("mousemove", latestPosFn);
|
|
71
|
+
// const timeout = setTimeout(() => {
|
|
72
|
+
// displayTooltip({ ev, target, tooltip, opts, mousePos });
|
|
73
|
+
// }, opts?.delay ?? DEFAULT_DELAY);
|
|
74
|
+
// const clearFn = (ev: MouseEvent) => {
|
|
75
|
+
// logger.debug("Clearing tooltip timeout");
|
|
76
|
+
// clearTimeout(timeout);
|
|
77
|
+
// target.removeEventListener("mouseleave", clearFn);
|
|
78
|
+
// target.removeEventListener("mousemove", latestPosFn);
|
|
79
|
+
// };
|
|
80
|
+
// target.addEventListener("mouseleave", clearFn);
|
|
81
|
+
// }
|
|
82
|
+
// /**
|
|
83
|
+
// *
|
|
84
|
+
// * @param value
|
|
85
|
+
// * @param opts
|
|
86
|
+
// * @returns
|
|
87
|
+
// */
|
|
88
|
+
// export function tooltipSpread(value: string, opts?: Partial<TooltipOpts>) {
|
|
89
|
+
// return {
|
|
90
|
+
// onmouseenter: (ev: MouseEvent) => tooltip(ev, value, opts),
|
|
91
|
+
// };
|
|
92
|
+
// }
|
|
93
|
+
// /**
|
|
94
|
+
// * TODO: consider adding arrows to top/bottom/left/right positioned tooltips
|
|
95
|
+
// * e.g.: https://getbootstrap.com/docs/3.4/javascript/#tooltips
|
|
96
|
+
// */
|
|
97
|
+
// function displayTooltip(settings: {
|
|
98
|
+
// ev: MouseEvent,
|
|
99
|
+
// target: HTMLElement,
|
|
100
|
+
// tooltip: string,
|
|
101
|
+
// mousePos: { x: number, y: number, }
|
|
102
|
+
// opts?: Partial<TooltipOpts>,
|
|
103
|
+
// }) {
|
|
104
|
+
// const { ev, target, tooltip, opts, mousePos } = settings;
|
|
105
|
+
// const cursorOffsetX = 5;
|
|
106
|
+
// const cursorOffsetY = 20;
|
|
107
|
+
// const pos = opts?.pos ?? DEFAULT_POS;
|
|
108
|
+
// let targetRect = target.getBoundingClientRect();
|
|
109
|
+
// // targetRect = new DOMRect(targetRect.x, targetRect.y, targetRect.width, targetRect.height);
|
|
110
|
+
// let targetCenter = {
|
|
111
|
+
// x: targetRect.left + (targetRect.width / 2),
|
|
112
|
+
// y: targetRect.top + (targetRect.height / 2),
|
|
113
|
+
// };
|
|
114
|
+
// const maxWidth = opts?.jumbo === true ? MAX_WIDTH_JUMBO_PX : MAX_WIDTH_PX;
|
|
115
|
+
// const maxHeight = opts?.jumbo === true ? MAX_HEIGHT_JUMBO_PX : MAX_HEIGHT_PX;
|
|
116
|
+
// logger.debug(`display tooltip "${tooltip}" on element ${target.tagName}`);
|
|
117
|
+
// const tooltipEl = document.createElement("div");
|
|
118
|
+
// tooltipEl.innerText = tooltip;
|
|
119
|
+
// tooltipEl.classList.value = "px-2 py-1 text-xs text-dark font-normal rounded border border-primary bg-white fade-in-100 no-select";
|
|
120
|
+
// tooltipEl.classList.add(TOOLTIP_CLASS);
|
|
121
|
+
// tooltipEl.style.position = "fixed";
|
|
122
|
+
// tooltipEl.style.maxWidth = `${maxWidth}px`;
|
|
123
|
+
// tooltipEl.style.maxHeight = `${maxHeight}px`;
|
|
124
|
+
// tooltipEl.style.zIndex = "1000";
|
|
125
|
+
// tooltipEl.style.whiteSpace = "normal";
|
|
126
|
+
// tooltipEl.style.opacity = "1";
|
|
127
|
+
// tooltipEl.style.textAlign = "left";
|
|
128
|
+
// tooltipEl.style.overflow = "hidden";
|
|
129
|
+
// tooltipEl.setAttribute("role", "tooltip");
|
|
130
|
+
// // // Add the tooltip before positioning it to read its dimensions
|
|
131
|
+
// // console.log(`tooltipEl width: ${tooltipEl.clientWidth}`);
|
|
132
|
+
// // console.log(`tooltipEl height: ${tooltipEl.clientHeight}`);
|
|
133
|
+
// if (settings.opts?.attachToBody === true) {
|
|
134
|
+
// document.body.append(tooltipEl);
|
|
135
|
+
// } else {
|
|
136
|
+
// target.append(tooltipEl);
|
|
137
|
+
// }
|
|
138
|
+
// // console.log(`tooltipEl width: ${tooltipEl.clientWidth}`);
|
|
139
|
+
// // console.log(`tooltipEl height: ${tooltipEl.clientHeight}`);
|
|
140
|
+
// if (pos === "cursor" || pos === "follow") {
|
|
141
|
+
// tooltipEl.style.top = `${mousePos.y + cursorOffsetY}px`;
|
|
142
|
+
// if (mousePos.x < window.innerWidth - maxWidth) {
|
|
143
|
+
// tooltipEl.style.left = `${mousePos.x + cursorOffsetX}px`;
|
|
144
|
+
// } else {
|
|
145
|
+
// tooltipEl.style.right = `${document.documentElement.clientWidth - mousePos.x - cursorOffsetX}px`;
|
|
146
|
+
// }
|
|
147
|
+
// } else if (pos === "top") {
|
|
148
|
+
// tooltipEl.style.bottom = `${window.innerHeight - targetRect.top}px`; // - window.scrollY
|
|
149
|
+
// tooltipEl.style.left = `${targetCenter.x - tooltipEl.clientWidth / 2}px`;
|
|
150
|
+
// } else if (pos === "bottom") {
|
|
151
|
+
// tooltipEl.style.top = `${targetRect.bottom}px`; // + window.scrollY
|
|
152
|
+
// tooltipEl.style.left = `${targetCenter.x - tooltipEl.clientWidth / 2}px`;
|
|
153
|
+
// } else if (pos === "left") {
|
|
154
|
+
// tooltipEl.style.right = `${document.documentElement.clientWidth - targetRect.left}px`;
|
|
155
|
+
// tooltipEl.style.top = `${targetCenter.y - tooltipEl.clientHeight / 2}px`;
|
|
156
|
+
// } else if (pos === "right") {
|
|
157
|
+
// tooltipEl.style.left = `${targetRect.right}px`;
|
|
158
|
+
// tooltipEl.style.top = `${targetCenter.y - tooltipEl.clientHeight / 2}px`;
|
|
159
|
+
// }
|
|
160
|
+
// if (pos === "follow") {
|
|
161
|
+
// const followFn = (ev: MouseEvent) => {
|
|
162
|
+
// tooltipEl.style.top = `${ev.y + cursorOffsetY}px`; // + window.scrollY
|
|
163
|
+
// if (mousePos.x < window.innerWidth - maxWidth) {
|
|
164
|
+
// tooltipEl.style.left = `${ev.x + cursorOffsetX}px`;
|
|
165
|
+
// tooltipEl.style.right = "";
|
|
166
|
+
// } else {
|
|
167
|
+
// tooltipEl.style.right = `${document.documentElement.clientWidth - ev.x - cursorOffsetX}px`;
|
|
168
|
+
// tooltipEl.style.left = "";
|
|
169
|
+
// }
|
|
170
|
+
// };
|
|
171
|
+
// const cleanupFn = () => {
|
|
172
|
+
// target.removeEventListener("mousemove", followFn);
|
|
173
|
+
// target.removeEventListener("mouseleave", cleanupFn);
|
|
174
|
+
// }
|
|
175
|
+
// target.addEventListener("mousemove", followFn);
|
|
176
|
+
// target.addEventListener("mouseleave", cleanupFn);
|
|
177
|
+
// }
|
|
178
|
+
// logger.debug(`displaying tooltip at: ${tooltipEl.style.left}, ${tooltipEl.style.top}`);
|
|
179
|
+
// const removeFn = () => {
|
|
180
|
+
// logger.debug(`Removing tooltip ${tooltip} from el ${target.tagName}`);
|
|
181
|
+
// tooltipEl.onanimationend = (ev: AnimationEvent) => {
|
|
182
|
+
// logger.debug(ev.animationName);
|
|
183
|
+
// if (settings.opts?.attachToBody === true) {
|
|
184
|
+
// document.body.removeChild(tooltipEl);
|
|
185
|
+
// } else {
|
|
186
|
+
// target.removeChild(tooltipEl);
|
|
187
|
+
// }
|
|
188
|
+
// }
|
|
189
|
+
// tooltipEl.classList.replace("fade-in-100", "fade-out-100");
|
|
190
|
+
// target.removeEventListener("mouseleave", removeFn);
|
|
191
|
+
// tooltipEl.removeEventListener("mouseenter", removeFn);
|
|
192
|
+
// }
|
|
193
|
+
// target.addEventListener("mouseleave", removeFn);
|
|
194
|
+
// tooltipEl.addEventListener("mouseenter", removeFn);
|
|
195
|
+
// if (settings.opts?.attachToBody === true) {
|
|
196
|
+
// const bodyListener = (ev: MouseEvent) => {
|
|
197
|
+
// if (ev.target && !target.contains(ev.target as any)) {
|
|
198
|
+
// removeFn();
|
|
199
|
+
// document.body.removeEventListener("mousemove", bodyListener);
|
|
200
|
+
// }
|
|
201
|
+
// };
|
|
202
|
+
// document.body.addEventListener("mousemove", bodyListener);
|
|
203
|
+
// }
|
|
204
|
+
// }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import m from "mithril";
|
|
2
|
+
import { TreeNode } from "./tree-node.interface";
|
|
3
|
+
/**
|
|
4
|
+
* An untyped tree component (see `createTree<T>()`)
|
|
5
|
+
*/
|
|
6
|
+
export declare const Tree: m.Component<{
|
|
7
|
+
/** The root list of nodes to display in the tree (circular references will crash) */
|
|
8
|
+
nodes: TreeNode<any>[];
|
|
9
|
+
/** Enable drag and drop reordering */
|
|
10
|
+
enableReordering?: boolean | undefined;
|
|
11
|
+
/** Enable expanding/collapsing of nodes with children */
|
|
12
|
+
enableToggling?: boolean | undefined;
|
|
13
|
+
/** Enable debug logging of certain events */
|
|
14
|
+
enableLogging?: boolean | undefined;
|
|
15
|
+
/** Function called when a node with children is toggled */
|
|
16
|
+
ontoggle?: ((node: TreeNode<any>, expanded: boolean, recursive: boolean) => void) | undefined;
|
|
17
|
+
/** Function called when a node label is clicked */
|
|
18
|
+
onclick?: ((node: TreeNode<any>) => void) | undefined;
|
|
19
|
+
/** Function called when a node has been dragged to a new position in the tree */
|
|
20
|
+
onreorder?: ((moved: any, target: any, above: boolean) => void) | undefined;
|
|
21
|
+
/** Optional message to display in defined but empty children lists */
|
|
22
|
+
emptyChildrenMessage?: string | undefined;
|
|
23
|
+
/** Optionally override the default icon for a collapsed folder */
|
|
24
|
+
collapsedIcon?: m.Vnode<any, {}> | undefined;
|
|
25
|
+
/** Optionally override the default icon for an expanded folder */
|
|
26
|
+
expandedIcon?: m.Vnode<any, {}> | undefined;
|
|
27
|
+
/** Optionally enable toggling of expansion nodes by clicking their labels */
|
|
28
|
+
toggleExpansionWithLabel?: boolean | undefined;
|
|
29
|
+
/** Optional CSS classes to apply to the root div */
|
|
30
|
+
class?: string | undefined;
|
|
31
|
+
/** Optional CSS styles to apply to the root div */
|
|
32
|
+
style?: string | undefined;
|
|
33
|
+
}, {
|
|
34
|
+
isReordering: boolean;
|
|
35
|
+
thisEl: Element;
|
|
36
|
+
targetRect: DOMRect | undefined;
|
|
37
|
+
targettingTop: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Build a component to display nested nodes in a tree.
|
|
41
|
+
*
|
|
42
|
+
* Nodes with children are collapsable, and nodes can be arbitrarily reordered.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createTree<TNodeData extends object = {}>(): m.Component<{
|
|
45
|
+
/** The root list of nodes to display in the tree (circular references will crash) */
|
|
46
|
+
nodes: TreeNode<TNodeData>[];
|
|
47
|
+
/** Enable drag and drop reordering */
|
|
48
|
+
enableReordering?: boolean | undefined;
|
|
49
|
+
/** Enable expanding/collapsing of nodes with children */
|
|
50
|
+
enableToggling?: boolean | undefined;
|
|
51
|
+
/** Enable debug logging of certain events */
|
|
52
|
+
enableLogging?: boolean | undefined;
|
|
53
|
+
/** Function called when a node with children is toggled */
|
|
54
|
+
ontoggle?: ((node: TreeNode<TNodeData>, expanded: boolean, recursive: boolean) => void) | undefined;
|
|
55
|
+
/** Function called when a node label is clicked */
|
|
56
|
+
onclick?: ((node: TreeNode<TNodeData>) => void) | undefined;
|
|
57
|
+
/** Function called when a node has been dragged to a new position in the tree */
|
|
58
|
+
onreorder?: ((moved: TNodeData, target: TNodeData, above: boolean) => void) | undefined;
|
|
59
|
+
/** Optional message to display in defined but empty children lists */
|
|
60
|
+
emptyChildrenMessage?: string | undefined;
|
|
61
|
+
/** Optionally override the default icon for a collapsed folder */
|
|
62
|
+
collapsedIcon?: m.Vnode<any, {}> | undefined;
|
|
63
|
+
/** Optionally override the default icon for an expanded folder */
|
|
64
|
+
expandedIcon?: m.Vnode<any, {}> | undefined;
|
|
65
|
+
/** Optionally enable toggling of expansion nodes by clicking their labels */
|
|
66
|
+
toggleExpansionWithLabel?: boolean | undefined;
|
|
67
|
+
/** Optional CSS classes to apply to the root div */
|
|
68
|
+
class?: string | undefined;
|
|
69
|
+
/** Optional CSS styles to apply to the root div */
|
|
70
|
+
style?: string | undefined;
|
|
71
|
+
}, {
|
|
72
|
+
isReordering: boolean;
|
|
73
|
+
thisEl: Element;
|
|
74
|
+
targetRect: DOMRect | undefined;
|
|
75
|
+
targettingTop: boolean;
|
|
76
|
+
}>;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTree = exports.Tree = void 0;
|
|
7
|
+
const utils_1 = require("@hypericon/utils");
|
|
8
|
+
const mithril_1 = __importDefault(require("mithril"));
|
|
9
|
+
const icon_1 = require("../icon");
|
|
10
|
+
const DATA_TRANSFER_TYPE = "application/tree-data";
|
|
11
|
+
/**
|
|
12
|
+
* An untyped tree component (see `createTree<T>()`)
|
|
13
|
+
*/
|
|
14
|
+
exports.Tree = createTree();
|
|
15
|
+
/**
|
|
16
|
+
* Build a component to display nested nodes in a tree.
|
|
17
|
+
*
|
|
18
|
+
* Nodes with children are collapsable, and nodes can be arbitrarily reordered.
|
|
19
|
+
*/
|
|
20
|
+
function createTree() {
|
|
21
|
+
// /** Reference to the TreeItem currently being dragged */
|
|
22
|
+
// let draggedTreeItem: TreeItem<T> | undefined = undefined;
|
|
23
|
+
const Tree = {
|
|
24
|
+
oninit() {
|
|
25
|
+
this.isReordering = false;
|
|
26
|
+
},
|
|
27
|
+
oncreate({ dom }) {
|
|
28
|
+
this.thisEl = dom;
|
|
29
|
+
},
|
|
30
|
+
view({ attrs }) {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const nodes = attrs.nodes;
|
|
33
|
+
return (0, mithril_1.default)(".mico-tree", {
|
|
34
|
+
style: `${(_a = attrs.style) !== null && _a !== void 0 ? _a : ""} position: relative;`,
|
|
35
|
+
class: `${(_b = attrs.class) !== null && _b !== void 0 ? _b : ""}`
|
|
36
|
+
}, [
|
|
37
|
+
(0, mithril_1.default)("ul", nodes.map((node, i) => {
|
|
38
|
+
return (0, mithril_1.default)(TreeNode, {
|
|
39
|
+
// key: i, // item.key,
|
|
40
|
+
node,
|
|
41
|
+
enableReordering: attrs.enableReordering,
|
|
42
|
+
enableToggling: attrs.enableToggling,
|
|
43
|
+
enableLogging: attrs.enableLogging,
|
|
44
|
+
ontoggle: attrs.ontoggle,
|
|
45
|
+
onclick: attrs.onclick,
|
|
46
|
+
reorder: attrs.onreorder,
|
|
47
|
+
emptyChildrenMessage: attrs.emptyChildrenMessage,
|
|
48
|
+
collapsedIcon: attrs.collapsedIcon,
|
|
49
|
+
expandedIcon: attrs.expandedIcon,
|
|
50
|
+
toggleExpansionWithLabel: attrs.toggleExpansionWithLabel,
|
|
51
|
+
});
|
|
52
|
+
})),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const TreeNode = {
|
|
57
|
+
oninit({ attrs }) {
|
|
58
|
+
const { enableReordering } = attrs;
|
|
59
|
+
this.isReorderingOver = false;
|
|
60
|
+
this.handleClick = (ev, node) => {
|
|
61
|
+
var _a;
|
|
62
|
+
(_a = attrs.onclick) === null || _a === void 0 ? void 0 : _a.call(attrs, node);
|
|
63
|
+
};
|
|
64
|
+
this.handleDragStart = (ev, node) => {
|
|
65
|
+
if (!enableReordering)
|
|
66
|
+
return;
|
|
67
|
+
if (!ev.dataTransfer)
|
|
68
|
+
return;
|
|
69
|
+
attrs.enableLogging && console.log(`start dragging`, node.data);
|
|
70
|
+
ev.dataTransfer.setData(DATA_TRANSFER_TYPE, JSON.stringify(node.data));
|
|
71
|
+
ev.dataTransfer.effectAllowed = "move";
|
|
72
|
+
// attrs.reorderstart?.();
|
|
73
|
+
};
|
|
74
|
+
this.handleDragOver = (ev, node) => {
|
|
75
|
+
ev.preventDefault();
|
|
76
|
+
if (!enableReordering)
|
|
77
|
+
return;
|
|
78
|
+
// attrs.enableLogging && console.log(`on dragover`, ev.dataTransfer?.getData(DATA_TRANSFER_TYPE), "over", item.data);
|
|
79
|
+
// ev.target;
|
|
80
|
+
// ev.x;
|
|
81
|
+
const overDiv = ev.currentTarget;
|
|
82
|
+
const targetRect = overDiv.getBoundingClientRect();
|
|
83
|
+
const topToMouse = ev.clientY - targetRect.top;
|
|
84
|
+
const isAbove = topToMouse < (targetRect.height - topToMouse);
|
|
85
|
+
this.isTargettingAbove = isAbove;
|
|
86
|
+
this.isReorderingOver = true;
|
|
87
|
+
// attrs.reorderover?.(overDiv, ev.clientY);
|
|
88
|
+
};
|
|
89
|
+
this.handleDrag = (ev, node) => {
|
|
90
|
+
// attrs.enableLogging && console.log(`on drag`, item.data);
|
|
91
|
+
};
|
|
92
|
+
this.handleDragLeave = (ev, node) => {
|
|
93
|
+
this.isReorderingOver = false;
|
|
94
|
+
};
|
|
95
|
+
this.handleDrop = (ev, node) => {
|
|
96
|
+
var _a;
|
|
97
|
+
ev.preventDefault();
|
|
98
|
+
if (!enableReordering)
|
|
99
|
+
return;
|
|
100
|
+
if (ev.dataTransfer) {
|
|
101
|
+
ev.dataTransfer.dropEffect = "move";
|
|
102
|
+
const movedNodeData = JSON.parse(ev.dataTransfer.getData(DATA_TRANSFER_TYPE));
|
|
103
|
+
attrs.enableLogging && console.log(`on drop`, movedNodeData, "on", node.data);
|
|
104
|
+
// attrs.reorderfinish?.();
|
|
105
|
+
const droppedOnSelf = (0, utils_1.compareObjects)(movedNodeData, node.data);
|
|
106
|
+
if (!droppedOnSelf) {
|
|
107
|
+
(_a = attrs.reorder) === null || _a === void 0 ? void 0 : _a.call(attrs, movedNodeData, node.data, this.isTargettingAbove);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.isReorderingOver = false;
|
|
111
|
+
};
|
|
112
|
+
this.handleDragEnd = (ev, node) => {
|
|
113
|
+
var _a;
|
|
114
|
+
attrs.enableLogging && console.log(`drag end, dropEffect:`, (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.dropEffect);
|
|
115
|
+
// "move" on success, "none" on failure/cancellation
|
|
116
|
+
// attrs.reorderfinish?.();
|
|
117
|
+
this.isReorderingOver = false;
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
view({ attrs }) {
|
|
121
|
+
const { node, enableToggling, enableReordering, emptyChildrenMessage } = attrs;
|
|
122
|
+
const canHaveChildren = node.children !== undefined;
|
|
123
|
+
const hasChildren = node.children !== undefined && (node.children.length > 0);
|
|
124
|
+
const expanded = canHaveChildren && node.expanded;
|
|
125
|
+
const collapsed = canHaveChildren && !node.expanded;
|
|
126
|
+
const targettingBackgroundClass = this.isReorderingOver ? (this.isTargettingAbove ? "mico-tree-reorder-above" : "mico-tree-reorder-below") : "";
|
|
127
|
+
return (0, mithril_1.default)("li.mico-tree-node", {
|
|
128
|
+
// key: item.key,
|
|
129
|
+
}, [
|
|
130
|
+
// The items's label (either a string or Vnode) and expand/collapse control (if there are children)
|
|
131
|
+
(0, mithril_1.default)("", {
|
|
132
|
+
style: `display: flex; align-items: stretch; max-width: 100%; overflow-x: hidden; ${(attrs.toggleExpansionWithLabel && enableToggling) ? `cursor: pointer;` : ``}`,
|
|
133
|
+
}, [
|
|
134
|
+
expanded ? (0, mithril_1.default)("button.mico-tree-toggle", {
|
|
135
|
+
style: `width: 1.5rem; flex: none;`,
|
|
136
|
+
class: enableToggling ? `hover:text-primary` : `text-view-icon`,
|
|
137
|
+
disabled: !enableToggling,
|
|
138
|
+
onclick: (ev) => {
|
|
139
|
+
var _a;
|
|
140
|
+
if (!enableToggling)
|
|
141
|
+
return;
|
|
142
|
+
node.expanded = false;
|
|
143
|
+
(_a = attrs.ontoggle) === null || _a === void 0 ? void 0 : _a.call(attrs, node, false, ev.altKey);
|
|
144
|
+
},
|
|
145
|
+
}, attrs.expandedIcon || (0, mithril_1.default)(icon_1.Icon, { fas: "chevron-down" })) : undefined,
|
|
146
|
+
collapsed ? (0, mithril_1.default)("button.mico-tree-toggle", {
|
|
147
|
+
style: `width: 1.5rem; flex: none;`,
|
|
148
|
+
class: enableToggling ? `hover:text-primary` : `text-view-icon`,
|
|
149
|
+
disabled: !enableToggling,
|
|
150
|
+
onclick: (ev) => {
|
|
151
|
+
var _a;
|
|
152
|
+
if (!enableToggling)
|
|
153
|
+
return;
|
|
154
|
+
node.expanded = true;
|
|
155
|
+
(_a = attrs.ontoggle) === null || _a === void 0 ? void 0 : _a.call(attrs, node, true, ev.altKey);
|
|
156
|
+
},
|
|
157
|
+
}, attrs.collapsedIcon || (0, mithril_1.default)(icon_1.Icon, { fas: "chevron-right" })) : undefined,
|
|
158
|
+
(0, mithril_1.default)("", {
|
|
159
|
+
class: `${targettingBackgroundClass}`,
|
|
160
|
+
style: "position: relative; flex: 1 1 0%; display: flex; flex-direction: column; overflow-x: hidden;",
|
|
161
|
+
draggable: enableReordering,
|
|
162
|
+
onclick: (ev) => {
|
|
163
|
+
var _a;
|
|
164
|
+
this.handleClick(ev, node);
|
|
165
|
+
// If labels should toggle expansion, toggle expansion
|
|
166
|
+
if (attrs.toggleExpansionWithLabel && enableToggling) {
|
|
167
|
+
node.expanded = !expanded;
|
|
168
|
+
(_a = attrs.ontoggle) === null || _a === void 0 ? void 0 : _a.call(attrs, node, !expanded, ev.altKey);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
ondragstart: (ev) => this.handleDragStart(ev, node),
|
|
172
|
+
ondrag: (ev) => this.handleDrag(ev, node),
|
|
173
|
+
ondragover: (ev) => this.handleDragOver(ev, node),
|
|
174
|
+
ondragleave: (ev) => this.handleDragLeave(ev, node),
|
|
175
|
+
ondrop: (ev) => this.handleDrop(ev, node),
|
|
176
|
+
ondragend: (ev) => this.handleDragEnd(ev, node),
|
|
177
|
+
}, [
|
|
178
|
+
(typeof node.label === "string") ? (0, mithril_1.default)("span", node.label) : node.label,
|
|
179
|
+
]),
|
|
180
|
+
]),
|
|
181
|
+
// Any nested children within the item
|
|
182
|
+
(expanded) ? (0, mithril_1.default)("ul", [
|
|
183
|
+
...(node.children || []).map((child, i) => {
|
|
184
|
+
return (0, mithril_1.default)(TreeNode, {
|
|
185
|
+
// key: child.key,
|
|
186
|
+
node: child,
|
|
187
|
+
enableToggling: attrs.enableToggling,
|
|
188
|
+
enableReordering: attrs.enableReordering,
|
|
189
|
+
ontoggle: attrs.ontoggle,
|
|
190
|
+
onclick: attrs.onclick,
|
|
191
|
+
reorder: attrs.reorder,
|
|
192
|
+
// reorderstart: attrs.reorderstart,
|
|
193
|
+
// reorderover: attrs.reorderover,
|
|
194
|
+
// reorderfinish: attrs.reorderfinish,
|
|
195
|
+
emptyChildrenMessage: attrs.emptyChildrenMessage,
|
|
196
|
+
collapsedIcon: attrs.collapsedIcon,
|
|
197
|
+
expandedIcon: attrs.expandedIcon,
|
|
198
|
+
toggleExpansionWithLabel: attrs.toggleExpansionWithLabel,
|
|
199
|
+
});
|
|
200
|
+
}),
|
|
201
|
+
(canHaveChildren && !hasChildren && emptyChildrenMessage !== undefined)
|
|
202
|
+
? (0, mithril_1.default)("span.mico-tree-empty-children", emptyChildrenMessage)
|
|
203
|
+
: [],
|
|
204
|
+
]) : [],
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return Tree;
|
|
209
|
+
}
|
|
210
|
+
exports.createTree = createTree;
|
|
@@ -1 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Tree"), exports);
|
|
18
|
+
__exportStar(require("./tree-node.interface"), exports);
|
|
19
|
+
__exportStar(require("./tree-utils"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TreeNode } from "./tree-node.interface";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively find the list of all descendants of the given node.
|
|
4
|
+
*
|
|
5
|
+
* The given node itself is not included in the list of descendants.
|
|
6
|
+
*
|
|
7
|
+
* Performs a depth-first traversal.
|
|
8
|
+
* @param node
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export declare function allTreeDescendants<T extends object = {}>(node: TreeNode<T>): T[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.allTreeDescendants = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Recursively find the list of all descendants of the given node.
|
|
6
|
+
*
|
|
7
|
+
* The given node itself is not included in the list of descendants.
|
|
8
|
+
*
|
|
9
|
+
* Performs a depth-first traversal.
|
|
10
|
+
* @param node
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
function allTreeDescendants(node) {
|
|
14
|
+
var _a;
|
|
15
|
+
const descendants = [];
|
|
16
|
+
for (const child of (_a = node.children) !== null && _a !== void 0 ? _a : []) {
|
|
17
|
+
descendants.push(child.data);
|
|
18
|
+
descendants.push(...allTreeDescendants(child));
|
|
19
|
+
}
|
|
20
|
+
return descendants;
|
|
21
|
+
}
|
|
22
|
+
exports.allTreeDescendants = allTreeDescendants;
|