wunderbaum 0.9.0 → 0.10.1
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 +1 -1
- package/dist/wunderbaum.css +9 -2
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +62 -2
- package/dist/wunderbaum.esm.js +159 -25
- package/dist/wunderbaum.esm.min.js +21 -21
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +159 -25
- package/dist/wunderbaum.umd.min.js +30 -30
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/common.ts +2 -0
- package/src/drag_observer.ts +32 -3
- package/src/types.ts +19 -0
- package/src/util.ts +53 -1
- package/src/wb_ext_filter.ts +4 -0
- package/src/wb_ext_grid.ts +66 -6
- package/src/wb_options.ts +5 -0
- package/src/wunderbaum.scss +11 -3
- package/src/wunderbaum.ts +19 -2
package/src/common.ts
CHANGED
|
@@ -26,6 +26,8 @@ export const TITLE_SPAN_PAD_Y = 7;
|
|
|
26
26
|
export const RENDER_MAX_PREFETCH = 5;
|
|
27
27
|
/** Skip rendering new rows when we have at least N nodes rendeed above and below the viewport. */
|
|
28
28
|
export const RENDER_MIN_PREFETCH = 5;
|
|
29
|
+
/** Minimum column width if not set otherwise. */
|
|
30
|
+
export const DEFAULT_MIN_COL_WIDTH = 4;
|
|
29
31
|
/** Regular expression to detect if a string describes an image URL (in contrast
|
|
30
32
|
* to a class name). Strings are considered image urls if they contain '.' or '/'.
|
|
31
33
|
*/
|
package/src/drag_observer.ts
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
export type DragCallbackArgType = {
|
|
8
8
|
/** "dragstart", "drag", or "dragstop". */
|
|
9
9
|
type: string;
|
|
10
|
-
/** Original
|
|
10
|
+
/** Original mousedown or touch event that triggered the dragstart event. */
|
|
11
|
+
startEvent: MouseEvent | TouchEvent;
|
|
12
|
+
/** Original mouse or touch event that triggered the current drag event.
|
|
13
|
+
* Note that this is not the same as `startEvent`, but a mousemove in case of
|
|
14
|
+
* a dragstart threshold.
|
|
15
|
+
*/
|
|
11
16
|
event: MouseEvent | TouchEvent;
|
|
17
|
+
/** Custom data that was passed to the DragObserver, typically on dragstart. */
|
|
18
|
+
customData: any;
|
|
12
19
|
/** Element which is currently dragged. */
|
|
13
20
|
dragElem: HTMLElement | null;
|
|
14
21
|
/** Relative horizontal drag distance since start. */
|
|
@@ -38,7 +45,16 @@ type DragObserverOptionsType = {
|
|
|
38
45
|
export class DragObserver {
|
|
39
46
|
protected _handler;
|
|
40
47
|
protected root: EventTarget;
|
|
41
|
-
protected start
|
|
48
|
+
protected start: {
|
|
49
|
+
event: MouseEvent | TouchEvent | null;
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
altKey: boolean;
|
|
53
|
+
ctrlKey: boolean;
|
|
54
|
+
metaKey: boolean;
|
|
55
|
+
shiftKey: boolean;
|
|
56
|
+
} = {
|
|
57
|
+
event: null,
|
|
42
58
|
x: 0,
|
|
43
59
|
y: 0,
|
|
44
60
|
altKey: false,
|
|
@@ -48,6 +64,7 @@ export class DragObserver {
|
|
|
48
64
|
};
|
|
49
65
|
protected dragElem: HTMLElement | null = null;
|
|
50
66
|
protected dragging: boolean = false;
|
|
67
|
+
protected customData: object = {};
|
|
51
68
|
// TODO: touch events
|
|
52
69
|
protected events = ["mousedown", "mouseup", "mousemove", "keydown"];
|
|
53
70
|
protected opts: DragObserverOptionsType;
|
|
@@ -81,10 +98,16 @@ export class DragObserver {
|
|
|
81
98
|
public stopDrag(cb_event?: DragCallbackArgType): void {
|
|
82
99
|
if (this.dragging && this.opts.dragstop && cb_event) {
|
|
83
100
|
cb_event.type = "dragstop";
|
|
84
|
-
|
|
101
|
+
try {
|
|
102
|
+
this.opts.dragstop(cb_event);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error("dragstop error", err); // eslint-disable-line no-console
|
|
105
|
+
}
|
|
85
106
|
}
|
|
86
107
|
this.dragElem = null;
|
|
87
108
|
this.dragging = false;
|
|
109
|
+
this.start.event = null;
|
|
110
|
+
this.customData = {};
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
protected handleEvent(e: MouseEvent): boolean | void {
|
|
@@ -92,12 +115,17 @@ export class DragObserver {
|
|
|
92
115
|
const opts = this.opts;
|
|
93
116
|
const cb_event: DragCallbackArgType = {
|
|
94
117
|
type: e.type,
|
|
118
|
+
startEvent: type === "mousedown" ? e : this.start.event!,
|
|
95
119
|
event: e,
|
|
120
|
+
customData: this.customData,
|
|
96
121
|
dragElem: this.dragElem,
|
|
97
122
|
dx: e.pageX - this.start.x,
|
|
98
123
|
dy: e.pageY - this.start.y,
|
|
99
124
|
apply: undefined,
|
|
100
125
|
};
|
|
126
|
+
|
|
127
|
+
// console.log("handleEvent", type, cb_event);
|
|
128
|
+
|
|
101
129
|
switch (type) {
|
|
102
130
|
case "keydown":
|
|
103
131
|
this.stopDrag(cb_event);
|
|
@@ -120,6 +148,7 @@ export class DragObserver {
|
|
|
120
148
|
}
|
|
121
149
|
}
|
|
122
150
|
}
|
|
151
|
+
this.start.event = e;
|
|
123
152
|
this.start.x = e.pageX;
|
|
124
153
|
this.start.y = e.pageY;
|
|
125
154
|
this.start.altKey = e.altKey;
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { WunderbaumOptions } from "./wb_options";
|
|
|
12
12
|
export type TristateType = boolean | undefined;
|
|
13
13
|
/** Show/hide checkbox or display a radiobutton icon instead. */
|
|
14
14
|
export type CheckboxOption = boolean | "radio";
|
|
15
|
+
/** A value that can either be true, false, or undefined. */
|
|
16
|
+
export type SortOrderType = "asc" | "desc" | undefined;
|
|
15
17
|
/** An icon may either be
|
|
16
18
|
* a string-tag that references an entry in the `iconMap` (e.g. `"folderOpen"`)),
|
|
17
19
|
* an HTML string that contains a `<` and is used as-is,
|
|
@@ -348,6 +350,23 @@ export interface ColumnDefinition {
|
|
|
348
350
|
* Default: `4px`.
|
|
349
351
|
*/
|
|
350
352
|
minWidth?: string | number;
|
|
353
|
+
/** Allow user to resize the column.
|
|
354
|
+
* Default: false.
|
|
355
|
+
*/
|
|
356
|
+
resizable?: boolean;
|
|
357
|
+
/** Optional custom column width when user resized by mouse drag.
|
|
358
|
+
* Default: unset.
|
|
359
|
+
*/
|
|
360
|
+
customWidthPx?: number;
|
|
361
|
+
/** Allow user to sort the column. Default: false. <br>
|
|
362
|
+
* **Note:** Sorting is not implemented yet.
|
|
363
|
+
*/
|
|
364
|
+
sortable?: boolean;
|
|
365
|
+
/** Optional custom column sort orde when user clicked the sort icon.
|
|
366
|
+
* Default: unset. <br>
|
|
367
|
+
* **Note:** Sorting is not implemented yet.
|
|
368
|
+
*/
|
|
369
|
+
sortOrder?: SortOrderType;
|
|
351
370
|
/** Optional class names that are added to all `span.wb-col` header AND data
|
|
352
371
|
* elements of that column.
|
|
353
372
|
*/
|
package/src/util.ts
CHANGED
|
@@ -521,7 +521,7 @@ export function isArray(obj: any) {
|
|
|
521
521
|
return Array.isArray(obj);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
/** Return true if `obj` is of type `Object` and has no
|
|
524
|
+
/** Return true if `obj` is of type `Object` and has no properties. */
|
|
525
525
|
export function isEmptyObject(obj: any) {
|
|
526
526
|
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
|
527
527
|
}
|
|
@@ -764,6 +764,58 @@ export function toSet(val: any): Set<string> {
|
|
|
764
764
|
throw new Error("Cannot convert to Set<string>: " + val);
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
/** Convert a pixel string to number.
|
|
768
|
+
* We accept a number or a string like '123px'. If undefined, the first default
|
|
769
|
+
* value that is a number or a string ending with 'px' is returned.
|
|
770
|
+
*
|
|
771
|
+
* Example:
|
|
772
|
+
* ```js
|
|
773
|
+
* let x = undefined;
|
|
774
|
+
* let y = "123px";
|
|
775
|
+
* const width = util.toPixel(x, y, 100); // returns 123
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
778
|
+
export function toPixel(
|
|
779
|
+
// val: string | number | undefined | null,
|
|
780
|
+
...defaults: (string | number | undefined | null)[]
|
|
781
|
+
): number {
|
|
782
|
+
// if (typeof val === "number") {
|
|
783
|
+
// return val;
|
|
784
|
+
// }
|
|
785
|
+
for (const d of defaults) {
|
|
786
|
+
if (typeof d === "number") {
|
|
787
|
+
return d;
|
|
788
|
+
}
|
|
789
|
+
if (typeof d === "string" && d.endsWith("px")) {
|
|
790
|
+
return parseInt(d, 10);
|
|
791
|
+
}
|
|
792
|
+
assert(d == null, `Expected a number or string like '123px': ${d}`);
|
|
793
|
+
}
|
|
794
|
+
throw new Error(`Expected a string like '123px': ${defaults}`);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** Return the the boolean value of the first non-null element.
|
|
798
|
+
* Example:
|
|
799
|
+
* ```js
|
|
800
|
+
* const opts = { flag: true };
|
|
801
|
+
* const value = util.toBool(opts.foo, opts.flag, false); // returns true
|
|
802
|
+
* ```
|
|
803
|
+
*/
|
|
804
|
+
export function toBool(
|
|
805
|
+
// val: boolean | undefined | null,
|
|
806
|
+
...boolDefaults: (boolean | undefined | null)[]
|
|
807
|
+
): boolean {
|
|
808
|
+
// if (val != null) {
|
|
809
|
+
// return !!val;
|
|
810
|
+
// }
|
|
811
|
+
for (const d of boolDefaults) {
|
|
812
|
+
if (d != null) {
|
|
813
|
+
return !!d;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
throw new Error("No default boolean value provided");
|
|
817
|
+
}
|
|
818
|
+
|
|
767
819
|
// /** Check if a string is contained in an Array or Set. */
|
|
768
820
|
// export function isAnyOf(s: string, items: Array<string>|Set<string>): boolean {
|
|
769
821
|
// return Array.prototype.includes.call(items, s)
|
package/src/wb_ext_filter.ts
CHANGED
|
@@ -52,6 +52,10 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
|
|
|
52
52
|
const connectInput = this.getPluginOption("connectInput");
|
|
53
53
|
if (connectInput) {
|
|
54
54
|
this.queryInput = elemFromSelector(connectInput) as HTMLInputElement;
|
|
55
|
+
assert(
|
|
56
|
+
this.queryInput,
|
|
57
|
+
`Invalid 'filter.connectInput' option: ${connectInput}.`
|
|
58
|
+
);
|
|
55
59
|
onEvent(
|
|
56
60
|
this.queryInput,
|
|
57
61
|
"input",
|
package/src/wb_ext_grid.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import { Wunderbaum } from "./wunderbaum";
|
|
7
7
|
import { WunderbaumExtension } from "./wb_extension_base";
|
|
8
8
|
import { DragCallbackArgType, DragObserver } from "./drag_observer";
|
|
9
|
-
import { GridOptionsType } from "./types";
|
|
9
|
+
import { ChangeType, ColumnDefinition, GridOptionsType } from "./types";
|
|
10
|
+
import { DEFAULT_MIN_COL_WIDTH } from "./common";
|
|
11
|
+
import { toBool, toPixel } from "./util";
|
|
10
12
|
|
|
11
13
|
export class GridExtension extends WunderbaumExtension<GridOptionsType> {
|
|
12
14
|
protected observer: DragObserver;
|
|
@@ -18,11 +20,47 @@ export class GridExtension extends WunderbaumExtension<GridOptionsType> {
|
|
|
18
20
|
|
|
19
21
|
this.observer = new DragObserver({
|
|
20
22
|
root: window.document,
|
|
21
|
-
selector: "span.wb-col-resizer",
|
|
23
|
+
selector: "span.wb-col-resizer-active",
|
|
22
24
|
thresh: 4,
|
|
23
25
|
// throttle: 400,
|
|
24
26
|
dragstart: (e) => {
|
|
25
|
-
|
|
27
|
+
const info = Wunderbaum.getEventInfo(e.startEvent);
|
|
28
|
+
const colDef = info.colDef!;
|
|
29
|
+
const allow =
|
|
30
|
+
colDef &&
|
|
31
|
+
this.tree.element.contains(e.dragElem) &&
|
|
32
|
+
toBool(colDef.resizable, tree.options.resizableColumns, false);
|
|
33
|
+
|
|
34
|
+
// this.tree.log("dragstart", colDef, e, info);
|
|
35
|
+
|
|
36
|
+
this.tree.element.classList.toggle("wb-col-resizing", !!allow);
|
|
37
|
+
info.colElem!.classList.toggle("wb-col-resizing", !!allow);
|
|
38
|
+
|
|
39
|
+
// We start dagging, so we remember the actual width in *pixels*
|
|
40
|
+
// (which may be 'auto' or '100%').
|
|
41
|
+
// Since we we re-create the markup on each update, we also cannot store
|
|
42
|
+
// the original event or DOM element, but only the colDef object.
|
|
43
|
+
if (allow) {
|
|
44
|
+
// Store initial target column infos in customData
|
|
45
|
+
e.customData.colDef = colDef;
|
|
46
|
+
e.customData.orgCustomWidthPx = colDef.customWidthPx;
|
|
47
|
+
const curWidthPx = Number.parseInt(info.colElem!.style.width, 10);
|
|
48
|
+
e.customData.orgWidthPx = curWidthPx;
|
|
49
|
+
// Set custom width to current width, so that we can modify it
|
|
50
|
+
colDef.customWidthPx = curWidthPx;
|
|
51
|
+
// this.tree.log(
|
|
52
|
+
// `dragstart customWidthPx=${colDef.customWidthPx}`,
|
|
53
|
+
// e,
|
|
54
|
+
// info
|
|
55
|
+
// );
|
|
56
|
+
this.tree.update(ChangeType.colStructure);
|
|
57
|
+
// this.tree.log(
|
|
58
|
+
// `dragstart 2 customWidthPx=${colDef.customWidthPx}`,
|
|
59
|
+
// e,
|
|
60
|
+
// info
|
|
61
|
+
// );
|
|
62
|
+
}
|
|
63
|
+
return allow;
|
|
26
64
|
},
|
|
27
65
|
drag: (e) => {
|
|
28
66
|
// TODO: throttle
|
|
@@ -38,9 +76,31 @@ export class GridExtension extends WunderbaumExtension<GridOptionsType> {
|
|
|
38
76
|
super.init();
|
|
39
77
|
}
|
|
40
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Hanldes drag and sragstop events for column resizing.
|
|
81
|
+
*/
|
|
41
82
|
protected handleDrag(e: DragCallbackArgType): void {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
this.tree.log(`${e.type}(
|
|
83
|
+
const custom = e.customData;
|
|
84
|
+
const colDef = <ColumnDefinition>custom.colDef!;
|
|
85
|
+
// this.tree.log(`${e.type} (dx=${e.dx})`, e, info);
|
|
86
|
+
|
|
87
|
+
if (e.type === "dragstop" || e.type === "drag") {
|
|
88
|
+
this.tree.element.classList.remove("wb-col-resizing");
|
|
89
|
+
// info.colElem!.classList.remove("wb-col-resizing");
|
|
90
|
+
if (e.apply || e.type === "drag") {
|
|
91
|
+
const minWidth = toPixel(colDef.minWidth, DEFAULT_MIN_COL_WIDTH);
|
|
92
|
+
const newWidth = Math.max(minWidth, custom.orgWidthPx + e.dx);
|
|
93
|
+
colDef.customWidthPx = newWidth;
|
|
94
|
+
// this.tree.log(
|
|
95
|
+
// `${e.type} minWidth=${minWidth}, newWidth=${newWidth}`,
|
|
96
|
+
// colDef
|
|
97
|
+
// );
|
|
98
|
+
} else {
|
|
99
|
+
// Drag was cancelled
|
|
100
|
+
this.tree.log("Column resize cancelled", e);
|
|
101
|
+
colDef.customWidthPx = custom.orgCustomWidthPx; // Restore original width or undefined
|
|
102
|
+
}
|
|
103
|
+
this.tree.update(ChangeType.colStructure);
|
|
104
|
+
}
|
|
45
105
|
}
|
|
46
106
|
}
|
package/src/wb_options.ts
CHANGED
|
@@ -217,6 +217,11 @@ export interface WunderbaumOptions {
|
|
|
217
217
|
* Default: false
|
|
218
218
|
*/
|
|
219
219
|
fixedCol?: boolean;
|
|
220
|
+
/**
|
|
221
|
+
* Default value for ColumnDefinition.resizable option.
|
|
222
|
+
* Default: false
|
|
223
|
+
*/
|
|
224
|
+
resizableColumns?: boolean;
|
|
220
225
|
|
|
221
226
|
// --- Selection ---
|
|
222
227
|
/**
|
package/src/wunderbaum.scss
CHANGED
|
@@ -183,6 +183,8 @@ div.wunderbaum {
|
|
|
183
183
|
position: sticky;
|
|
184
184
|
top: 0;
|
|
185
185
|
z-index: 2;
|
|
186
|
+
-webkit-user-select: none; /* Safari */
|
|
187
|
+
user-select: none;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
div.wb-header,
|
|
@@ -384,7 +386,12 @@ div.wunderbaum {
|
|
|
384
386
|
border: none;
|
|
385
387
|
border-right: 2px solid var(--wb-border-color);
|
|
386
388
|
height: 100%;
|
|
387
|
-
|
|
389
|
+
-webkit-user-select: none; // Safari
|
|
390
|
+
user-select: none;
|
|
391
|
+
&.wb-col-resizer-active {
|
|
392
|
+
cursor: col-resize;
|
|
393
|
+
// border-right-color: red;
|
|
394
|
+
}
|
|
388
395
|
}
|
|
389
396
|
}
|
|
390
397
|
|
|
@@ -405,6 +412,7 @@ div.wunderbaum {
|
|
|
405
412
|
}
|
|
406
413
|
|
|
407
414
|
span.wb-node {
|
|
415
|
+
-webkit-user-select: none; // Safari
|
|
408
416
|
user-select: none;
|
|
409
417
|
// &:first-of-type {
|
|
410
418
|
// margin-left: 8px; // leftmost icon gets a little margin
|
|
@@ -765,12 +773,12 @@ div.wunderbaum {
|
|
|
765
773
|
}
|
|
766
774
|
|
|
767
775
|
.wb-no-select {
|
|
776
|
+
-webkit-user-select: none; // Safari
|
|
768
777
|
user-select: none;
|
|
769
|
-
-webkit-user-select: none;
|
|
770
778
|
|
|
771
779
|
span.wb-title {
|
|
780
|
+
-webkit-user-select: contain; // Safari
|
|
772
781
|
user-select: contain;
|
|
773
|
-
-webkit-user-select: contain;
|
|
774
782
|
}
|
|
775
783
|
}
|
|
776
784
|
|
package/src/wunderbaum.ts
CHANGED
|
@@ -1602,6 +1602,14 @@ export class Wunderbaum {
|
|
|
1602
1602
|
}
|
|
1603
1603
|
}
|
|
1604
1604
|
|
|
1605
|
+
/** Reset column widths to default. */
|
|
1606
|
+
resetColumns() {
|
|
1607
|
+
this.columns.forEach((col) => {
|
|
1608
|
+
delete col.customWidthPx;
|
|
1609
|
+
});
|
|
1610
|
+
this.update(ChangeType.colStructure);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1605
1613
|
/**
|
|
1606
1614
|
* Make sure that this node is vertically scrolled into the viewport.
|
|
1607
1615
|
*
|
|
@@ -2026,7 +2034,7 @@ export class Wunderbaum {
|
|
|
2026
2034
|
this._columnsById = {};
|
|
2027
2035
|
for (const col of columns) {
|
|
2028
2036
|
this._columnsById[<string>col.id] = col;
|
|
2029
|
-
const cw = col.width;
|
|
2037
|
+
const cw = col.customWidthPx ? `${col.customWidthPx}px` : col.width;
|
|
2030
2038
|
if (col.id === "*" && col !== col0) {
|
|
2031
2039
|
throw new Error(
|
|
2032
2040
|
`Column id '*' must be defined only once: '${col.title}'.`
|
|
@@ -2139,7 +2147,12 @@ export class Wunderbaum {
|
|
|
2139
2147
|
}
|
|
2140
2148
|
let resizer = "";
|
|
2141
2149
|
if (i < colCount - 1) {
|
|
2142
|
-
|
|
2150
|
+
if (util.toBool(col.resizable, this.options.resizableColumns, false)) {
|
|
2151
|
+
resizer =
|
|
2152
|
+
'<span class="wb-col-resizer wb-col-resizer-active"></span>';
|
|
2153
|
+
} else {
|
|
2154
|
+
resizer = '<span class="wb-col-resizer"></span>';
|
|
2155
|
+
}
|
|
2143
2156
|
}
|
|
2144
2157
|
colElem.innerHTML = `<span class="wb-col-title"${tooltip}>${title}</span>${resizer}`;
|
|
2145
2158
|
if (this.isCellNav()) {
|
|
@@ -2229,6 +2242,10 @@ export class Wunderbaum {
|
|
|
2229
2242
|
}
|
|
2230
2243
|
|
|
2231
2244
|
if (this.options.connectTopBreadcrumb) {
|
|
2245
|
+
util.assert(
|
|
2246
|
+
this.options.connectTopBreadcrumb.textContent != null,
|
|
2247
|
+
`Invalid 'connectTopBreadcrumb' option (input element expected).`
|
|
2248
|
+
);
|
|
2232
2249
|
let path = this.getTopmostVpNode(true)?.getPath(false, "title", " > ");
|
|
2233
2250
|
path = path ? path + " >" : "";
|
|
2234
2251
|
this.options.connectTopBreadcrumb.textContent = path;
|