From 26edaf6652bdfb484cd2a1db8940f4d7024634ff Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Sun, 23 Aug 2026 21:36:31 +0200 Subject: [PATCH] Update Current Navigation to version 1.1.5 with new features and configurable initial expansion --- README.md | 5 +++- config/package-solution.json | 2 +- config/serve.json | 2 +- deployment/add-currentnavigation.ps1 | 2 ++ package-lock.json | 2 +- package.json | 2 +- .../CurrentNavigationApplicationCustomizer.ts | 10 ++++++-- .../CurrentNavigationRenderer.ts | 25 ++++++++++++++----- tests/CurrentNavigationCore.test.ts | 5 ++++ tests/tsconfig.json | 1 + tests/validate-static-assets.js | 6 +++-- 11 files changed, 47 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 676214b..7bdc030 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CurrentNavigation -CurrentNavigation 1.1.4 ist ein SPFx-1.4.1-Application-Customizer für moderne Seiten in SharePoint Server +CurrentNavigation 1.1.5 ist ein SPFx-1.4.1-Application-Customizer für moderne Seiten in SharePoint Server Subscription Edition. Die Solution ergänzt unterhalb der vorhandenen Einträge im modernen SiteNavigation-Bereich einen Treeview für das aktuelle Web. Native Einträge wie Documents bleiben unverändert sichtbar. @@ -21,6 +21,9 @@ einen Term und seinen Unterbaum aus. Der Treeview unterstützt auf- und zuklappbare Zweige, aktive Seitenpfade sowie die Navigation mit den Pfeiltasten, Pos1 und Ende. +Der initiale Aufklappzustand ist über `initialExpansion` steuerbar: `activePath` öffnet wie bisher nur den aktiven +Pfad, `expanded` öffnet alle Zweige und `collapsed` startet mit vollständig geschlossenen Zweigen. + Wenn kein passender Root-Term gefunden wird oder das Termset nicht geladen werden kann, bleibt ausschließlich die native SharePoint-Sitenavigation sichtbar. diff --git a/config/package-solution.json b/config/package-solution.json index 85513b1..421e9e6 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "current-navigation-client-side-solution", "id": "bb8f9af5-b318-4111-8401-ce189cfe1b1a", - "version": "1.1.4.0", + "version": "1.1.5.0", "includeClientSideAssets": true, "skipFeatureDeployment": true }, diff --git a/config/serve.json b/config/serve.json index 7f39bf8..64f7362 100644 --- a/config/serve.json +++ b/config/serve.json @@ -11,7 +11,7 @@ "properties": { "termSetId": "00000000-0000-0000-0000-000000000000", "cacheMinutes": 15, "cacheVersion": "1", "maxDepth": 3, - "showRoot": false, "debug": true + "showRoot": false, "initialExpansion": "activePath", "debug": true } } } diff --git a/deployment/add-currentnavigation.ps1 b/deployment/add-currentnavigation.ps1 index 03c4c24..630ccf7 100644 --- a/deployment/add-currentnavigation.ps1 +++ b/deployment/add-currentnavigation.ps1 @@ -5,6 +5,7 @@ param( [string]$CacheVersion = '1', [ValidateRange(1, 8)][int]$MaxDepth = 3, [switch]$ShowRoot, + [ValidateSet('activePath', 'expanded', 'collapsed')][string]$InitialExpansion = 'activePath', [switch]$EnableDebug ) @@ -28,6 +29,7 @@ $properties = @{ cacheVersion = $(if ([string]::IsNullOrWhiteSpace($CacheVersion)) { '1' } else { $CacheVersion.Trim() }) maxDepth = $MaxDepth showRoot = [bool]$ShowRoot.IsPresent + initialExpansion = $InitialExpansion debug = [bool]$EnableDebug.IsPresent } diff --git a/package-lock.json b/package-lock.json index 411ee9c..4434351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "current-navigation", - "version": "1.1.4", + "version": "1.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e162e64..12c4f2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "current-navigation", - "version": "1.1.4", + "version": "1.1.5", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/extensions/currentNavigation/CurrentNavigationApplicationCustomizer.ts b/src/extensions/currentNavigation/CurrentNavigationApplicationCustomizer.ts index a56ae11..fae82fb 100644 --- a/src/extensions/currentNavigation/CurrentNavigationApplicationCustomizer.ts +++ b/src/extensions/currentNavigation/CurrentNavigationApplicationCustomizer.ts @@ -5,7 +5,7 @@ import * as strings from 'CurrentNavigationApplicationCustomizerStrings'; import { buildCurrentNavigationItems, selectCurrentNavigationRoot } from '../../services/CurrentNavigationCore'; import { CurrentNavigationTaxonomyService } from '../../services/CurrentNavigationTaxonomyService'; import { ICurrentNavigationItem, ICurrentNavigationTerm, ITermSetResult } from '../../services/CurrentNavigationTypes'; -import { CurrentNavigationRenderer } from './CurrentNavigationRenderer'; +import { CurrentNavigationInitialExpansion, CurrentNavigationRenderer } from './CurrentNavigationRenderer'; import './CurrentNavigation.module.scss'; export interface ICurrentNavigationApplicationCustomizerProperties { @@ -15,6 +15,7 @@ export interface ICurrentNavigationApplicationCustomizerProperties { cacheVersion?: string; maxDepth?: number; showRoot?: boolean; + initialExpansion?: CurrentNavigationInitialExpansion; debug?: boolean; } @@ -123,7 +124,7 @@ export default class CurrentNavigationApplicationCustomizer navigationLabel: strings.NavigationLabel, expand: strings.Expand, collapse: strings.Collapse - }); + }, this.initialExpansion()); } } catch (error) { this.debug(strings.LoadError, error); } } @@ -141,6 +142,11 @@ export default class CurrentNavigationApplicationCustomizer ? String(this.properties.cacheVersion).trim() : '1'; return value || '1'; } + private initialExpansion(): CurrentNavigationInitialExpansion { + const value: string = this.properties && this.properties.initialExpansion + ? String(this.properties.initialExpansion) : 'activePath'; + return value === 'expanded' || value === 'collapsed' ? value : 'activePath'; + } private languageLcid(): number { const legacy: any = (this.context.pageContext as any).legacyPageContext; // tslint:disable-line:no-any const value: number = legacy ? Number(legacy.currentLanguage) : 0; diff --git a/src/extensions/currentNavigation/CurrentNavigationRenderer.ts b/src/extensions/currentNavigation/CurrentNavigationRenderer.ts index 09c4489..22778f3 100644 --- a/src/extensions/currentNavigation/CurrentNavigationRenderer.ts +++ b/src/extensions/currentNavigation/CurrentNavigationRenderer.ts @@ -7,19 +7,32 @@ export interface ICurrentNavigationRendererStrings { collapse: string; } +export type CurrentNavigationInitialExpansion = 'activePath' | 'expanded' | 'collapsed'; + +export function isInitiallyExpanded( + initialExpansion: CurrentNavigationInitialExpansion, + containsCurrentItem: boolean +): boolean { + return initialExpansion === 'expanded' || + (initialExpansion === 'activePath' && containsCurrentItem); +} + export class CurrentNavigationRenderer { private container: HTMLElement | undefined; private keydownHandler: ((event: KeyboardEvent) => void) | undefined; private strings: ICurrentNavigationRendererStrings; + private initialExpansion: CurrentNavigationInitialExpansion = 'activePath'; public render( container: HTMLElement, items: ICurrentNavigationItem[], - strings: ICurrentNavigationRendererStrings + strings: ICurrentNavigationRendererStrings, + initialExpansion: CurrentNavigationInitialExpansion ): void { this.clear(container); this.container = container; this.strings = strings; + this.initialExpansion = initialExpansion; const nav: HTMLElement = document.createElement('nav'); nav.className = 'current-navigation'; nav.setAttribute('aria-label', strings.navigationLabel); @@ -52,7 +65,7 @@ export class CurrentNavigationRenderer { private createItem(item: ICurrentNavigationItem, depth: number): HTMLLIElement { const entry: HTMLLIElement = document.createElement('li'); const hasChildren: boolean = item.items.length > 0; - const activeBranch: boolean = this.containsCurrentItem(item); + const expanded: boolean = isInitiallyExpanded(this.initialExpansion, this.containsCurrentItem(item)); entry.className = hasChildren ? 'current-navigation-item has-children' : 'current-navigation-item'; entry.setAttribute('role', 'treeitem'); entry.setAttribute('aria-level', String(depth)); @@ -64,8 +77,8 @@ export class CurrentNavigationRenderer { toggle.type = 'button'; toggle.className = 'current-navigation-toggle'; toggle.tabIndex = -1; - toggle.setAttribute('aria-expanded', activeBranch ? 'true' : 'false'); - toggle.setAttribute('aria-label', (activeBranch ? this.strings.collapse : this.strings.expand) + ' ' + item.label); + toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false'); + toggle.setAttribute('aria-label', (expanded ? this.strings.collapse : this.strings.expand) + ' ' + item.label); const chevron: HTMLSpanElement = document.createElement('span'); chevron.className = 'current-navigation-chevron'; chevron.setAttribute('aria-hidden', 'true'); @@ -74,7 +87,7 @@ export class CurrentNavigationRenderer { this.setExpanded(entry, entry.getAttribute('aria-expanded') !== 'true', item.label); }); row.appendChild(toggle); - entry.setAttribute('aria-expanded', activeBranch ? 'true' : 'false'); + entry.setAttribute('aria-expanded', expanded ? 'true' : 'false'); } else { const spacer: HTMLSpanElement = document.createElement('span'); spacer.className = 'current-navigation-toggle-spacer'; @@ -99,7 +112,7 @@ export class CurrentNavigationRenderer { entry.appendChild(row); if (hasChildren) { const children: HTMLUListElement = this.createList(item.items, depth + 1, false); - children.hidden = !activeBranch; + children.hidden = !expanded; entry.appendChild(children); } return entry; diff --git a/tests/CurrentNavigationCore.test.ts b/tests/CurrentNavigationCore.test.ts index 9bb2a4d..38b8e62 100644 --- a/tests/CurrentNavigationCore.test.ts +++ b/tests/CurrentNavigationCore.test.ts @@ -3,6 +3,7 @@ import { selectCurrentNavigationRoot } from '../src/services/CurrentNavigationCore'; import { ICurrentNavigationTerm } from '../src/services/CurrentNavigationTypes'; +import { isInitiallyExpanded } from '../src/extensions/currentNavigation/CurrentNavigationRenderer'; let passed: number = 0; function assert(name: string, condition: boolean): void { @@ -27,6 +28,10 @@ const terms: ICurrentNavigationTerm[] = [ assert('German boolean is accepted', parseBoolean('ja')); assert('local property is read', getLocalProperty(terms[0], 'CurrentNavigation.Root') === 'true'); +assert('expanded opens every branch', isInitiallyExpanded('expanded', false)); +assert('collapsed closes even the active branch', !isInitiallyExpanded('collapsed', true)); +assert('activePath keeps the previous active branch behavior', isInitiallyExpanded('activePath', true)); +assert('activePath keeps inactive branches closed', !isInitiallyExpanded('activePath', false)); assert('site collection token resolves', resolveTermUrl(term({ LocalCustomProperties: { '_Sys_Nav_SimpleLinkUrl': '~sitecollection/start' } }), 'https://portal/sites/a') === 'https://portal/sites/a/start'); diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 910894f..8453a29 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -7,6 +7,7 @@ "files": [ "../src/services/CurrentNavigationTypes.ts", "../src/services/CurrentNavigationCore.ts", + "../src/extensions/currentNavigation/CurrentNavigationRenderer.ts", "CurrentNavigationCore.test.ts" ] } diff --git a/tests/validate-static-assets.js b/tests/validate-static-assets.js index 569b9ea..103c85e 100644 --- a/tests/validate-static-assets.js +++ b/tests/validate-static-assets.js @@ -9,8 +9,8 @@ var manifest = json('src/extensions/currentNavigation/CurrentNavigationApplicati var app = read('src/extensions/currentNavigation/CurrentNavigationApplicationCustomizer.ts'); var renderer = read('src/extensions/currentNavigation/CurrentNavigationRenderer.ts'); var service = read('src/services/CurrentNavigationTaxonomyService.ts'); -assert('package version', packageJson.version === '1.1.4'); -assert('solution version', solution.version === '1.1.4.0'); +assert('package version', packageJson.version === '1.1.5'); +assert('solution version', solution.version === '1.1.5.0'); assert('tenant deployment', solution.skipFeatureDeployment === true); assert('component type', manifest.extensionType === 'ApplicationCustomizer'); assert('safe renderer', renderer.indexOf('innerHTML') < 0 && app.indexOf('innerHTML') < 0); @@ -26,6 +26,8 @@ assert('custom navigation is appended below SiteNavigation', app.indexOf('this.s assert('observer cleanup', app.indexOf('observer.disconnect()') >= 0); assert('treeview semantics', renderer.indexOf("setAttribute('role', root ? 'tree' : 'group')") >= 0 && renderer.indexOf("setAttribute('role', 'treeitem')") >= 0); +assert('configurable initial expansion', renderer.indexOf('isInitiallyExpanded(this.initialExpansion') >= 0 && + renderer.indexOf("initialExpansion === 'expanded'") >= 0 && app.indexOf('this.initialExpansion()') >= 0); assert('legacy selector compatibility', renderer.indexOf(':scope') < 0); assert('local properties loaded', service.indexOf('LocalCustomProperties') >= 0); assert('root property present', read('src/services/CurrentNavigationCore.ts').indexOf('CurrentNavigation.Root') >= 0);