gardenjs 0.9.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 (72) hide show
  1. package/.babelrc +3 -0
  2. package/.eslintrc.yml +19 -0
  3. package/.github/workflows/npm-publish.yml +32 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.prettierrc +5 -0
  6. package/LICENSE.md +21 -0
  7. package/README.md +76 -0
  8. package/bin/servegarden.js +172 -0
  9. package/examples/garden_button.svelte +5 -0
  10. package/examples/garden_button.svelte.das.js +16 -0
  11. package/examples/garden_button.svelte.md +3 -0
  12. package/examples/garden_button.vue +9 -0
  13. package/examples/garden_button.vue.das.js +16 -0
  14. package/examples/garden_button.vue.md +3 -0
  15. package/frame.html +21 -0
  16. package/index.html +14 -0
  17. package/index.js +1 -0
  18. package/jsconfig.json +32 -0
  19. package/package.json +55 -0
  20. package/src/client/GardenApp.svelte +173 -0
  21. package/src/client/GardenFrame.svelte +95 -0
  22. package/src/client/assets/icons/logo.svg +1 -0
  23. package/src/client/assets/scss/abstracts/root.css +42 -0
  24. package/src/client/assets/scss/base/buttons.css +5 -0
  25. package/src/client/assets/scss/base/general.css +77 -0
  26. package/src/client/assets/scss/base/navs.css +17 -0
  27. package/src/client/assets/scss/base/typography.css +46 -0
  28. package/src/client/assets/scss/main.scss +5 -0
  29. package/src/client/components/highlight/Highlight.js +12 -0
  30. package/src/client/components/highlight/highlight.css +249 -0
  31. package/src/client/components/sidebar/Sidebar.svelte +310 -0
  32. package/src/client/components/sidebar/SidebarNav.svelte +135 -0
  33. package/src/client/components/sidebar/SidebarNavLinks.svelte +86 -0
  34. package/src/client/components/splitpanes/HorizontalSplitPane.svelte +85 -0
  35. package/src/client/components/splitpanes/VerticalSplitPane.svelte +42 -0
  36. package/src/client/components/stage/Stage.svelte +172 -0
  37. package/src/client/components/stage/panel/PanelCode.svelte +31 -0
  38. package/src/client/components/stage/panel/PanelComponent.svelte +161 -0
  39. package/src/client/components/stage/panel/PanelContent.svelte +5 -0
  40. package/src/client/components/stage/panel/PanelDescription.svelte +26 -0
  41. package/src/client/components/stage/panel/PanelStoriesNav.svelte +67 -0
  42. package/src/client/components/stage/panel/markdown.css +679 -0
  43. package/src/client/components/topbar/Topbar.svelte +526 -0
  44. package/src/client/gardenapp.js +12 -0
  45. package/src/client/gardenframe.js +11 -0
  46. package/src/client/index.js +2 -0
  47. package/src/client/logic/navTree.js +146 -0
  48. package/src/client/logic/routing.js +50 -0
  49. package/src/client/logic/stage.js +89 -0
  50. package/src/client/router.js +82 -0
  51. package/src/codegenerator/base_generator.js +294 -0
  52. package/src/codegenerator/copy_base_classes.js +67 -0
  53. package/src/codegenerator/das_file_finder.js +66 -0
  54. package/src/codegenerator/shapes/Hellogarden.svelte +63 -0
  55. package/src/codegenerator/shapes/assets/favicon.svg +1 -0
  56. package/src/codegenerator/shapes/frame.html +22 -0
  57. package/src/codegenerator/shapes/gardenframe/index.html +22 -0
  58. package/src/codegenerator/shapes/index.html +14 -0
  59. package/src/codegenerator/shapes/lib/gardenapp.js +12 -0
  60. package/src/codegenerator/shapes/screenshottests/disable_animations.css +12 -0
  61. package/src/codegenerator/shapes/screenshottests/screenshottest.base.js +48 -0
  62. package/src/codegenerator/watch.js +36 -0
  63. package/src/codegenerator/watchcl.js +34 -0
  64. package/src/config.js +19 -0
  65. package/src/renderer/SvelteRenderer.js +16 -0
  66. package/src/renderer/SvelteRenderer.svelte +79 -0
  67. package/src/renderer/VueRenderer.js +20 -0
  68. package/src/renderer/VueRenderer.vue +20 -0
  69. package/src/renderer/state.js +10 -0
  70. package/src/server.js +26 -0
  71. package/svelte.config.js +8 -0
  72. package/vite.config.js +26 -0
@@ -0,0 +1,526 @@
1
+ <script>
2
+ import { createEventDispatcher } from 'svelte'
3
+ const dispatch = createEventDispatcher()
4
+
5
+ export let active = true
6
+ export let dark = false
7
+ export let landscape = false
8
+ export let stageSize = 'full'
9
+ export let themes = []
10
+ export let stageRect
11
+
12
+ let stageWidth, stageHeight
13
+
14
+ $: {
15
+ ;({ width: stageWidth, height: stageHeight } = stageRect)
16
+ stageWidth = Math.round(stageWidth)
17
+ stageHeight = Math.round(stageHeight)
18
+ }
19
+
20
+ $: {
21
+ if (dark) document.body.classList.add('dark')
22
+ else document.body.classList.remove('dark')
23
+ }
24
+
25
+ function toggleSidebar() {
26
+ active = !active
27
+ updateStage()
28
+ }
29
+
30
+ function toggleDarkmode() {
31
+ dark = !dark
32
+ }
33
+
34
+ function setFramesize(nStageSize) {
35
+ stageSize = nStageSize
36
+ updateStage()
37
+ }
38
+
39
+ function toggleOrientation() {
40
+ landscape = !landscape
41
+ updateStage()
42
+ }
43
+
44
+ function openInTab() {
45
+ dispatch('out', {
46
+ openInTab: true,
47
+ })
48
+ }
49
+
50
+ function updateStage() {
51
+ dispatch('out', {
52
+ active,
53
+ stageSize,
54
+ landscape,
55
+ })
56
+ }
57
+
58
+ function handleThemeChange(theme) {
59
+ dispatch('out', {
60
+ selectTheme: theme,
61
+ })
62
+ }
63
+ </script>
64
+
65
+ <div class="topbar">
66
+ <div class="topbar_container">
67
+ <button
68
+ class="topbar_btn is-first-btn"
69
+ on:click={toggleSidebar}
70
+ title={active ? 'Collapse sidebar' : 'Expand sidebar'}
71
+ >
72
+ {#if active}
73
+ <svg
74
+ xmlns="http://www.w3.org/2000/svg"
75
+ height="24"
76
+ viewBox="0 0 24 24"
77
+ width="24"
78
+ fill="none"
79
+ stroke="currentColor"
80
+ stroke-width="1.5"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ ><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><path
84
+ d="M9 3v18m7-6l-3-3 3-3"
85
+ /></svg
86
+ >
87
+ {:else}
88
+ <svg
89
+ xmlns="http://www.w3.org/2000/svg"
90
+ height="24"
91
+ viewBox="0 0 24 24"
92
+ width="24"
93
+ fill="none"
94
+ stroke="currentColor"
95
+ stroke-width="1.5"
96
+ stroke-linecap="round"
97
+ stroke-linejoin="round"
98
+ ><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><path
99
+ d="M9 3v18m5-12l3 3-3 3"
100
+ /></svg
101
+ >
102
+ {/if}
103
+ </button>
104
+ <div class="topbar_nav">
105
+ <div class="stagesize-value">
106
+ <div>{stageWidth}px</div>
107
+ <div class="stagesize-value-multi_sign">&#10005;</div>
108
+ <div>{stageHeight}px</div>
109
+ </div>
110
+ <div class="stagesize-nav">
111
+ <button
112
+ title="Small"
113
+ class:active={stageSize === 'small'}
114
+ on:click={() => setFramesize('small')}
115
+ >
116
+ <svg
117
+ xmlns="http://www.w3.org/2000/svg"
118
+ class:landscape
119
+ height="24"
120
+ viewBox="0 0 24 24"
121
+ width="24"
122
+ fill="none"
123
+ stroke="currentColor"
124
+ stroke-width="1.5"
125
+ stroke-linecap="round"
126
+ stroke-linejoin="round"
127
+ ><rect x="5" y="2" width="14" height="20" rx="2" ry="2" /><path
128
+ d="M12 18h.01"
129
+ /></svg
130
+ >
131
+ <span class="dot"></span>
132
+ </button>
133
+ <button
134
+ title="Medium"
135
+ class:active={stageSize === 'medium'}
136
+ on:click={() => setFramesize('medium')}
137
+ >
138
+ <svg
139
+ xmlns="http://www.w3.org/2000/svg"
140
+ class:landscape
141
+ height="24"
142
+ viewBox="0 0 24 24"
143
+ width="24"
144
+ fill="none"
145
+ stroke="currentColor"
146
+ stroke-width="1.5"
147
+ stroke-linecap="round"
148
+ stroke-linejoin="round"
149
+ ><rect x="4" y="2" width="16" height="20" rx="2" ry="2" /><path
150
+ d="M12 18h.01"
151
+ /></svg
152
+ >
153
+ <span class="dot"></span>
154
+ </button>
155
+ <button
156
+ title="Large"
157
+ class:active={stageSize === 'large'}
158
+ on:click={() => setFramesize('large')}
159
+ >
160
+ <svg
161
+ xmlns="http://www.w3.org/2000/svg"
162
+ class:landscape
163
+ height="24"
164
+ viewBox="0 0 24 24"
165
+ width="24"
166
+ fill="none"
167
+ stroke="currentColor"
168
+ stroke-width="1.5"
169
+ stroke-linecap="round"
170
+ stroke-linejoin="round"
171
+ ><rect x="3" y="4" width="18" height="12" rx="2" ry="2" /><path
172
+ d="M2 20h20"
173
+ /></svg
174
+ >
175
+ <span class="dot"></span>
176
+ </button>
177
+ <button
178
+ title="Full"
179
+ class:active={stageSize === 'full'}
180
+ on:click={() => setFramesize('full')}
181
+ >
182
+ <svg
183
+ xmlns="http://www.w3.org/2000/svg"
184
+ height="24"
185
+ viewBox="0 0 24 24"
186
+ width="24"
187
+ fill="none"
188
+ stroke="currentColor"
189
+ stroke-width="1.5"
190
+ stroke-linecap="round"
191
+ stroke-linejoin="round"
192
+ ><rect x="2" y="3" width="20" height="14" rx="2" ry="2" /><path
193
+ d="M8 21h8m-4-4v4"
194
+ /></svg
195
+ >
196
+ <span class="dot"></span>
197
+ </button>
198
+ <button
199
+ title={landscape ? 'Portrait mode' : 'Landscape mode'}
200
+ on:click={toggleOrientation}
201
+ >
202
+ <svg
203
+ xmlns="http://www.w3.org/2000/svg"
204
+ width="24"
205
+ viewBox="0 0 24 24"
206
+ height="24"
207
+ fill="none"
208
+ stroke="currentColor"
209
+ stroke-width="1.5"
210
+ stroke-linecap="round"
211
+ stroke-linejoin="round"
212
+ class="lucide lucide-ratio"
213
+ ><rect width="12" height="20" x="6" y="2" rx="2" /><rect
214
+ width="20"
215
+ height="12"
216
+ x="2"
217
+ y="6"
218
+ rx="2"
219
+ /></svg
220
+ >
221
+ </button>
222
+ </div>
223
+ {#if themes.length > 1}
224
+ <div class="dropdown">
225
+ <button
226
+ class="dropdown_btn topbar_btn"
227
+ title="Switch component theme"
228
+ >
229
+ <svg
230
+ xmlns="http://www.w3.org/2000/svg"
231
+ width="24"
232
+ viewBox="0 0 24 24"
233
+ height="24"
234
+ fill="none"
235
+ stroke="currentColor"
236
+ stroke-width="1.5"
237
+ stroke-linecap="round"
238
+ stroke-linejoin="round"
239
+ ><circle cx="13.5" cy="6.5" r=".5" /><circle
240
+ cx="17.5"
241
+ cy="10.5"
242
+ r=".5"
243
+ /><circle cx="8.5" cy="7.5" r=".5" /><circle
244
+ cx="6.5"
245
+ cy="12.5"
246
+ r=".5"
247
+ /><path
248
+ d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 011.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"
249
+ /></svg
250
+ >
251
+ </button>
252
+ <div class="dropdown_items">
253
+ <ul>
254
+ {#each themes as theme}
255
+ <li>
256
+ <button
257
+ class:active={theme.active}
258
+ on:click={() => handleThemeChange(theme.name)}
259
+ >
260
+ <span class="dropdown_item-dot"></span>
261
+ {theme.name}
262
+ </button>
263
+ </li>
264
+ {/each}
265
+ </ul>
266
+ </div>
267
+ </div>
268
+ {/if}
269
+ <button
270
+ class="topbar_btn"
271
+ title="Open component in new tab"
272
+ on:click={openInTab}
273
+ >
274
+ <svg
275
+ xmlns="http://www.w3.org/2000/svg"
276
+ class="open-new-tab-icon"
277
+ height="24"
278
+ viewBox="0 0 24 24"
279
+ width="24"
280
+ fill="none"
281
+ stroke="currentColor"
282
+ stroke-width="1.5"
283
+ stroke-linecap="round"
284
+ stroke-linejoin="round"
285
+ ><path
286
+ d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6m4-3h6v6m-11 5L21 3"
287
+ /></svg
288
+ >
289
+ </button>
290
+ <button
291
+ class="topbar_btn is-last-btn"
292
+ on:click={toggleDarkmode}
293
+ title={dark ? 'Light mode' : 'Dark mode'}
294
+ >
295
+ {#if dark}
296
+ <svg
297
+ xmlns="http://www.w3.org/2000/svg"
298
+ class="mode-icon"
299
+ width="24"
300
+ viewBox="0 0 24 24"
301
+ height="24"
302
+ fill="none"
303
+ stroke="currentColor"
304
+ stroke-width="1.5"
305
+ stroke-linecap="round"
306
+ stroke-linejoin="round"
307
+ ><circle cx="12" cy="12" r="4" /><path
308
+ d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
309
+ /></svg
310
+ >
311
+ {:else}
312
+ <svg
313
+ xmlns="http://www.w3.org/2000/svg"
314
+ class="mode-icon"
315
+ height="24"
316
+ viewBox="0 0 24 24"
317
+ width="24"
318
+ fill="none"
319
+ stroke="currentColor"
320
+ stroke-width="1.5"
321
+ stroke-linecap="round"
322
+ stroke-linejoin="round"
323
+ ><path d="M12 3a6.364 6.364 0 009 9 9 9 0 11-9-9z" /></svg
324
+ >
325
+ {/if}
326
+ </button>
327
+ </div>
328
+ </div>
329
+ </div>
330
+
331
+ <style>
332
+ .topbar {
333
+ --h-topbar: 2.25rem;
334
+ margin: 0.375rem 0;
335
+ width: 100%;
336
+ height: var(--h-topbar);
337
+ background-color: var(--c-bg-panels);
338
+ border-radius: 0.5rem;
339
+ }
340
+ .topbar_container {
341
+ display: flex;
342
+ justify-content: space-between;
343
+ padding: 1px;
344
+ width: 100%;
345
+ height: 100%;
346
+ padding: 0;
347
+ }
348
+ .topbar_nav {
349
+ display: flex;
350
+ align-items: center;
351
+ }
352
+
353
+ /* buttons */
354
+ .topbar_btn {
355
+ display: flex;
356
+ align-items: center;
357
+ padding: 0 0.375rem;
358
+ height: var(--h-topbar);
359
+ background: none;
360
+ }
361
+ .topbar_btn svg {
362
+ height: 1.125rem;
363
+ color: var(--c-basic-700);
364
+ }
365
+ .topbar_btn:hover svg {
366
+ color: var(--c-primary);
367
+ }
368
+ .is-first-btn {
369
+ border-radius: 0.5rem 0 0 0.5rem;
370
+ }
371
+ .is-last-btn {
372
+ border-radius: 0 0.5rem 0.5rem 0;
373
+ }
374
+
375
+ /* stage size */
376
+ .stagesize-value {
377
+ display: none;
378
+ }
379
+ @media (min-width: 640px) {
380
+ .stagesize-value {
381
+ display: inline-flex;
382
+ align-items: center;
383
+ font-size: 0.75rem;
384
+ padding: 0 1rem;
385
+ font-family:
386
+ ui-monospace,
387
+ Menlo,
388
+ Monaco,
389
+ 'Cascadia Mono',
390
+ 'Segoe UI Mono',
391
+ 'Roboto Mono',
392
+ 'Oxygen Mono',
393
+ 'Ubuntu Monospace',
394
+ 'Source Code Pro',
395
+ 'Fira Mono',
396
+ 'Droid Sans Mono',
397
+ Courier New,
398
+ 'monospace';
399
+ color: var(--c-basic-500);
400
+ }
401
+ .stagesize-value-multi_sign {
402
+ margin: 0 0.25rem;
403
+ font-size: 0.813rem;
404
+ }
405
+ }
406
+
407
+ /* stagesize nav */
408
+ .stagesize-nav {
409
+ display: none;
410
+ }
411
+ @media (min-width: 1280px) {
412
+ .stagesize-nav {
413
+ position: relative;
414
+ display: inline-flex;
415
+ margin: 0 0.75rem;
416
+ background-color: var(--c-basic-100);
417
+ }
418
+ .stagesize-nav button {
419
+ position: relative;
420
+ display: flex;
421
+ justify-content: center;
422
+ flex-direction: column;
423
+ align-self: center;
424
+ height: var(--h-topbar);
425
+ margin: 0;
426
+ padding: 0 0.25rem;
427
+ background: none;
428
+ overflow: hidden;
429
+ }
430
+ .stagesize-nav button svg {
431
+ height: 1.125rem;
432
+ color: var(--c-basic-700);
433
+ transition: 0.2s;
434
+ }
435
+ .stagesize-nav button:hover svg,
436
+ .stagesize-nav button.active svg {
437
+ color: var(--c-primary);
438
+ }
439
+ .stagesize-nav button svg.landscape {
440
+ transform: rotate(90deg);
441
+ transition: 0.2s;
442
+ }
443
+ .stagesize-nav button .dot {
444
+ display: block;
445
+ position: absolute;
446
+ left: 50%;
447
+ bottom: 0.125rem;
448
+ transform: translateX(-50%);
449
+ height: 0.313rem;
450
+ width: 0.313rem;
451
+ background-color: transparent;
452
+ border-radius: 50%;
453
+ }
454
+ .stagesize-nav button.active .dot {
455
+ background-color: var(--c-primary);
456
+ }
457
+ }
458
+
459
+ /* theme dropdown nav */
460
+ .dropdown {
461
+ position: relative;
462
+ display: inline-block;
463
+ }
464
+ .dropdown_items {
465
+ visibility: hidden;
466
+ position: absolute;
467
+ left: -0.75rem;
468
+ padding: 0.375rem 0 0;
469
+ z-index: 9;
470
+ }
471
+ .dropdown_items ul {
472
+ margin: 0;
473
+ padding: 0;
474
+ background-color: var(--c-basic-50);
475
+ filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.05))
476
+ drop-shadow(0 1px 3px rgba(0, 0, 0, 0.1));
477
+ border-radius: 0.5rem;
478
+ overflow: hidden;
479
+ }
480
+ .dropdown_items ul li {
481
+ display: block;
482
+ list-style: none;
483
+ margin: 0;
484
+ padding: 0;
485
+ }
486
+ .dropdown_items ul li button {
487
+ display: flex;
488
+ align-items: center;
489
+ justify-items: flex-start;
490
+ width: 100%;
491
+ min-width: 113px;
492
+ padding: 0.5rem;
493
+ font-size: 0.75rem;
494
+ color: var(--c-basic-900);
495
+ text-transform: capitalize;
496
+ white-space: nowrap;
497
+ }
498
+ .dropdown_items ul li button .dropdown_item-dot {
499
+ display: block;
500
+ margin: 0 0.5rem 0 0;
501
+ height: 0.313rem;
502
+ width: 0.313rem;
503
+ background-color: transparent;
504
+ border-radius: 50%;
505
+ }
506
+ .dropdown_items ul li button.active .dropdown_item-dot,
507
+ .dropdown_items ul li button.active:hover .dropdown_item-dot {
508
+ background-color: var(--c-primary);
509
+ }
510
+ .dropdown_items ul li button:hover {
511
+ color: var(--c-primary);
512
+ font-weight: 500;
513
+ background-color: var(--c-basic-100);
514
+ }
515
+ .dropdown_items ul li button.active {
516
+ color: var(--c-primary);
517
+ font-weight: 500;
518
+ background-color: var(--c-primary-bg);
519
+ border-color: var(--c-primary);
520
+ }
521
+ .dropdown:hover > .dropdown_items,
522
+ .dropdown:focus > .dropdown_items {
523
+ display: block;
524
+ visibility: visible;
525
+ }
526
+ </style>
@@ -0,0 +1,12 @@
1
+ import GardenApp from './GardenApp.svelte'
2
+ import { navTree, routes } from '../base.js'
3
+ import { dasMap } from '../das_import_map.js'
4
+ import config from '../../garden.config.js'
5
+ import './assets/scss/main.scss'
6
+
7
+ const app = new GardenApp({
8
+ target: document.getElementById('app'),
9
+ props: { navTree, routes, dasMap, config },
10
+ })
11
+
12
+ export default app
@@ -0,0 +1,11 @@
1
+ import GardenFrame from './GardenFrame.svelte'
2
+ import { dasMap } from '../das_import_map.js'
3
+ import { componentMap } from '../component_import_map.js'
4
+ import config from '../../garden.config.js'
5
+
6
+ const app = new GardenFrame({
7
+ target: document.body,
8
+ props: { componentMap, dasMap, config },
9
+ })
10
+
11
+ export default app
@@ -0,0 +1,2 @@
1
+ export { default as GardenApp } from './GardenApp.svelte'
2
+ export { default as GardenFrame } from './GardenFrame.svelte'
@@ -0,0 +1,146 @@
1
+ import { writable, get } from 'svelte/store'
2
+
3
+ export const nodes = writable([])
4
+ export const rootNodesExpanded = writable(true)
5
+ export const filterNavTree = writable()
6
+
7
+ let initialized = false
8
+ let currentRoute = ''
9
+ let selectedNode
10
+ let navtree = []
11
+ let unfoldedNodes = []
12
+
13
+ function initializeTree(navtree) {
14
+ const all = navtree.flatMap(getAllNodes)
15
+ unfoldedNodes = all.reduce((acc, cur) => {
16
+ acc[cur.key] = true
17
+ return acc
18
+ }, {})
19
+ }
20
+
21
+ function getAllNodes(node) {
22
+ return [node, ...getAllChildNodes(node)]
23
+ }
24
+
25
+ function getAllChildNodes(node) {
26
+ return node.children ? node.children.flatMap(getAllNodes) : []
27
+ }
28
+
29
+ export function updateSelectedComponent(route, componentName) {
30
+ currentRoute = route
31
+ selectedNode = componentName
32
+ updateTree()
33
+ }
34
+
35
+ export function updateFilter(newFilter) {
36
+ filterNavTree.set(newFilter)
37
+ updateTree()
38
+ }
39
+
40
+ export function updateNavTree(newNavTree) {
41
+ navtree = newNavTree
42
+ if (!initialized) {
43
+ initializeTree(navtree)
44
+ initialized = true
45
+ }
46
+ nodes.set(transformNavTree(navtree))
47
+ }
48
+
49
+ function updateTree() {
50
+ nodes.set(transformNavTree(navtree))
51
+ }
52
+
53
+ function transformNavTree(nodes, parentVisible) {
54
+ const filter = get(filterNavTree)
55
+ return nodes
56
+ .map((child) => {
57
+ const filterMatches = filter
58
+ ? child.name?.toLowerCase().includes(filter)
59
+ : true
60
+ const name =
61
+ filter && filterMatches ? highlightFilterMatch(child.name) : child.name
62
+ if (child.isLeaf) {
63
+ const visible = parentVisible || filterMatches
64
+ return visible
65
+ ? {
66
+ ...child,
67
+ name,
68
+ selected: selectedNode === child.key,
69
+ isLeaf: true,
70
+ }
71
+ : undefined
72
+ } else {
73
+ const children = transformNavTree(
74
+ child.children,
75
+ parentVisible || filterMatches
76
+ ).filter((n) => n)
77
+ const visible = filterMatches || children.length > 0
78
+ return visible
79
+ ? {
80
+ ...child,
81
+ name,
82
+ children,
83
+ unfolded: isUnfolded(child, currentRoute, filter, visible),
84
+ filterMatches,
85
+ }
86
+ : undefined
87
+ }
88
+ })
89
+ .filter((n) => n)
90
+ }
91
+
92
+ function highlightFilterMatch(text) {
93
+ const filter = get(filterNavTree)
94
+ const matchStart = text.toLowerCase().indexOf(filter)
95
+ const matchEnd = matchStart + filter.length
96
+ const start = text.substring(0, matchStart)
97
+ const middle = text.substring(matchStart, matchEnd)
98
+ const end = text.substring(matchEnd)
99
+ return `${start}<span class="highlight">${middle}</span>${end}`
100
+ }
101
+
102
+ function isUnfolded(node, route, filter, visible) {
103
+ return (
104
+ (filter && visible) ||
105
+ unfoldedNodes[node.key] ||
106
+ route?.indexOf(node.key) === 0
107
+ )
108
+ }
109
+
110
+ export function toggleRootFolders() {
111
+ rootNodesExpanded.set(!get(rootNodesExpanded))
112
+ const newNodes = get(nodes).map((n) => ({
113
+ ...n,
114
+ unfolded: get(rootNodesExpanded) && unfoldedNodes[n.key],
115
+ }))
116
+ nodes.set(newNodes)
117
+ }
118
+
119
+ export function toggleFolder(node) {
120
+ if (!get(rootNodesExpanded)) {
121
+ expandRootNode(node)
122
+ return
123
+ }
124
+ if (unfoldedNodes[node.key] && !(currentRoute.indexOf(node.key) === 0)) {
125
+ unfoldedNodes[node.key] = false
126
+ } else {
127
+ unfoldedNodes[node.key] = true
128
+ }
129
+ updateTree()
130
+ }
131
+
132
+ function expandRootNode(node) {
133
+ rootNodesExpanded.set(true)
134
+ Object.keys(unfoldedNodes).forEach((key) => (unfoldedNodes[key] = false))
135
+ const newNodes = get(nodes).map((n) => {
136
+ if (n.key === node.key || currentRoute.indexOf(n.key) === 0) {
137
+ unfoldedNodes[n.key] = true
138
+ return { ...n, unfolded: true }
139
+ } else {
140
+ unfoldedNodes[n.key] = false
141
+ return { ...n, unfolded: false }
142
+ }
143
+ })
144
+ nodes.set(newNodes)
145
+ updateTree()
146
+ }