wunderbaum 0.0.1-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.
@@ -0,0 +1,117 @@
1
+ /*!
2
+ * Wunderbaum - utils
3
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
+ * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
+ */
6
+
7
+ import {
8
+ BoolOptionResolver,
9
+ NavigationModeOption,
10
+ WbEventType,
11
+ } from "./common";
12
+
13
+ export interface WbNodeData {
14
+ title: string;
15
+ key?: string;
16
+ refKey?: string;
17
+ expanded?: boolean;
18
+ selected?: boolean;
19
+ checkbox?: boolean | "radio" | BoolOptionResolver;
20
+ children?: Array<WbNodeData>;
21
+ // ...any?: Any;
22
+ }
23
+
24
+ /**
25
+ * Available options for [[Wunderbaum]].
26
+ *
27
+ * Options are passed to the constructor as plain object:
28
+ *
29
+ * ```js
30
+ * const tree = new mar10.Wunderbaum({
31
+ * id: "demo",
32
+ * element: document.querySelector("#demo-tree"),
33
+ * source: "url/of/data/request",
34
+ * ...
35
+ * });
36
+ * ```
37
+ */
38
+ export interface WunderbaumOptions {
39
+ /**
40
+ * The target `div` element (or selector) that shall become a Wunderbaum.
41
+ */
42
+ element: string | HTMLDivElement;
43
+ /**
44
+ * The identifier of this tree. Used to reference the instance, especially
45
+ * when multiple trees are present (e.g. `tree = mar10.Wunderbaum.getTree("demo")`).
46
+ *
47
+ * Default: `"wb_" + COUNTER`.
48
+ */
49
+ id?: string;
50
+ /**
51
+ * Define the initial tree data. Typically a URL of an endpoint that serves
52
+ * a JSON formatted structure, but also a callback, Promise, or static data
53
+ * is allowed.
54
+ *
55
+ * Default: `{}`.
56
+ */
57
+ source?: string | Array<WbNodeData>;
58
+ /**
59
+ * Define shared attributes for multiple nodes of the same type.
60
+ * This allows for more compact data models. Type definitions can be passed
61
+ * as tree option, or be part of a `source` response.
62
+ *
63
+ * Default: `{}`.
64
+ */
65
+ types: any; //[key: string]: any;
66
+ /**
67
+ * A list of maps that define column headers. If this option is set,
68
+ * Wunderbaum becomes a tree-grid control instead of a plain tree.
69
+ * Column definitions can be passed as tree option, or be part of a `source`
70
+ * response.
71
+ * Default: `[]` meaning this is a plain tree.
72
+ */
73
+ columns?: Array<any>;
74
+ /**
75
+ *
76
+ * Default: false.
77
+ */
78
+ skeleton: false;
79
+ /**
80
+ *
81
+ * Default: false.
82
+ */
83
+ strings: any; //[key: string] string;
84
+ /**
85
+ */
86
+ debugLevel: 3;
87
+ minExpandLevel: 0;
88
+ escapeTitles: true;
89
+ headerHeightPx: 22;
90
+ autoCollapse: false;
91
+ // --- Extensions ---
92
+ dnd: any; // = {};
93
+ filter: any; // = {};
94
+ /**
95
+ * A list of maps that define column headers. If this option is set,
96
+ * Wunderbaum becomes a tree grid control instead of a plain tree.
97
+ * Column definitions can be passed as tree option, or be part of a `source`
98
+ * response.
99
+ */
100
+ navigationMode?: NavigationModeOption;
101
+ // --- Events ---
102
+ /**
103
+ * Called after initial data was loaded and tree markup was rendered.
104
+ * @category Callback
105
+ */
106
+ init?: (e: WbEventType) => void;
107
+ /**
108
+ * Called after data was loaded from local storage.
109
+ * @category Callback
110
+ */
111
+ update?: (e: WbEventType) => void;
112
+ /**
113
+ * Called after data was loaded from local storage.
114
+ * @category Callback
115
+ */
116
+ modifyChild?: (e: WbEventType) => void;
117
+ }
@@ -0,0 +1,509 @@
1
+ /*!
2
+ * Wunderbaum style sheet (generated from wunderbaum.scss)
3
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
+ * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
+ */
6
+
7
+ $font-stack: Helvetica, sans-serif;
8
+
9
+ // Basic Theme Colors
10
+ $error-color: #b5373b;
11
+ $node-text-color: #56534c;
12
+ $bg-highlight-color: #26a0da;
13
+ $header-color: #dedede;
14
+ $alternate-row-color: #fcfcfc;
15
+ $alternate-row-color-even: #f7fcfe;
16
+ // derived
17
+ $border-color: $node-text-color;
18
+ $drop-source-color: adjust-color($node-text-color, $lightness: 50%);
19
+ $drop-target-color: adjust-color($bg-highlight-color, $lightness: 40%);
20
+ $dim-color: adjust-color($node-text-color, $lightness: 20%);
21
+ $error-background-color: adjust-color($error-color, $lightness: 45%);
22
+ // @debug $dim-color;
23
+ $hover-color: adjust-color($bg-highlight-color, $lightness: 48%); // #f7f7f7;
24
+ $hover-border-color: $hover-color;
25
+ $focus-border-color: #969da3;
26
+ $grid-color: #dedede;
27
+ $active-color: #e5f3fb;
28
+ $active-cell-color: $bg-highlight-color;
29
+ $active-border-color: #70c0e7;
30
+ $active-hover-color: #dceff8;
31
+ $active-hover-border-color: $bg-highlight-color;
32
+ $background-color: #fcfcfc;
33
+ $active-column-color: $hover-color;
34
+ $active-header-column-color: adjust-color($header-color, $lightness: -10%);
35
+ // @debug $active-header-column-color;
36
+ $filter-dim-color: #dedede;
37
+ $filter-submatch-color: #868581;
38
+
39
+ $row-outer-height: 22px;
40
+ $row-inner-height: $row-outer-height - 2; // outer height minus border size
41
+ $row-padding-y: ($row-outer-height - $row-inner-height) / 2;
42
+ $col-padding-x: 2px; // on each side within span.wb-col
43
+
44
+ $icon-outer-height: $row-inner-height;
45
+ $icon-outer-width: 20px;
46
+ $icon-height: 16px;
47
+ $icon-width: 16px;
48
+ $icon-padding-y: ($icon-outer-height - $icon-height) / 2;
49
+ $icon-padding-x: ($icon-outer-width - $icon-width) / 2;
50
+
51
+ $header-height: $row-outer-height;
52
+
53
+ // PyCharm:
54
+ $level-rainbow: rgba(255, 255, 232, 1), rgba(240, 255, 240, 1),
55
+ rgba(255, 240, 255, 1), rgba(234, 253, 253, 1);
56
+ // VS-Code_
57
+ // $level-rainbow: rgba(255, 255, 64, 0.07), rgba(127, 255, 127, 0.07),
58
+ // rgba(255, 127, 255, 0.07), rgba(79, 236, 236, 0.07);
59
+
60
+ div.wunderbaum {
61
+ box-sizing: border-box;
62
+ height: 100%; // fill parent container
63
+ margin: 0;
64
+ padding: 0;
65
+
66
+ font-family: $font-stack;
67
+ font-size: 14px;
68
+
69
+ color: $node-text-color;
70
+ border: 1px solid $border-color;
71
+ overflow: hidden; // avoid unwanted effects when auto-resizing
72
+
73
+ // Focus border is generated by the browsers per default:
74
+ // &:focus,
75
+ // &:focus-within {
76
+ // border-color: $active-border-color;
77
+ // }
78
+
79
+ div.wb-scroll-container {
80
+ position: relative;
81
+ overflow: auto;
82
+ // scroll-behavior: smooth;
83
+ min-height: 4px;
84
+ // height: calc(100% - #{$header-height}); // didn't work. Using JS instead
85
+ }
86
+
87
+ // --- Header and node list ---
88
+ div.wb-row {
89
+ position: absolute;
90
+ width: 100%;
91
+ height: $row-outer-height;
92
+ line-height: $row-outer-height;
93
+ border: 1px solid transparent;
94
+ }
95
+
96
+ // --- Node List Only ---
97
+
98
+ // Dimm some colors if tree has no focus
99
+ &:not(:focus-within),
100
+ &:not(:focus) {
101
+ div.wb-node-list div.wb-row {
102
+ &.wb-active,
103
+ &.wb-selected {
104
+ background-color: grayscale($active-color);
105
+ border-color: grayscale($active-border-color);
106
+ &:hover {
107
+ background-color: grayscale($active-hover-color);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ div.wb-node-list {
113
+ div.wb-row {
114
+ &:hover {
115
+ background-color: $hover-color;
116
+ }
117
+ &.wb-active,
118
+ &.wb-selected {
119
+ background-color: $active-color;
120
+ // border-color: $active-border-color;
121
+ &:hover {
122
+ background-color: $active-hover-color;
123
+ // border-color: $active-hover-border-color;
124
+ }
125
+ }
126
+ &.wb-focus:not(.wb-active) {
127
+ border-style: dotted;
128
+ border-color: $active-border-color;
129
+ }
130
+ &.wb-active {
131
+ // background-color: $active-hover-color;
132
+ border-style: solid;
133
+ border-color: $active-border-color;
134
+ &:hover {
135
+ // background-color: $active-hover-color;
136
+ border-color: $active-hover-border-color;
137
+ }
138
+ }
139
+ &.wb-loading {
140
+ font-style: italic;
141
+ }
142
+ &.wb-dirty,
143
+ .wb-col.wb-dirty {
144
+ font-style: italic;
145
+ background: repeating-linear-gradient(
146
+ 45deg,
147
+ $hover-color,
148
+ $hover-color 5px,
149
+ $grid-color 5px,
150
+ $grid-color 10px
151
+ );
152
+ animation: wb-dirty-animation 2s linear infinite;
153
+ }
154
+ &.wb-error,
155
+ &.wb-status-error {
156
+ color: $error-color;
157
+ }
158
+ }
159
+ }
160
+
161
+ // --- HEADER ---
162
+
163
+ div.wb-header {
164
+ position: relative;
165
+ height: $header-height;
166
+ border-bottom: 1px solid $border-color;
167
+ padding: 0;
168
+ background-color: $header-color;
169
+ span.wb-col {
170
+ font-weight: bold;
171
+ // &::after {
172
+ // content: "|";
173
+ // }
174
+ }
175
+ }
176
+
177
+ span.wb-col {
178
+ position: absolute;
179
+ display: inline-block;
180
+ text-overflow: ellipsis;
181
+ height: $row-inner-height;
182
+ line-height: $row-inner-height;
183
+ padding: 0 $col-padding-x;
184
+ border-right: 1px solid $grid-color;
185
+ &:last-of-type {
186
+ border-right: none;
187
+ }
188
+ }
189
+
190
+ span.wb-node {
191
+ user-select: none;
192
+ // &:first-of-type {
193
+ // margin-left: 8px; // leftmost icon gets a little margin
194
+ // }
195
+ }
196
+
197
+ i.wb-checkbox,
198
+ i.wb-expander,
199
+ i.wb-icon,
200
+ i.wb-indent {
201
+ height: $icon-outer-height;
202
+ width: $icon-outer-width;
203
+ padding: $icon-padding-y $icon-padding-x;
204
+ display: inline-block;
205
+ }
206
+
207
+ /* Fix Bootstrap Icon alignment */
208
+ i.bi::before {
209
+ vertical-align: baseline;
210
+ }
211
+ img.wb-icon {
212
+ width: $icon-width;
213
+ height: $icon-height;
214
+ padding: $icon-padding-y $icon-padding-x;
215
+ // margin-top: $icon-padding-y;
216
+ }
217
+ i.wb-indent {
218
+ // border-left: 1px solid gray;
219
+ &::before {
220
+ content: "\00a0";
221
+ }
222
+ }
223
+ i.wb-expander.wb-spin,
224
+ i.wb-icon.wb-spin {
225
+ height: unset; // Prevent wobble
226
+ width: unset;
227
+ padding: 0 3px;
228
+ animation: wb-spin-animation 2s linear infinite;
229
+ }
230
+
231
+ span.wb-title {
232
+ min-width: 1em;
233
+ display: inline-block; // fix vertical offset on Chrome?
234
+ vertical-align: top;
235
+ overflow-x: hidden;
236
+ white-space: nowrap;
237
+ text-overflow: ellipsis;
238
+ }
239
+
240
+ /* --- GRID --- */
241
+ &.wb-grid {
242
+ div.wb-header {
243
+ div.wb-row {
244
+ span.wb-col {
245
+ &:hover {
246
+ background-color: $active-header-column-color;
247
+ }
248
+ }
249
+ }
250
+ }
251
+ &.wb-cell-mode div.wb-header div.wb-row span.wb-col {
252
+ &.wb-active {
253
+ background-color: $active-hover-color;
254
+ }
255
+ }
256
+ div.wb-node-list div.wb-row {
257
+ border-bottom-color: $grid-color;
258
+ &:hover:not(.wb-active):not(.wb-selected) {
259
+ background-color: $hover-color;
260
+ // border-color: $hover-border-color;
261
+ }
262
+ &.wb-active {
263
+ border-bottom-color: $active-border-color;
264
+ }
265
+ span.wb-col {
266
+ border-right: 1px solid $grid-color;
267
+
268
+ input.wb-input-edit,
269
+ >input[type=color],
270
+ >input[type=date],
271
+ >input[type=datetime], // deprecated
272
+ >input[type=datetime-local],
273
+ >input[type=email],
274
+ >input[type=month],
275
+ >input[type=number],
276
+ >input[type=password],
277
+ >input[type=search],
278
+ >input[type=tel],
279
+ >input[type=text],
280
+ >input[type=time],
281
+ >input[type=url],
282
+ >input[type=week],
283
+ >select {
284
+ width: 100%;
285
+ }
286
+ }
287
+ }
288
+ &.wb-cell-mode {
289
+ div.wb-row {
290
+ background-color: $background-color;
291
+ span.wb-col.wb-active {
292
+ background-color: $active-column-color;
293
+ }
294
+ &.wb-active {
295
+ background-color: $active-column-color;
296
+ span.wb-col.wb-active {
297
+ background-color: $active-cell-color;
298
+ }
299
+ }
300
+ }
301
+ &.wb-cell-edit-mode div.wb-row.wb-active span.wb-col.wb-active {
302
+ background-color: $error-color;
303
+ }
304
+ }
305
+ &.wb-alternate div.wb-node-list {
306
+ div.wb-row:nth-of-type(even):not(.wb-active):not(.wb-selected) {
307
+ background-color: $alternate-row-color;
308
+ &:hover {
309
+ background-color: $alternate-row-color-even;
310
+ }
311
+ }
312
+ }
313
+ // Dimm some colors if tree has no focus
314
+ &:not(:focus-within),
315
+ &:not(:focus) {
316
+ div.wb-node-list div.wb-row {
317
+ border-bottom-color: grayscale($grid-color);
318
+ }
319
+ }
320
+ }
321
+
322
+ /* --- FILTER --- */
323
+ &.wb-ext-filter-dim,
324
+ &.wb-ext-filter-hide {
325
+ div.wb-node-list div.wb-row {
326
+ color: $filter-dim-color;
327
+ &.wb-submatch {
328
+ color: $filter-submatch-color;
329
+ }
330
+ &.wb-match {
331
+ color: $node-text-color;
332
+ }
333
+ }
334
+ }
335
+ /* --- DND --- */
336
+ div.wb-row.wb-drag-source {
337
+ opacity: 0.5;
338
+ .wb-node {
339
+ background-color: $drop-source-color;
340
+ }
341
+ }
342
+ div.wb-row.wb-drop-target {
343
+ overflow: visible;
344
+ .wb-node {
345
+ background-color: $drop-target-color;
346
+ overflow: visible;
347
+ // z-index: 1000;
348
+ .wb-icon {
349
+ position: relative;
350
+ overflow: visible;
351
+ &::after {
352
+ // use ::after, because ::before is used by icon fonts
353
+ // vertical-align: baseline;
354
+ position: absolute;
355
+ z-index: 1000;
356
+ content: url(../docs/assets/drop_marker_16x32.png);
357
+ left: 0; //-$icon-outer-width;
358
+ top: ($row-outer-height - 16) / 2;
359
+ }
360
+ }
361
+ }
362
+ }
363
+ div.wb-row.wb-drop-target.wb-drop-before .wb-node .wb-icon::after {
364
+ content: url(../docs/assets/drop_marker_insert_16x64.png);
365
+ left: 0; // $icon-outer-width * 1.5;
366
+ top: ($row-outer-height - 16) / 2 - $row-outer-height / 2;
367
+ }
368
+ div.wb-row.wb-drop-target.wb-drop-after .wb-node .wb-icon::after {
369
+ content: url(../docs/assets/drop_marker_insert_16x64.png);
370
+ left: 0; //$icon-outer-width * 1.5;
371
+ top: ($row-outer-height - 16) / 2 + $row-outer-height / 2;
372
+ }
373
+
374
+ &.wb-rainbow {
375
+ @for $i from 1 through length($level-rainbow) {
376
+ i.wb-indent:nth-child(#{length($level-rainbow)}n + #{$i}) {
377
+ background: nth($level-rainbow, $i);
378
+ }
379
+ }
380
+ }
381
+
382
+ /* Fade out expanders, when container is not hovered or active */
383
+ &.wb-fade-expander {
384
+ i.wb-expander {
385
+ transition: opacity 1.5s;
386
+ opacity: 0;
387
+ }
388
+ div.wb-row.wb-loading i.wb-expander,
389
+ &:hover i.wb-expander,
390
+ &:focus i.wb-expander,
391
+ &:focus-within i.wb-expander,
392
+ [class*="wb-statusnode-"] i.wb-expander {
393
+ transition: opacity 0.6s;
394
+ opacity: 1;
395
+ }
396
+ }
397
+
398
+ /* Skeleton */
399
+ div.wb-row.wb-skeleton {
400
+ span.wb-title,
401
+ i.wb-icon {
402
+ animation: wb-skeleton-animation 1s linear infinite alternate;
403
+ border-radius: 0.25em;
404
+ color: transparent;
405
+ opacity: 0.7;
406
+ // max-width: 20px;
407
+ }
408
+ }
409
+
410
+ /* Auto-hide checkboxes unless selected or hovered */
411
+ &.wb-checkbox-auto-hide {
412
+ i.wb-checkbox {
413
+ visibility: hidden;
414
+ }
415
+ .wb-row:hover i.wb-checkbox,
416
+ .wb-row.wb-selected i.wb-checkbox {
417
+ visibility: unset;
418
+ }
419
+ &:focus,
420
+ &:focus-within {
421
+ .wb-row.wb-active i.wb-checkbox {
422
+ visibility: unset;
423
+ }
424
+ }
425
+ }
426
+ } // div.wunderbaum
427
+
428
+ /* --- TOOL CLASSES --- */
429
+
430
+ .wb-helper-center {
431
+ text-align: center;
432
+ }
433
+ .wb-helper-disabled {
434
+ color: $dim-color;
435
+ }
436
+ .wb-helper-hidden {
437
+ display: none;
438
+ }
439
+ .wb-helper-invalid {
440
+ color: $error-color;
441
+ }
442
+ .wb-helper-lazy-expander {
443
+ color: $bg-highlight-color;
444
+ }
445
+ .wb-helper-link {
446
+ cursor: pointer;
447
+ }
448
+ // .wb-helper-edit-text {
449
+ // // content-editable: true;
450
+ // }
451
+
452
+ /* RTL support */
453
+ .wb-helper-start {
454
+ text-align: left;
455
+ }
456
+ .wb-helper-end {
457
+ text-align: right;
458
+ }
459
+ .wb-rtl {
460
+ .wb-helper-start {
461
+ text-align: right;
462
+ }
463
+ .wb-helper-end {
464
+ text-align: left;
465
+ }
466
+ }
467
+ /* Class 'wb-tristate' is used to mark checkboxes that should toggle like
468
+ * indeterminate -> checked -> unchecked -> indeterminate ...
469
+ */
470
+ // input[type=checkbox].wb-tristate {
471
+ // }
472
+ // tri-state checkbox in indeterminate state
473
+ .wb-col input[type="checkbox"]:indeterminate {
474
+ color: $dim-color;
475
+ background-color: red;
476
+ }
477
+
478
+ .wb-col input:invalid {
479
+ // border-color: $error-color;
480
+ color: $error-color;
481
+ background-color: $error-background-color;
482
+ }
483
+
484
+ @keyframes wb-spin-animation {
485
+ 0% {
486
+ transform: rotate(0deg);
487
+ }
488
+ to {
489
+ transform: rotate(1turn);
490
+ }
491
+ }
492
+
493
+ @keyframes wb-skeleton-animation {
494
+ 0% {
495
+ background-color: hsl(200, 20%, 70%);
496
+ }
497
+ 100% {
498
+ background-color: hsl(200, 20%, 95%);
499
+ }
500
+ }
501
+
502
+ @keyframes wb-dirty-animation {
503
+ 0% {
504
+ background-position: 0 0;
505
+ }
506
+ 100% {
507
+ background-position: -70px 0;
508
+ }
509
+ }