wunderbaum 0.10.0 → 0.11.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/src/wb_options.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  NodeTypeDefinitionMap,
21
21
  SelectModeType,
22
22
  WbActivateEventType,
23
+ WbButtonClickEventType,
23
24
  WbCancelableEventResultType,
24
25
  WbChangeEventType,
25
26
  WbClickEventType,
@@ -217,11 +218,30 @@ export interface WunderbaumOptions {
217
218
  * Default: false
218
219
  */
219
220
  fixedCol?: boolean;
221
+ /**
222
+ * Default value for ColumnDefinition.filterable option.
223
+ * Default: false
224
+ * @since 0.11.0
225
+ */
226
+ columnsFilterable?: boolean;
227
+ /**
228
+ * Default value for ColumnDefinition.menu option.
229
+ * Default: false
230
+ * @since 0.11.0
231
+ */
232
+ columnsMenu?: boolean;
220
233
  /**
221
234
  * Default value for ColumnDefinition.resizable option.
222
235
  * Default: false
236
+ * @since 0.10.0
237
+ */
238
+ columnsResizable?: boolean;
239
+ /**
240
+ * Default value for ColumnDefinition.sortable option.
241
+ * Default: false
242
+ * @since 0.11.0
223
243
  */
224
- resizableColumns?: boolean;
244
+ columnsSortable?: boolean;
225
245
 
226
246
  // --- Selection ---
227
247
  /**
@@ -271,11 +291,15 @@ export interface WunderbaumOptions {
271
291
  */
272
292
  beforeExpand?: (e: WbExpandEventType) => WbCancelableEventResultType;
273
293
  /**
274
- *
275
294
  * Return `false` to prevent default handling, i.e. (de)selecting the node.
276
295
  * @category Callback
277
296
  */
278
297
  beforeSelect?: (e: WbSelectEventType) => WbCancelableEventResultType;
298
+ /**
299
+ * Return `false` to prevent default handling, i.e. (de)selecting the node.
300
+ * @category Callback
301
+ */
302
+ buttonClick?: (e: WbButtonClickEventType) => void;
279
303
  /**
280
304
  *
281
305
  * @category Callback
@@ -183,8 +183,8 @@ div.wunderbaum {
183
183
  position: sticky;
184
184
  top: 0;
185
185
  z-index: 2;
186
- user-select: none;
187
186
  -webkit-user-select: none; /* Safari */
187
+ user-select: none;
188
188
  }
189
189
 
190
190
  div.wb-header,
@@ -386,13 +386,23 @@ div.wunderbaum {
386
386
  border: none;
387
387
  border-right: 2px solid var(--wb-border-color);
388
388
  height: 100%;
389
- user-select: none;
390
389
  -webkit-user-select: none; // Safari
390
+ user-select: none;
391
391
  &.wb-col-resizer-active {
392
392
  cursor: col-resize;
393
393
  // border-right-color: red;
394
394
  }
395
395
  }
396
+
397
+ i.wb-col-icon {
398
+ float: inline-end;
399
+ padding-left: 2px;
400
+
401
+ &:hover {
402
+ cursor: pointer;
403
+ color: var(--wb-focus-border-color);
404
+ }
405
+ }
396
406
  }
397
407
 
398
408
  span.wb-col {
@@ -412,8 +422,8 @@ div.wunderbaum {
412
422
  }
413
423
 
414
424
  span.wb-node {
415
- user-select: none;
416
425
  -webkit-user-select: none; // Safari
426
+ user-select: none;
417
427
  // &:first-of-type {
418
428
  // margin-left: 8px; // leftmost icon gets a little margin
419
429
  // }
@@ -773,12 +783,12 @@ div.wunderbaum {
773
783
  }
774
784
 
775
785
  .wb-no-select {
776
- user-select: none;
777
786
  -webkit-user-select: none; // Safari
787
+ user-select: none;
778
788
 
779
789
  span.wb-title {
780
- user-select: contain;
781
790
  -webkit-user-select: contain; // Safari
791
+ user-select: contain;
782
792
  }
783
793
  }
784
794
 
package/src/wunderbaum.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  SetActiveOptions,
48
48
  SetColumnOptions,
49
49
  SetStatusOptions,
50
+ SortByPropertyOptions,
50
51
  SortCallback,
51
52
  SourceType,
52
53
  UpdateOptions,
@@ -285,7 +286,7 @@ export class Wunderbaum {
285
286
  delete opts.types;
286
287
 
287
288
  // --- Create Markup
288
- this.element = util.elemFromSelector(opts.element) as HTMLDivElement;
289
+ this.element = util.elemFromSelector<HTMLDivElement>(opts.element)!;
289
290
  util.assert(!!this.element, `Invalid 'element' option: ${opts.element}`);
290
291
 
291
292
  this.element.classList.add("wunderbaum");
@@ -298,9 +299,8 @@ export class Wunderbaum {
298
299
 
299
300
  // Create header markup, or take it from the existing html
300
301
 
301
- this.headerElement = this.element.querySelector(
302
- "div.wb-header"
303
- ) as HTMLDivElement;
302
+ this.headerElement =
303
+ this.element.querySelector<HTMLDivElement>("div.wb-header")!;
304
304
 
305
305
  const wantHeader =
306
306
  opts.header == null ? this.columns.length > 1 : !!opts.header;
@@ -312,9 +312,8 @@ export class Wunderbaum {
312
312
  "`opts.columns` must not be set if markup already contains a header"
313
313
  );
314
314
  this.columns = [];
315
- const rowElement = this.headerElement.querySelector(
316
- "div.wb-row"
317
- ) as HTMLDivElement;
315
+ const rowElement =
316
+ this.headerElement.querySelector<HTMLDivElement>("div.wb-row")!;
318
317
  for (const colDiv of rowElement.querySelectorAll("div")) {
319
318
  this.columns.push({
320
319
  id: colDiv.dataset.id || `col_${this.columns.length}`,
@@ -337,9 +336,7 @@ export class Wunderbaum {
337
336
  </div>`;
338
337
 
339
338
  if (!wantHeader) {
340
- const he = this.element.querySelector(
341
- "div.wb-header"
342
- ) as HTMLDivElement;
339
+ const he = this.element.querySelector<HTMLDivElement>("div.wb-header")!;
343
340
  he.style.display = "none";
344
341
  }
345
342
  }
@@ -349,15 +346,15 @@ export class Wunderbaum {
349
346
  <div class="wb-list-container">
350
347
  <div class="wb-node-list"></div>
351
348
  </div>`;
352
- this.listContainerElement = this.element.querySelector(
349
+ this.listContainerElement = this.element.querySelector<HTMLDivElement>(
353
350
  "div.wb-list-container"
354
- ) as HTMLDivElement;
355
- this.nodeListElement = this.listContainerElement.querySelector(
356
- "div.wb-node-list"
357
- ) as HTMLDivElement;
358
- this.headerElement = this.element.querySelector(
359
- "div.wb-header"
360
- ) as HTMLDivElement;
351
+ )!;
352
+ this.nodeListElement =
353
+ this.listContainerElement.querySelector<HTMLDivElement>(
354
+ "div.wb-node-list"
355
+ )!;
356
+ this.headerElement =
357
+ this.element.querySelector<HTMLDivElement>("div.wb-header")!;
361
358
 
362
359
  this.element.classList.toggle("wb-grid", this.columns.length > 1);
363
360
 
@@ -418,6 +415,17 @@ export class Wunderbaum {
418
415
  });
419
416
  this.resizeObserver.observe(this.element);
420
417
 
418
+ util.onEvent(this.element, "click", ".wb-button,.wb-col-icon", (e) => {
419
+ const info = Wunderbaum.getEventInfo(e);
420
+ const command = (<HTMLElement>e.target)?.dataset?.command;
421
+
422
+ this._callEvent("buttonClick", {
423
+ event: e,
424
+ info: info,
425
+ command: command,
426
+ });
427
+ });
428
+
421
429
  util.onEvent(this.nodeListElement, "click", "div.wb-row", (e) => {
422
430
  const info = Wunderbaum.getEventInfo(e);
423
431
  const node = info.node;
@@ -1078,7 +1086,7 @@ export class Wunderbaum {
1078
1086
 
1079
1087
  /** Run code, but defer rendering of viewport until done.
1080
1088
  *
1081
- * ```
1089
+ * ```js
1082
1090
  * tree.runWithDeferredUpdate(() => {
1083
1091
  * return someFuncThatWouldUpdateManyNodes();
1084
1092
  * });
@@ -1602,7 +1610,7 @@ export class Wunderbaum {
1602
1610
  }
1603
1611
  }
1604
1612
 
1605
- /** Reset column widths to default. */
1613
+ /** Reset column widths to default. @since 0.10.0 */
1606
1614
  resetColumns() {
1607
1615
  this.columns.forEach((col) => {
1608
1616
  delete col.customWidthPx;
@@ -1610,6 +1618,11 @@ export class Wunderbaum {
1610
1618
  this.update(ChangeType.colStructure);
1611
1619
  }
1612
1620
 
1621
+ // /** Renumber nodes `_nativeIndex`. @see {@link WunderbaumNode.resetNativeChildOrder} */
1622
+ // resetNativeChildOrder(options?: ResetOrderOptions) {
1623
+ // this.root.resetNativeChildOrder(options);
1624
+ // }
1625
+
1613
1626
  /**
1614
1627
  * Make sure that this node is vertically scrolled into the viewport.
1615
1628
  *
@@ -1790,15 +1803,15 @@ export class Wunderbaum {
1790
1803
  * The render operation is async and debounced unless the `immediate` option
1791
1804
  * is set.
1792
1805
  *
1793
- * Use {@link WunderbaumNode.update()} if only a single node has changed,
1794
- * or {@link WunderbaumNode._render()}) to pass special options.
1806
+ * Use {@link WunderbaumNode.update} if only a single node has changed,
1807
+ * or {@link WunderbaumNode._render}) to pass special options.
1795
1808
  */
1796
1809
  update(change: ChangeType, options?: UpdateOptions): void;
1797
1810
 
1798
1811
  /**
1799
1812
  * Update a row to reflect a single node's modification.
1800
1813
  *
1801
- * @see {@link WunderbaumNode.update()}, {@link WunderbaumNode._render()}
1814
+ * @see {@link WunderbaumNode.update}, {@link WunderbaumNode._render}
1802
1815
  */
1803
1816
  update(
1804
1817
  change: ChangeType,
@@ -1988,6 +2001,15 @@ export class Wunderbaum {
1988
2001
  this.root.sortChildren(cmp, deep);
1989
2002
  }
1990
2003
 
2004
+ /**
2005
+ * Convenience method to implement column sorting.
2006
+ * @see {@link WunderbaumNode.sortByProperty}.
2007
+ * @since 0.11.0
2008
+ */
2009
+ sortByProperty(options: SortByPropertyOptions) {
2010
+ this.root.sortByProperty(options);
2011
+ }
2012
+
1991
2013
  /** Convert tree to an array of plain objects.
1992
2014
  *
1993
2015
  * @param callback is called for every node, in order to allow
@@ -2109,6 +2131,12 @@ export class Wunderbaum {
2109
2131
  return modified;
2110
2132
  }
2111
2133
 
2134
+ protected _insertIcon(icon: string, elem: HTMLElement) {
2135
+ const iconElem = document.createElement("i");
2136
+ iconElem.className = icon;
2137
+ elem.appendChild(iconElem);
2138
+ }
2139
+
2112
2140
  /** Create/update header markup from `this.columns` definition.
2113
2141
  * @internal
2114
2142
  */
@@ -2119,6 +2147,7 @@ export class Wunderbaum {
2119
2147
  if (!wantHeader) {
2120
2148
  return;
2121
2149
  }
2150
+ const iconMap = this.iconMap;
2122
2151
  const colCount = this.columns.length;
2123
2152
  const headerRow = this.headerElement.querySelector(".wb-row")!;
2124
2153
  util.assert(headerRow, "Expected a row in header element");
@@ -2139,22 +2168,55 @@ export class Wunderbaum {
2139
2168
  col.classes ? colElem.classList.add(...col.classes.split(" ")) : 0;
2140
2169
  }
2141
2170
 
2142
- const title = util.escapeHtml(col.title || col.id);
2171
+ // Add tooltip to column title
2143
2172
  let tooltip = "";
2144
2173
  if (col.tooltip) {
2145
2174
  tooltip = util.escapeTooltip(col.tooltip);
2146
2175
  tooltip = ` title="${tooltip}"`;
2147
2176
  }
2148
- let resizer = "";
2177
+ // Add column header icons
2178
+ let addMarkup = "";
2179
+ // NOTE: we use CSS float: right to align icons, so they must be added in
2180
+ // reverse order
2181
+ if (util.toBool(col.menu, this.options.columnsMenu, false)) {
2182
+ const iconClass = "wb-col-icon-menu " + iconMap.colMenu;
2183
+ const icon = `<i data-command=menu class="wb-col-icon ${iconClass}"></i>`;
2184
+ addMarkup += icon;
2185
+ }
2186
+ if (util.toBool(col.sortable, this.options.columnsSortable, false)) {
2187
+ let iconClass = "wb-col-icon-sort " + iconMap.colSortable;
2188
+ if (col.sortOrder) {
2189
+ iconClass += `wb-col-sort-${col.sortOrder}`;
2190
+ iconClass +=
2191
+ col.sortOrder === "asc" ? iconMap.colSortAsc : iconMap.colSortDesc;
2192
+ }
2193
+ const icon = `<i data-command=sort class="wb-col-icon ${iconClass}"></i>`;
2194
+ addMarkup += icon;
2195
+ }
2196
+ if (util.toBool(col.filterable, this.options.columnsFilterable, false)) {
2197
+ colElem.classList.toggle("wb-col-filter", !!col.filterActive);
2198
+ let iconClass = "wb-col-icon-filter " + iconMap.colFilter;
2199
+ if (col.filterActive) {
2200
+ iconClass += iconMap.colFilterActive;
2201
+ }
2202
+ const icon = `<i data-command=filter class="wb-col-icon ${iconClass}"></i>`;
2203
+ addMarkup += icon;
2204
+ }
2205
+ // Add resizer to all but the last column
2149
2206
  if (i < colCount - 1) {
2150
- if (util.toBool(col.resizable, this.options.resizableColumns, false)) {
2151
- resizer =
2207
+ if (util.toBool(col.resizable, this.options.columnsResizable, false)) {
2208
+ addMarkup +=
2152
2209
  '<span class="wb-col-resizer wb-col-resizer-active"></span>';
2153
2210
  } else {
2154
- resizer = '<span class="wb-col-resizer"></span>';
2211
+ addMarkup += '<span class="wb-col-resizer"></span>';
2155
2212
  }
2156
2213
  }
2157
- colElem.innerHTML = `<span class="wb-col-title"${tooltip}>${title}</span>${resizer}`;
2214
+
2215
+ // Create column header
2216
+ const title = util.escapeHtml(col.title || col.id);
2217
+ colElem.innerHTML = `<span class="wb-col-title"${tooltip}>${title}</span>${addMarkup}`;
2218
+
2219
+ // Highlight active column
2158
2220
  if (this.isCellNav()) {
2159
2221
  colElem.classList.toggle("wb-active", i === this.activeColIdx);
2160
2222
  }
@@ -2242,6 +2304,10 @@ export class Wunderbaum {
2242
2304
  }
2243
2305
 
2244
2306
  if (this.options.connectTopBreadcrumb) {
2307
+ util.assert(
2308
+ this.options.connectTopBreadcrumb.textContent != null,
2309
+ `Invalid 'connectTopBreadcrumb' option (input element expected).`
2310
+ );
2245
2311
  let path = this.getTopmostVpNode(true)?.getPath(false, "title", " > ");
2246
2312
  path = path ? path + " >" : "";
2247
2313
  this.options.connectTopBreadcrumb.textContent = path;
@@ -2629,6 +2695,7 @@ export class Wunderbaum {
2629
2695
  /**
2630
2696
  * Return the number of nodes that match the current filter.
2631
2697
  * @see {@link Wunderbaum.filterNodes}
2698
+ * @since 0.9.0
2632
2699
  */
2633
2700
  countMatches(): number {
2634
2701
  return (this.extensions.filter as FilterExtension).countMatches();