wunderbaum 0.0.9 → 0.1.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/src/wb_node.ts CHANGED
@@ -11,17 +11,20 @@ import { Wunderbaum } from "./wunderbaum";
11
11
  import {
12
12
  AddChildrenOptions,
13
13
  AddNodeType,
14
+ ApplyCommandOptions,
14
15
  ApplyCommandType,
15
16
  ChangeType,
16
- ColumnEventInfos,
17
+ ColumnEventInfoMap,
17
18
  ExpandAllOptions,
18
19
  MakeVisibleOptions,
19
20
  MatcherCallback,
21
+ NavigateOptions,
20
22
  NodeAnyCallback,
21
23
  NodeStatusType,
22
24
  NodeStringCallback,
23
25
  NodeVisitCallback,
24
26
  NodeVisitResponse,
27
+ RenderOptions,
25
28
  ScrollIntoViewOptions,
26
29
  SetActiveOptions,
27
30
  SetExpandedOptions,
@@ -356,8 +359,8 @@ export class WunderbaumNode {
356
359
  *
357
360
  * @see {@link Wunderbaum.applyCommand}
358
361
  */
359
- applyCommand(cmd: ApplyCommandType, opts: any): any {
360
- return this.tree.applyCommand(cmd, this, opts);
362
+ applyCommand(cmd: ApplyCommandType, options: ApplyCommandOptions): any {
363
+ return this.tree.applyCommand(cmd, this, options);
361
364
  }
362
365
 
363
366
  /**
@@ -580,7 +583,7 @@ export class WunderbaumNode {
580
583
  }
581
584
 
582
585
  /**
583
- * Return multiline string representation of a node/subnode hierarchy.
586
+ * Return a multiline string representation of a node/subnode hierarchy.
584
587
  * Mostly useful for debugging.
585
588
  *
586
589
  * Example:
@@ -592,7 +595,6 @@ export class WunderbaumNode {
592
595
  * Books
593
596
  * ├─ Art of War
594
597
  * ╰─ Don Quixote
595
- * ...
596
598
  * ```
597
599
  * @see {@link WunderbaumNode.format_iter}
598
600
  */
@@ -736,11 +738,18 @@ export class WunderbaumNode {
736
738
  return this.tree.activeNode === this;
737
739
  }
738
740
 
739
- /** Return true if this node is a *direct* child of `other`.
741
+ /** Return true if this node is a direct or indirect parent of `other`.
742
+ * (See also [[isParentOf]].)
743
+ */
744
+ isAncestorOf(other: WunderbaumNode) {
745
+ return other && other.isDescendantOf(this);
746
+ }
747
+
748
+ /** Return true if this node is a **direct** subnode of `other`.
740
749
  * (See also [[isDescendantOf]].)
741
750
  */
742
751
  isChildOf(other: WunderbaumNode) {
743
- return this.parent && this.parent === other;
752
+ return other && this.parent === other;
744
753
  }
745
754
 
746
755
  /** Return true if this node's title spans all columns, i.e. the node has no
@@ -750,7 +759,7 @@ export class WunderbaumNode {
750
759
  return !!this.getOption("colspan");
751
760
  }
752
761
 
753
- /** Return true if this node is a direct or indirect sub node of `other`.
762
+ /** Return true if this node is a direct or indirect subnode of `other`.
754
763
  * (See also [[isChildOf]].)
755
764
  */
756
765
  isDescendantOf(other: WunderbaumNode) {
@@ -763,7 +772,7 @@ export class WunderbaumNode {
763
772
  return true;
764
773
  }
765
774
  if (p === p.parent) {
766
- util.error("Recursive parent link: " + p);
775
+ util.error(`Recursive parent link: ${p}`);
767
776
  }
768
777
  p = p.parent;
769
778
  }
@@ -821,6 +830,13 @@ export class WunderbaumNode {
821
830
  return this.statusNodeType === "paging";
822
831
  }
823
832
 
833
+ /** Return true if this node is a **direct** parent of `other`.
834
+ * (See also [[isAncestorOf]].)
835
+ */
836
+ isParentOf(other: WunderbaumNode) {
837
+ return other && other.parent === this;
838
+ }
839
+
824
840
  /** (experimental) Return true if this node is partially loaded. */
825
841
  isPartload(): boolean {
826
842
  return !!this._partload;
@@ -1257,7 +1273,7 @@ export class WunderbaumNode {
1257
1273
  * e.g. `ArrowLeft` = 'left'.
1258
1274
  * @param options
1259
1275
  */
1260
- async navigate(where: string, options?: any) {
1276
+ async navigate(where: string, options?: NavigateOptions) {
1261
1277
  // Allow to pass 'ArrowLeft' instead of 'left'
1262
1278
  where = KEY_TO_ACTION_DICT[where] || where;
1263
1279
 
@@ -1334,8 +1350,8 @@ export class WunderbaumNode {
1334
1350
  }
1335
1351
 
1336
1352
  protected _getRenderInfo(): any {
1337
- const allColInfosById: ColumnEventInfos = {};
1338
- const renderColInfosById: ColumnEventInfos = {};
1353
+ const allColInfosById: ColumnEventInfoMap = {};
1354
+ const renderColInfosById: ColumnEventInfoMap = {};
1339
1355
  const isColspan = this.isColspan();
1340
1356
 
1341
1357
  const colElems = this._rowElem
@@ -1421,7 +1437,7 @@ export class WunderbaumNode {
1421
1437
  * Create a whole new `<div class="wb-row">` element.
1422
1438
  * @see {@link WunderbaumNode.render}
1423
1439
  */
1424
- protected _render_markup(opts: any) {
1440
+ protected _render_markup(opts: RenderOptions) {
1425
1441
  const tree = this.tree;
1426
1442
  const treeOptions = tree.options;
1427
1443
  const checkbox = this.getOption("checkbox") !== false;
@@ -1556,13 +1572,14 @@ export class WunderbaumNode {
1556
1572
  *
1557
1573
  * @see {@link WunderbaumNode.render}
1558
1574
  */
1559
- protected _render_data(opts: any) {
1575
+ protected _render_data(opts: RenderOptions) {
1560
1576
  util.assert(this._rowElem);
1561
1577
 
1562
1578
  const tree = this.tree;
1563
1579
  const treeOptions = tree.options;
1564
1580
  const rowDiv = this._rowElem!;
1565
1581
  const isNew = !!opts.isNew; // Called by _render_markup()?
1582
+ const preventScroll = !!opts.preventScroll;
1566
1583
  const columns = tree.columns;
1567
1584
  const isColspan = this.isColspan();
1568
1585
 
@@ -1572,10 +1589,16 @@ export class WunderbaumNode {
1572
1589
  "span.wb-title"
1573
1590
  ) as HTMLSpanElement;
1574
1591
 
1592
+ const scrollTop = tree.element.scrollTop;
1575
1593
  if (this.titleWithHighlight) {
1576
1594
  titleSpan.innerHTML = this.titleWithHighlight;
1577
1595
  } else {
1578
- titleSpan.textContent = this.title;
1596
+ titleSpan.textContent = this.title; // TODO: this triggers scroll events
1597
+ }
1598
+ // NOTE: At least on Safari, this render call triggers a scroll event
1599
+ // probably when a focused input is replaced.
1600
+ if (preventScroll) {
1601
+ tree.element.scrollTop = scrollTop;
1579
1602
  }
1580
1603
 
1581
1604
  // Set the width of the title span, so overflow ellipsis work
@@ -1602,6 +1625,7 @@ export class WunderbaumNode {
1602
1625
  this._callEvent("renderStatusNode", {
1603
1626
  isNew: isNew,
1604
1627
  nodeElem: nodeElem,
1628
+ isColspan: isColspan,
1605
1629
  });
1606
1630
  } else if (this.parent) {
1607
1631
  // Skip root node
@@ -1609,9 +1633,8 @@ export class WunderbaumNode {
1609
1633
 
1610
1634
  this._callEvent("render", {
1611
1635
  isNew: isNew,
1612
- isColspan: isColspan,
1613
- // isDataChange: true,
1614
1636
  nodeElem: nodeElem,
1637
+ isColspan: isColspan,
1615
1638
  allColInfosById: renderInfo.allColInfosById,
1616
1639
  renderColInfosById: renderInfo.renderColInfosById,
1617
1640
  });
@@ -1622,7 +1645,7 @@ export class WunderbaumNode {
1622
1645
  * Update row classes to reflect active, focuses, etc.
1623
1646
  * @see {@link WunderbaumNode.render}
1624
1647
  */
1625
- protected _render_status(opts: any) {
1648
+ protected _render_status(opts: RenderOptions) {
1626
1649
  // this.log("_render_status", opts);
1627
1650
  const tree = this.tree;
1628
1651
  const treeOptions = tree.options;
@@ -1700,6 +1723,19 @@ export class WunderbaumNode {
1700
1723
  this._createIcon(nodeElem, iconSpan, !expanderSpan);
1701
1724
  }
1702
1725
  }
1726
+ // Adjust column width
1727
+ if (opts.resizeCols !== false && !this.isColspan()) {
1728
+ const colElems = rowDiv.querySelectorAll("span.wb-col");
1729
+ let idx = 0;
1730
+ let ofs = 0;
1731
+ for (let colDef of this.tree.columns) {
1732
+ const colElem = colElems[idx] as HTMLSpanElement;
1733
+ colElem.style.left = `${ofs}px`;
1734
+ colElem.style.width = `${colDef._widthPx}px`;
1735
+ idx++;
1736
+ ofs += colDef._widthPx!;
1737
+ }
1738
+ }
1703
1739
  }
1704
1740
 
1705
1741
  /**
@@ -1716,11 +1752,11 @@ export class WunderbaumNode {
1716
1752
  * Calling `setModified` instead may be a better alternative.
1717
1753
  * @see {@link WunderbaumNode.setModified}
1718
1754
  */
1719
- render(options?: any) {
1755
+ render(options?: RenderOptions) {
1720
1756
  // this.log("render", options);
1721
1757
  const opts = Object.assign({ change: ChangeType.data }, options);
1722
1758
  if (!this._rowElem) {
1723
- opts.change = "row";
1759
+ opts.change = ChangeType.row;
1724
1760
  }
1725
1761
  switch (opts.change) {
1726
1762
  case "status":
@@ -1882,11 +1918,11 @@ export class WunderbaumNode {
1882
1918
  if (
1883
1919
  prev?._callEvent("deactivate", {
1884
1920
  nextNode: this,
1885
- orgEvent: orgEvent,
1921
+ event: orgEvent,
1886
1922
  }) === false ||
1887
1923
  this._callEvent("beforeActivate", {
1888
1924
  prevNode: prev,
1889
- orgEvent: orgEvent,
1925
+ event: orgEvent,
1890
1926
  }) === false
1891
1927
  ) {
1892
1928
  return;
@@ -1895,7 +1931,7 @@ export class WunderbaumNode {
1895
1931
  prev?.setModified(ChangeType.status);
1896
1932
  }
1897
1933
  } else if (prev === this || retrigger) {
1898
- this._callEvent("deactivate", { nextNode: null, orgEvent: orgEvent });
1934
+ this._callEvent("deactivate", { nextNode: null, event: orgEvent });
1899
1935
  }
1900
1936
  }
1901
1937
 
@@ -1917,7 +1953,7 @@ export class WunderbaumNode {
1917
1953
  tree.setColumn(options.colIdx);
1918
1954
  }
1919
1955
  if (flag && !noEvents) {
1920
- this._callEvent("activate", { prevNode: prev, orgEvent: orgEvent });
1956
+ this._callEvent("activate", { prevNode: prev, event: orgEvent });
1921
1957
  }
1922
1958
  return this.makeVisible();
1923
1959
  }
package/src/wb_options.ts CHANGED
@@ -8,9 +8,18 @@ import {
8
8
  BoolOptionResolver,
9
9
  ColumnDefinitionList,
10
10
  DndOptionsType,
11
- NavigationOptions,
12
- NodeTypeDefinitions,
11
+ NavModeEnum,
12
+ NodeTypeDefinitionMap,
13
+ WbActivateEventType,
14
+ WbChangeEventType,
15
+ WbClickEventType,
16
+ WbDeactivateEventType,
17
+ WbEnhanceTitleEventType,
18
+ WbErrorEventType,
19
+ WbInitEventType,
20
+ WbKeydownEventType,
13
21
  WbNodeEventType,
22
+ WbReceiveEventType,
14
23
  WbRenderEventType,
15
24
  WbTreeEventType,
16
25
  } from "./types";
@@ -22,6 +31,7 @@ export interface WbNodeData {
22
31
  expanded?: boolean;
23
32
  selected?: boolean;
24
33
  checkbox?: boolean | string;
34
+ colspan?: boolean;
25
35
  children?: Array<WbNodeData>;
26
36
  // ...any?: Any;
27
37
  }
@@ -82,7 +92,7 @@ export interface WunderbaumOptions {
82
92
  *
83
93
  * Default: `{}`.
84
94
  */
85
- types?: NodeTypeDefinitions; // { [key: string]: NodeTypeDefinition };
95
+ types?: NodeTypeDefinitionMap; // { [key: string]: NodeTypeDefinition };
86
96
  /**
87
97
  * A list of maps that define column headers. If this option is set,
88
98
  * Wunderbaum becomes a treegrid control instead of a plain tree.
@@ -135,9 +145,9 @@ export interface WunderbaumOptions {
135
145
  */
136
146
  connectTopBreadcrumb?: HTMLElement;
137
147
  /**
138
- * Default: NavigationOptions.startRow
148
+ * Default: NavModeEnum.startRow
139
149
  */
140
- navigationModeOption?: NavigationOptions;
150
+ navigationModeOption?: NavModeEnum;
141
151
  /**
142
152
  * Show/hide header (default: null)
143
153
  * null: assume false for plain tree and true for grids.
@@ -187,32 +197,36 @@ export interface WunderbaumOptions {
187
197
  *
188
198
  * @category Callback
189
199
  */
190
- activate?: (e: WbNodeEventType) => void;
200
+ activate?: (e: WbActivateEventType) => void;
191
201
  /**
192
202
  *
203
+ * Return `false` to prevent default handling, e.g. activating the node.
193
204
  * @category Callback
194
205
  */
195
- change?: (e: WbNodeEventType) => void;
206
+ beforeActivate?: (e: WbActivateEventType) => void;
196
207
  /**
197
208
  *
198
- * Return `false` to prevent default handling, e.g. activating the node.
199
209
  * @category Callback
200
210
  */
201
- click?: (e: WbTreeEventType) => void;
211
+ change?: (e: WbChangeEventType) => void;
202
212
  /**
203
213
  *
204
214
  * Return `false` to prevent default handling, e.g. activating the node.
205
215
  * @category Callback
206
216
  */
207
- beforeActivate?: (e: WbNodeEventType) => void;
217
+ click?: (e: WbClickEventType) => void;
208
218
  /**
219
+ *
220
+ * @category Callback
221
+ */
222
+ dblclick?: (e: WbClickEventType) => void;
209
223
  /**
210
224
  *
211
225
  * Return `false` to prevent default handling, e.g. deactivating the node
212
226
  * and activating the next.
213
227
  * @category Callback
214
228
  */
215
- deactivate?: (e: WbNodeEventType) => void;
229
+ deactivate?: (e: WbDeactivateEventType) => void;
216
230
  /**
217
231
  *
218
232
  * @category Callback
@@ -222,12 +236,12 @@ export interface WunderbaumOptions {
222
236
  *
223
237
  * @category Callback
224
238
  */
225
- enhanceTitle?: (e: WbNodeEventType) => void;
239
+ enhanceTitle?: (e: WbEnhanceTitleEventType) => void;
226
240
  /**
227
241
  *
228
242
  * @category Callback
229
243
  */
230
- error?: (e: WbTreeEventType) => void;
244
+ error?: (e: WbErrorEventType) => void;
231
245
  /**
232
246
  *
233
247
  * Check `e.flag` for status.
@@ -241,12 +255,12 @@ export interface WunderbaumOptions {
241
255
  * Check `e.error` for status.
242
256
  * @category Callback
243
257
  */
244
- init?: (e: WbTreeEventType) => void;
258
+ init?: (e: WbInitEventType) => void;
245
259
  /**
246
260
  *
247
261
  * @category Callback
248
262
  */
249
- keydown?: (e: WbNodeEventType) => void;
263
+ keydown?: (e: WbKeydownEventType) => void;
250
264
  /**
251
265
  * Fires when a node that was marked 'lazy', is expanded for the first time.
252
266
  * Typically we return an endpoint URL or the Promise of a fetch request that
@@ -271,7 +285,7 @@ export interface WunderbaumOptions {
271
285
  * external response to native Wunderbaum syntax.
272
286
  * @category Callback
273
287
  */
274
- receive?: (e: WbNodeEventType) => void;
288
+ receive?: (e: WbReceiveEventType) => void;
275
289
  /**
276
290
  * Fires when a node is about to be displayed.
277
291
  * The default HTML markup is already created, but not yet added to the DOM.
@@ -285,7 +299,7 @@ export interface WunderbaumOptions {
285
299
  *
286
300
  * @category Callback
287
301
  */
288
- renderStatusNode?: (e: WbNodeEventType) => void;
302
+ renderStatusNode?: (e: WbRenderEventType) => void;
289
303
  /**
290
304
  *
291
305
  * Check `e.flag` for status.
@@ -27,7 +27,7 @@ $hover-color: adjust-color($bg-highlight-color, $lightness: 48%); // #f7f7f7;
27
27
  $hover-border-color: $hover-color;
28
28
  $grid-color: #dedede;
29
29
  $active-color: #e5f3fb;
30
- $active-cell-color: $bg-highlight-color;
30
+ $active-cell-color: adjust-color($bg-highlight-color, $lightness: 20%);
31
31
  $active-border-color: #70c0e7;
32
32
  $active-hover-color: #dceff8;
33
33
  $active-hover-border-color: $bg-highlight-color;
@@ -77,6 +77,7 @@ div.wunderbaum {
77
77
  // TODO:auto would be better, but may cause unwanted effects when auto-resizing?
78
78
  // overflow-y: auto;
79
79
  overflow-y: scroll;
80
+ // scroll-behavior: smooth;
80
81
 
81
82
  // Focus border is generated by the browsers per default:
82
83
  &:focus,
@@ -90,10 +91,9 @@ div.wunderbaum {
90
91
  pointer-events: none;
91
92
  }
92
93
 
93
- div.wb-scroll-container {
94
+ div.wb-list-container {
94
95
  position: relative;
95
96
  // overflow: auto;
96
- // scroll-behavior: smooth;
97
97
  min-height: 4px;
98
98
  // height: calc(100% - #{$header-height}); // didn't work. Using JS instead
99
99
  }
@@ -107,35 +107,62 @@ div.wunderbaum {
107
107
  }
108
108
 
109
109
  div.wb-header,
110
- div.wb-scroll-container {
110
+ div.wb-list-container {
111
111
  overflow: unset;
112
112
  }
113
113
 
114
114
  // }
115
+ // --- Header and node list ---
116
+ div.wb-row {
117
+ position: absolute;
118
+ width: 100%;
119
+ height: $row-outer-height;
120
+ line-height: $row-outer-height;
121
+ border: 1px solid transparent;
122
+ }
115
123
 
116
- /* Fixed column must be opaque, i.e. have the bg color set.*/
124
+ /* Fixed column must be opaque, i.e. have the bg color set. */
117
125
  &.wb-fixed-col {
118
- div.wb-header {
119
- span.wb-col:first-of-type {
120
- background-color: $header-color;
121
- }
122
- }
123
-
126
+ /* Sticky firdt column (header and nodes) */
124
127
  span.wb-col:first-of-type {
125
128
  position: sticky;
126
129
  left: 0;
127
130
  z-index: 1;
128
131
  background-color: $background-color;
129
132
  }
130
- }
131
133
 
132
- // --- Header and node list ---
133
- div.wb-row {
134
- position: absolute;
135
- width: 100%;
136
- height: $row-outer-height;
137
- line-height: $row-outer-height;
138
- border: 1px solid transparent;
134
+ div.wb-header {
135
+ span.wb-col:first-of-type {
136
+ background-color: $header-color;
137
+ }
138
+ }
139
+ div.wb-node-list div.wb-row {
140
+ &.wb-active span.wb-col:first-of-type,
141
+ &.wb-selected span.wb-col:first-of-type {
142
+ background-color: $active-color;
143
+ }
144
+ &.wb-active:hover span.wb-col:first-of-type,
145
+ &.wb-selected:hover span.wb-col:first-of-type {
146
+ background-color: $active-hover-color;
147
+ }
148
+ &:hover span.wb-col:first-of-type {
149
+ background-color: $hover-color;
150
+ }
151
+ }
152
+
153
+ &:not(:focus-within),
154
+ &:not(:focus) {
155
+ div.wb-node-list div.wb-row {
156
+ &.wb-active span.wb-col:first-of-type,
157
+ &.wb-selected span.wb-col:first-of-type {
158
+ background-color: grayscale($active-color);
159
+ border-color: grayscale($active-border-color);
160
+ &:hover span.wb-col:first-of-type {
161
+ background-color: grayscale($active-hover-color);
162
+ }
163
+ }
164
+ }
165
+ }
139
166
  }
140
167
 
141
168
  // --- Node List Only ---
@@ -349,6 +376,7 @@ div.wunderbaum {
349
376
  }
350
377
 
351
378
  /* --- GRID --- */
379
+
352
380
  &.wb-grid {
353
381
  div.wb-header {
354
382
  div.wb-row {
@@ -417,6 +445,16 @@ div.wunderbaum {
417
445
 
418
446
  &.wb-cell-mode {
419
447
  div.wb-row:not(.wb-colspan) {
448
+ &.wb-active {
449
+ span.wb-col.wb-active {
450
+ background-color: grayscale($active-cell-color);
451
+ }
452
+ }
453
+ }
454
+ }
455
+ &.wb-cell-mode:focus-within,
456
+ &.wb-cell-mode:focus {
457
+ div.wb-row:not(.wb-colspan):not(.wb-selected) {
420
458
  // background-color: $background-color;
421
459
 
422
460
  span.wb-col.wb-active {
@@ -453,6 +491,7 @@ div.wunderbaum {
453
491
  }
454
492
 
455
493
  /* --- FILTER --- */
494
+
456
495
  &.wb-ext-filter-dim,
457
496
  &.wb-ext-filter-hide {
458
497
  div.wb-node-list div.wb-row {
@@ -476,6 +515,7 @@ div.wunderbaum {
476
515
  // }
477
516
 
478
517
  /* --- DND --- */
518
+
479
519
  div.wb-row.wb-drag-source {
480
520
  opacity: 0.5;
481
521
 
@@ -531,6 +571,9 @@ div.wunderbaum {
531
571
  top: ($row-outer-height - 16) / 2 + $row-outer-height / 2;
532
572
  }
533
573
 
574
+ /* --- SPECIAL EFFECTS --- */
575
+
576
+ /* Colorize indentation levels. */
534
577
  &.wb-rainbow {
535
578
  @for $i from 1 through length($level-rainbow) {
536
579
  i.wb-expander:nth-child(#{length($level-rainbow)}n + #{$i}),
@@ -592,8 +635,6 @@ div.wunderbaum {
592
635
  }
593
636
  }
594
637
 
595
- // div.wunderbaum
596
-
597
638
  /* --- TOOL CLASSES --- */
598
639
 
599
640
  .wb-helper-center {