diff --git a/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts
new file mode 100644
index 0000000000..fa312846fc
--- /dev/null
+++ b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts
@@ -0,0 +1,90 @@
+import { TestBed } from "@angular/core/testing"
+import { provideMockStore, MockStore } from "@ngrx/store/testing"
+import { firstValueFrom, take } from "rxjs"
+import { CodeMapNode, NodeType } from "../../../codeCharta.model"
+import { accumulatedDataSelector } from "./accumulatedData.selector"
+import { maxFolderDepthSelector } from "./maxFolderDepth.selector"
+
+function getValue() {
+ maxFolderDepthSelector.release()
+ const store = TestBed.inject(MockStore)
+ return firstValueFrom(store.select(maxFolderDepthSelector).pipe(take(1)))
+}
+
+describe("maxFolderDepthSelector", () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [provideMockStore()]
+ })
+ })
+
+ it("should return undefined when no project is loaded", async () => {
+ const store = TestBed.inject(MockStore)
+ store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: undefined, unifiedFileMeta: undefined })
+
+ expect(await getValue()).toBeUndefined()
+ })
+
+ it("should return 1 for a root with only leaf children", async () => {
+ const root: CodeMapNode = {
+ name: "root",
+ type: NodeType.FOLDER,
+ attributes: {},
+ path: "/root",
+ children: [
+ { name: "a.ts", type: NodeType.FILE, attributes: {}, path: "/root/a.ts" },
+ { name: "b.ts", type: NodeType.FILE, attributes: {}, path: "/root/b.ts" }
+ ]
+ }
+ const store = TestBed.inject(MockStore)
+ store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined })
+
+ expect(await getValue()).toBe(1)
+ })
+
+ it("should return deepest folder depth + 1 for a nested tree", async () => {
+ const root: CodeMapNode = {
+ name: "root",
+ type: NodeType.FOLDER,
+ attributes: {},
+ path: "/root",
+ children: [
+ {
+ name: "src",
+ type: NodeType.FOLDER,
+ attributes: {},
+ path: "/root/src",
+ children: [
+ {
+ name: "deep",
+ type: NodeType.FOLDER,
+ attributes: {},
+ path: "/root/src/deep",
+ children: [{ name: "x.ts", type: NodeType.FILE, attributes: {}, path: "/root/src/deep/x.ts" }]
+ }
+ ]
+ },
+ { name: "readme.md", type: NodeType.FILE, attributes: {}, path: "/root/readme.md" }
+ ]
+ }
+ const store = TestBed.inject(MockStore)
+ store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined })
+
+ // deepest folder ("deep") is at depth 2 → max = 3
+ expect(await getValue()).toBe(3)
+ })
+
+ it("should clamp to at least 1 for a root with no children", async () => {
+ const root: CodeMapNode = {
+ name: "root",
+ type: NodeType.FOLDER,
+ attributes: {},
+ path: "/root",
+ children: []
+ }
+ const store = TestBed.inject(MockStore)
+ store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined })
+
+ expect(await getValue()).toBe(1)
+ })
+})
diff --git a/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts
new file mode 100644
index 0000000000..0db1321867
--- /dev/null
+++ b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts
@@ -0,0 +1,25 @@
+import { createSelector } from "@ngrx/store"
+import { CodeMapNode } from "../../../codeCharta.model"
+import { accumulatedDataSelector } from "./accumulatedData.selector"
+
+function computeMaxFolderDepth(node: CodeMapNode, depth: number): number {
+ if (!node.children || node.children.length === 0) {
+ return depth - 1
+ }
+ let max = depth
+ for (const child of node.children) {
+ const childDepth = computeMaxFolderDepth(child, depth + 1)
+ if (childDepth > max) {
+ max = childDepth
+ }
+ }
+ return max
+}
+
+export const maxFolderDepthSelector = createSelector(accumulatedDataSelector, accumulatedData => {
+ const root = accumulatedData.unifiedMapNode
+ if (!root) {
+ return undefined
+ }
+ return Math.max(1, computeMaxFolderDepth(root, 0) + 1)
+})
diff --git a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html
index 220c3e67e2..f725b49af9 100644
--- a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html
+++ b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html
@@ -14,7 +14,7 @@
[value]="floorLabelDepth$ | async"
[onChange]="applyDebouncedFloorLabelDepth"
[min]="1"
- [max]="8"
+ [max]="(maxFloorLabelDepth$ | async) ?? 15"
>
Invert Area
diff --git a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts
index 9f36ad5e78..f0ecec80ac 100644
--- a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts
+++ b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts
@@ -6,6 +6,7 @@ import { setEnableFloorLabels } from "../../../state/store/appSettings/enableFlo
import { enableFloorLabelsSelector } from "../../../state/store/appSettings/enableFloorLabels/enableFloorLabels.selector"
import { setFloorLabelDepth } from "../../../state/store/appSettings/floorLabelDepth/floorLabelDepth.actions"
import { floorLabelDepthSelector } from "../../../state/store/appSettings/floorLabelDepth/floorLabelDepth.selector"
+import { maxFolderDepthSelector } from "../../../state/selectors/accumulatedData/maxFolderDepth.selector"
import { invertAreaSelector } from "../../../state/store/appSettings/invertArea/invertArea.selector"
import { debounce } from "../../../util/debounce"
import { setInvertArea } from "../../../state/store/appSettings/invertArea/invertArea.actions"
@@ -27,6 +28,7 @@ export class AreaSettingsPanelComponent {
margin$ = this.store.select(marginSelector)
enableFloorLabels$ = this.store.select(enableFloorLabelsSelector)
floorLabelDepth$ = this.store.select(floorLabelDepthSelector)
+ maxFloorLabelDepth$ = this.store.select(maxFolderDepthSelector)
isInvertedArea$ = this.store.select(invertAreaSelector)
constructor(private store: Store) {}