@design.estate/dees-catalog 3.94.0 → 3.96.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.
Files changed (31) hide show
  1. package/dist_bundle/bundle.js +1744 -788
  2. package/dist_ts_web/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.d.ts +49 -0
  4. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.d.ts +1 -0
  5. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.js +82 -0
  6. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.js +622 -0
  7. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.d.ts +3 -0
  8. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.js +4 -0
  9. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.d.ts +37 -0
  10. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.js +174 -0
  11. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.d.ts +33 -0
  12. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.js +199 -0
  13. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.d.ts +52 -0
  14. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.js +8 -0
  15. package/dist_ts_web/elements/00group-layout/index.d.ts +1 -0
  16. package/dist_ts_web/elements/00group-layout/index.js +2 -1
  17. package/dist_ts_web/services/DeesServiceLibLoader.d.ts +14 -13
  18. package/dist_ts_web/services/DeesServiceLibLoader.js +37 -12
  19. package/dist_watch/bundle.js +1745 -789
  20. package/dist_watch/bundle.js.map +4 -4
  21. package/package.json +1 -1
  22. package/readme.md +28 -1
  23. package/ts_web/00_commitinfo_data.ts +1 -1
  24. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.ts +87 -0
  25. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.ts +562 -0
  26. package/ts_web/elements/00group-layout/dees-mosaic/index.ts +3 -0
  27. package/ts_web/elements/00group-layout/dees-mosaic/interaction.ts +187 -0
  28. package/ts_web/elements/00group-layout/dees-mosaic/tree.ts +239 -0
  29. package/ts_web/elements/00group-layout/dees-mosaic/types.ts +63 -0
  30. package/ts_web/elements/00group-layout/index.ts +1 -0
  31. package/ts_web/services/DeesServiceLibLoader.ts +42 -12
@@ -0,0 +1,562 @@
1
+ import { demoFunc } from './dees-mosaic.demo.js';
2
+ import {
3
+ DeesElement,
4
+ html,
5
+ css,
6
+ customElement,
7
+ type TemplateResult,
8
+ property,
9
+ state,
10
+ cssManager,
11
+ } from '@design.estate/dees-element';
12
+ import { themeDefaultStyles } from '../../00theme.js';
13
+ import type {
14
+ IMosaicLayout,
15
+ IMosaicLayoutChangeDetail,
16
+ IMosaicSplitNode,
17
+ IMosaicSurfaceMeta,
18
+ TMosaicLayoutChangeReason,
19
+ TMosaicNode,
20
+ } from './types.js';
21
+ import { mosaicEqualize, mosaicLeaves, mosaicRemove, mosaicResize } from './tree.js';
22
+ import { MosaicDragController, type IMosaicDropTarget } from './interaction.js';
23
+ import '../../00group-utility/dees-icon/dees-icon.js';
24
+
25
+ declare global {
26
+ interface HTMLElementTagNameMap {
27
+ 'dees-mosaic': DeesMosaic;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Sway-like tiling workspace. Controlled component: the host owns `layout`
33
+ * and applies the complete next layouts emitted through
34
+ * `mosaic-layout-change`. Tile content projects through named slots
35
+ * (`slot="tile-<leafId>"`) so hosts keep light-DOM ownership — the load-
36
+ * bearing property for Electron webviews, which must never be reparented
37
+ * (slot reassignment is safe; verified by spike).
38
+ */
39
+ @customElement('dees-mosaic')
40
+ export class DeesMosaic extends DeesElement {
41
+ public static demo = demoFunc;
42
+ public static demoGroups = ['Layout'];
43
+
44
+ @property({ attribute: false })
45
+ accessor layout: IMosaicLayout = { root: null };
46
+
47
+ @property({ attribute: false })
48
+ accessor surfaces: Record<string, IMosaicSurfaceMeta> = {};
49
+
50
+ @property()
51
+ accessor focusedLeafId: string = '';
52
+
53
+ @property({ type: Number })
54
+ accessor minTileWidth: number = 160;
55
+
56
+ @property({ type: Number })
57
+ accessor minTileHeight: number = 100;
58
+
59
+ @property({ type: Number })
60
+ accessor tilebarHeight: number = 26;
61
+
62
+ @property({ type: Boolean })
63
+ accessor resizeEnabled: boolean = true;
64
+
65
+ @property({ type: Boolean })
66
+ accessor dragEnabled: boolean = true;
67
+
68
+ /** transient drop-target highlight during a tile drag */
69
+ @state()
70
+ accessor dropTarget: IMosaicDropTarget | null = null;
71
+
72
+ private dragController = new MosaicDragController(this);
73
+
74
+ /** cancels an in-flight divider drag when the host disconnects mid-gesture */
75
+ private activeDividerCancel: (() => void) | null = null;
76
+
77
+ public async disconnectedCallback(): Promise<void> {
78
+ this.activeDividerCancel?.();
79
+ this.dragController.cancelActiveGesture();
80
+ await super.disconnectedCallback();
81
+ }
82
+
83
+ public static styles = [
84
+ themeDefaultStyles,
85
+ cssManager.defaultStyles,
86
+ css`
87
+ :host {
88
+ display: block;
89
+ position: relative;
90
+ min-width: 0;
91
+ min-height: 0;
92
+ height: 100%;
93
+ font-family: var(--dees-font-family);
94
+ background: var(--dees-color-bg-canvas);
95
+ --mosaic-tilebar-height: 26px;
96
+ }
97
+
98
+ .rootBox {
99
+ position: relative;
100
+ width: 100%;
101
+ height: 100%;
102
+ overflow: hidden;
103
+ }
104
+
105
+ .split {
106
+ display: flex;
107
+ min-width: 0;
108
+ min-height: 0;
109
+ width: 100%;
110
+ height: 100%;
111
+ }
112
+
113
+ .split.row {
114
+ flex-direction: row;
115
+ }
116
+
117
+ .split.column {
118
+ flex-direction: column;
119
+ }
120
+
121
+ .cell {
122
+ min-width: 0;
123
+ min-height: 0;
124
+ display: flex;
125
+ }
126
+
127
+ .divider {
128
+ flex: 0 0 5px;
129
+ position: relative;
130
+ background: transparent;
131
+ z-index: 1;
132
+ }
133
+
134
+ .split.row > .divider {
135
+ cursor: col-resize;
136
+ }
137
+
138
+ .split.column > .divider {
139
+ cursor: row-resize;
140
+ }
141
+
142
+ .divider::after {
143
+ content: '';
144
+ position: absolute;
145
+ inset: 0;
146
+ margin: auto;
147
+ background: var(--dees-color-border-subtle);
148
+ transition: background var(--dees-transition-fast) var(--dees-ease-standard);
149
+ }
150
+
151
+ .split.row > .divider::after {
152
+ width: 1px;
153
+ }
154
+
155
+ .split.column > .divider::after {
156
+ height: 1px;
157
+ }
158
+
159
+ .divider:hover::after,
160
+ .divider:focus-visible::after,
161
+ .divider.dragging::after {
162
+ background: var(--dees-color-accent-primary);
163
+ }
164
+
165
+ .split.row > .divider:hover::after,
166
+ .split.row > .divider.dragging::after {
167
+ width: 3px;
168
+ }
169
+
170
+ .split.column > .divider:hover::after,
171
+ .split.column > .divider.dragging::after {
172
+ height: 3px;
173
+ }
174
+
175
+ .divider:focus-visible {
176
+ outline: none;
177
+ }
178
+
179
+ .pane {
180
+ flex: 1;
181
+ min-width: 0;
182
+ min-height: 0;
183
+ display: flex;
184
+ flex-direction: column;
185
+ border-radius: var(--dees-radius-md);
186
+ corner-shape: var(--dees-corner-shape);
187
+ background: var(--dees-color-bg-primary);
188
+ border: 1px solid var(--dees-color-border-subtle);
189
+ overflow: hidden;
190
+ }
191
+
192
+ .pane.focused {
193
+ border-color: var(--dees-color-border-strong);
194
+ }
195
+
196
+ /* really slim tilebar: drag handle + title + close */
197
+ .tilebar {
198
+ flex: 0 0 var(--mosaic-tilebar-height);
199
+ display: flex;
200
+ align-items: center;
201
+ gap: var(--dees-spacing-xs);
202
+ padding: 0 4px 0 var(--dees-spacing-sm);
203
+ background: var(--dees-color-bg-secondary);
204
+ border-bottom: 1px solid var(--dees-color-border-subtle);
205
+ cursor: grab;
206
+ user-select: none;
207
+ }
208
+
209
+ .tilebar.dragging {
210
+ cursor: grabbing;
211
+ }
212
+
213
+ .tilebar dees-icon {
214
+ color: var(--dees-color-text-secondary);
215
+ flex: 0 0 auto;
216
+ }
217
+
218
+ .tileTitle {
219
+ min-width: 0;
220
+ color: var(--dees-color-text-secondary);
221
+ font-size: var(--dees-font-caption1-size, 11px);
222
+ font-weight: 600;
223
+ overflow: hidden;
224
+ text-overflow: ellipsis;
225
+ white-space: nowrap;
226
+ }
227
+
228
+ .pane.focused .tileTitle {
229
+ color: var(--dees-color-text-primary);
230
+ }
231
+
232
+ .tileSpacer {
233
+ flex: 1;
234
+ }
235
+
236
+ .tileClose {
237
+ flex: 0 0 auto;
238
+ display: grid;
239
+ place-items: center;
240
+ width: 20px;
241
+ height: 20px;
242
+ border: 0;
243
+ border-radius: var(--dees-radius-sm);
244
+ corner-shape: var(--dees-corner-shape);
245
+ background: transparent;
246
+ color: var(--dees-color-text-muted);
247
+ cursor: pointer;
248
+ padding: 0;
249
+ }
250
+
251
+ .tileClose:hover {
252
+ background: var(--dees-color-hover);
253
+ color: var(--dees-color-text-primary);
254
+ }
255
+
256
+ .paneContent {
257
+ flex: 1;
258
+ min-width: 0;
259
+ min-height: 0;
260
+ position: relative;
261
+ display: flex;
262
+ }
263
+
264
+ .paneContent ::slotted(*) {
265
+ flex: 1;
266
+ min-width: 0;
267
+ min-height: 0;
268
+ }
269
+
270
+ .emptyBox {
271
+ display: grid;
272
+ place-items: center;
273
+ width: 100%;
274
+ height: 100%;
275
+ }
276
+
277
+ /* drag artifacts */
278
+ .dragShield {
279
+ position: fixed;
280
+ inset: 0;
281
+ z-index: 40;
282
+ cursor: grabbing;
283
+ }
284
+
285
+ .dragGhost {
286
+ position: fixed;
287
+ z-index: 42;
288
+ transform: translate(-50%, -120%);
289
+ padding: 3px 10px;
290
+ border-radius: var(--dees-radius-full);
291
+ background: var(--dees-material-thick-bg);
292
+ backdrop-filter: var(--dees-material-thick-filter);
293
+ border: 1px solid var(--dees-color-border-default);
294
+ color: var(--dees-color-text-primary);
295
+ font-size: var(--dees-font-caption1-size, 11px);
296
+ font-weight: 600;
297
+ pointer-events: none;
298
+ box-shadow: var(--dees-shadow-md);
299
+ white-space: nowrap;
300
+ }
301
+
302
+ .dropPreview {
303
+ position: absolute;
304
+ z-index: 41;
305
+ border-radius: var(--dees-radius-md);
306
+ corner-shape: var(--dees-corner-shape);
307
+ background: color-mix(in srgb, var(--dees-color-accent-primary) 18%, transparent);
308
+ border: 1px solid var(--dees-color-accent-primary);
309
+ pointer-events: none;
310
+ transition:
311
+ left var(--dees-transition-fast) var(--dees-ease-standard),
312
+ top var(--dees-transition-fast) var(--dees-ease-standard),
313
+ width var(--dees-transition-fast) var(--dees-ease-standard),
314
+ height var(--dees-transition-fast) var(--dees-ease-standard);
315
+ }
316
+
317
+ @media (prefers-reduced-motion: reduce) {
318
+ .dropPreview {
319
+ transition: none;
320
+ }
321
+ }
322
+ `,
323
+ ];
324
+
325
+ public render(): TemplateResult {
326
+ return html`
327
+ <div
328
+ class="rootBox"
329
+ style="--mosaic-tilebar-height: ${this.tilebarHeight}px"
330
+ @pointerdown=${this.handleRootPointerDown}
331
+ >
332
+ ${this.layout.root
333
+ ? this.renderNode(this.layout.root)
334
+ : html`<div class="emptyBox"><slot name="empty"></slot></div>`}
335
+ ${this.renderDropPreview()}
336
+ </div>
337
+ `;
338
+ }
339
+
340
+ private renderNode(node: TMosaicNode): TemplateResult {
341
+ if (node.type === 'leaf') {
342
+ const meta = this.surfaces[node.surface] ?? { title: node.surface };
343
+ const title = node.title || meta.title;
344
+ const closeable = meta.closeable !== false;
345
+ return html`
346
+ <div class="pane ${this.focusedLeafId === node.id ? 'focused' : ''}" data-leaf-id=${node.id}>
347
+ <div
348
+ class="tilebar"
349
+ data-leaf-id=${node.id}
350
+ @pointerdown=${(event: PointerEvent) => this.dragController.handleTilebarPointerDown(event, node.id, title)}
351
+ >
352
+ ${meta.icon ? html`<dees-icon .icon=${meta.icon} iconSize="12"></dees-icon>` : ''}
353
+ <span class="tileTitle">${title}</span>
354
+ <span class="tileSpacer"></span>
355
+ ${closeable
356
+ ? html`
357
+ <button
358
+ class="tileClose"
359
+ type="button"
360
+ aria-label="Close ${title}"
361
+ @pointerdown=${(event: PointerEvent) => event.stopPropagation()}
362
+ @click=${() => this.requestCloseTile(node.id, node.surface)}
363
+ >
364
+ <dees-icon icon="lucide:X" iconSize="12"></dees-icon>
365
+ </button>
366
+ `
367
+ : ''}
368
+ </div>
369
+ <div class="paneContent" data-leaf-id=${node.id}>
370
+ <slot name="tile-${node.id}"></slot>
371
+ </div>
372
+ </div>
373
+ `;
374
+ }
375
+ const parts: TemplateResult[] = [];
376
+ node.children.forEach((child, index) => {
377
+ parts.push(html`
378
+ <div class="cell" data-cell-of=${node.id} style="flex: ${node.ratios[index]} 1 0%">
379
+ ${this.renderNode(child)}
380
+ </div>
381
+ `);
382
+ if (index < node.children.length - 1 && this.resizeEnabled) {
383
+ parts.push(html`
384
+ <div
385
+ class="divider"
386
+ role="separator"
387
+ tabindex="0"
388
+ aria-orientation=${node.orientation === 'row' ? 'vertical' : 'horizontal'}
389
+ data-split-id=${node.id}
390
+ data-divider-index=${index}
391
+ @pointerdown=${(event: PointerEvent) => this.handleDividerPointerDown(event, node, index)}
392
+ @dblclick=${() => this.commitLayout(mosaicEqualize(this.layout, node.id), 'equalize')}
393
+ @keydown=${(event: KeyboardEvent) => this.handleDividerKeydown(event, node, index)}
394
+ ></div>
395
+ `);
396
+ }
397
+ });
398
+ return html`<div class="split ${node.orientation}" data-split-id=${node.id}>${parts}</div>`;
399
+ }
400
+
401
+ private renderDropPreview(): TemplateResult {
402
+ const target = this.dropTarget;
403
+ if (!target) return html``;
404
+ const hostRect = this.getBoundingClientRect();
405
+ const rect = target.previewRect;
406
+ return html`
407
+ <div
408
+ class="dropPreview"
409
+ style="left: ${rect.left - hostRect.left}px; top: ${rect.top - hostRect.top}px; width: ${rect.width}px; height: ${rect.height}px"
410
+ ></div>
411
+ `;
412
+ }
413
+
414
+ // ------------------------------------------------------------- public API
415
+
416
+ /** Content-area rect per leaf id, in viewport coordinates. */
417
+ public getLeafRects(): Record<string, DOMRect> {
418
+ const rects: Record<string, DOMRect> = {};
419
+ for (const pane of this.shadowRoot?.querySelectorAll<HTMLElement>('.paneContent[data-leaf-id]') ?? []) {
420
+ rects[pane.dataset.leafId as string] = pane.getBoundingClientRect();
421
+ }
422
+ return rects;
423
+ }
424
+
425
+ public equalize(splitId?: string): void {
426
+ this.commitLayout(mosaicEqualize(this.layout, splitId), 'equalize');
427
+ }
428
+
429
+ public focusTile(leafId: string): void {
430
+ this.focusedLeafId = leafId;
431
+ const leaf = mosaicLeaves(this.layout).find((candidate) => candidate.id === leafId);
432
+ if (leaf) this.emitTileEvent('mosaic-tile-focus', leaf.id, leaf.surface);
433
+ }
434
+
435
+ // ------------------------------------------------------------- internals
436
+
437
+ public commitLayout(layout: IMosaicLayout, reason: TMosaicLayoutChangeReason): void {
438
+ const detail: IMosaicLayoutChangeDetail = { layout, reason };
439
+ this.dispatchEvent(new CustomEvent('mosaic-layout-change', { detail, bubbles: true, composed: true }));
440
+ }
441
+
442
+ private requestCloseTile(leafId: string, surface: string): void {
443
+ const closeEvent = new CustomEvent('mosaic-tile-close', {
444
+ detail: { leafId, surface },
445
+ bubbles: true,
446
+ composed: true,
447
+ cancelable: true,
448
+ });
449
+ const proceed = this.dispatchEvent(closeEvent);
450
+ if (!proceed) return;
451
+ this.commitLayout(mosaicRemove(this.layout, leafId), 'close');
452
+ }
453
+
454
+ private emitTileEvent(name: string, leafId: string, surface: string): void {
455
+ this.dispatchEvent(new CustomEvent(name, { detail: { leafId, surface }, bubbles: true, composed: true }));
456
+ }
457
+
458
+ private handleRootPointerDown = (event: PointerEvent): void => {
459
+ const pane = (event.composedPath().find(
460
+ (element) => element instanceof HTMLElement && element.classList?.contains('pane'),
461
+ )) as HTMLElement | undefined;
462
+ const leafId = pane?.dataset.leafId;
463
+ if (!leafId || leafId === this.focusedLeafId) return;
464
+ this.focusTile(leafId);
465
+ };
466
+
467
+ // -------------------------------------------------------------- resizing
468
+
469
+ private handleDividerPointerDown(event: PointerEvent, split: IMosaicSplitNode, dividerIndex: number): void {
470
+ if (!this.resizeEnabled || event.button !== 0) return;
471
+ if (this.activeDividerCancel) return; // one divider gesture at a time (multi-touch guard)
472
+ event.preventDefault();
473
+ const divider = event.currentTarget as HTMLElement;
474
+ const container = divider.parentElement as HTMLElement;
475
+ const horizontal = split.orientation === 'row';
476
+ const containerExtent = horizontal ? container.clientWidth : container.clientHeight;
477
+ const startCoord = horizontal ? event.clientX : event.clientY;
478
+ const cells = Array.from(container.children).filter(
479
+ (child) => child instanceof HTMLElement && child.classList.contains('cell'),
480
+ ) as HTMLElement[];
481
+ const leftCell = cells[dividerIndex];
482
+ const rightCell = cells[dividerIndex + 1];
483
+ if (!leftCell || !rightCell || !containerExtent) return;
484
+ const startLeftRatio = split.ratios[dividerIndex];
485
+ const startRightRatio = split.ratios[dividerIndex + 1];
486
+ const pair = startLeftRatio + startRightRatio;
487
+ const minPx = horizontal ? this.minTileWidth : this.minTileHeight;
488
+ const minRatio = Math.max(0.05, Math.min(minPx / containerExtent, pair / 2));
489
+ divider.classList.add('dragging');
490
+ try {
491
+ divider.setPointerCapture(event.pointerId);
492
+ } catch {
493
+ // synthetic pointers (tests) cannot be captured; listeners work anyway
494
+ }
495
+ let lastLeftRatio = startLeftRatio;
496
+ let resizeEmitFrame = 0;
497
+
498
+ const onMove = (moveEvent: PointerEvent) => {
499
+ const delta = (horizontal ? moveEvent.clientX : moveEvent.clientY) - startCoord;
500
+ const ratioDelta = delta / containerExtent;
501
+ lastLeftRatio = Math.min(Math.max(startLeftRatio + ratioDelta, minRatio), pair - minRatio);
502
+ // css-only while dragging; the tree commits once on release
503
+ leftCell.style.flex = `${lastLeftRatio} 1 0%`;
504
+ rightCell.style.flex = `${pair - lastLeftRatio} 1 0%`;
505
+ if (!resizeEmitFrame) {
506
+ resizeEmitFrame = requestAnimationFrame(() => {
507
+ resizeEmitFrame = 0;
508
+ this.dispatchEvent(new CustomEvent('mosaic-resizing', { detail: {}, bubbles: true, composed: true }));
509
+ });
510
+ }
511
+ };
512
+ const finish = (commit: boolean) => {
513
+ this.activeDividerCancel = null;
514
+ divider.classList.remove('dragging');
515
+ divider.removeEventListener('pointermove', onMove);
516
+ divider.removeEventListener('pointerup', onUp);
517
+ divider.removeEventListener('lostpointercapture', onLost);
518
+ window.removeEventListener('keydown', onKey, true);
519
+ if (resizeEmitFrame) cancelAnimationFrame(resizeEmitFrame);
520
+ if (commit && lastLeftRatio !== startLeftRatio) {
521
+ this.commitLayout(
522
+ mosaicResize(this.layout, split.id, dividerIndex, lastLeftRatio - startLeftRatio),
523
+ 'resize',
524
+ );
525
+ } else if (!commit) {
526
+ leftCell.style.flex = `${startLeftRatio} 1 0%`;
527
+ rightCell.style.flex = `${startRightRatio} 1 0%`;
528
+ }
529
+ };
530
+ const onUp = () => finish(true);
531
+ const onLost = () => finish(true);
532
+ const onKey = (keyEvent: KeyboardEvent) => {
533
+ if (keyEvent.key === 'Escape') {
534
+ keyEvent.preventDefault();
535
+ try {
536
+ divider.releasePointerCapture(event.pointerId);
537
+ } catch {
538
+ // not captured (synthetic pointer)
539
+ }
540
+ finish(false);
541
+ }
542
+ };
543
+ divider.addEventListener('pointermove', onMove);
544
+ divider.addEventListener('pointerup', onUp);
545
+ divider.addEventListener('lostpointercapture', onLost);
546
+ window.addEventListener('keydown', onKey, true);
547
+ this.activeDividerCancel = () => finish(false);
548
+ }
549
+
550
+ private handleDividerKeydown(event: KeyboardEvent, split: IMosaicSplitNode, dividerIndex: number): void {
551
+ const horizontal = split.orientation === 'row';
552
+ const decrease = horizontal ? 'ArrowLeft' : 'ArrowUp';
553
+ const increase = horizontal ? 'ArrowRight' : 'ArrowDown';
554
+ if (event.key !== decrease && event.key !== increase) return;
555
+ event.preventDefault();
556
+ const container = (event.currentTarget as HTMLElement).parentElement as HTMLElement;
557
+ const extent = horizontal ? container.clientWidth : container.clientHeight;
558
+ if (!extent) return;
559
+ const step = (event.key === increase ? 16 : -16) / extent;
560
+ this.commitLayout(mosaicResize(this.layout, split.id, dividerIndex, step), 'resize');
561
+ }
562
+ }
@@ -0,0 +1,3 @@
1
+ export * from './dees-mosaic.js';
2
+ export * from './types.js';
3
+ export * from './tree.js';