mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
feat(ui): add workspace groups disclosure
This commit is contained in:
1 parent
2676160b31
commit
7d11cbf6a2
9 files changed
+289
-40
No files matched your search
@@ -351,7 +351,7 @@ git commit -m "feat(ui): add independent M3U panel controls"
|
||||
- Modify:
|
||||
`libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.{ts,html,scss,spec.ts}`
|
||||
|
||||
- [ ] **Step 1: Write failing header and layout tests**
|
||||
- [x] **Step 1: Write failing header and layout tests**
|
||||
|
||||
Assert the Groups header control is last, stays present in loading/empty/zero
|
||||
search results, and emits an explicit collapse request only on Live/ITV/Radio.
|
||||
@@ -359,7 +359,7 @@ Assert the context sidebar suppresses Groups at `max-width: 1023px` without
|
||||
changing intent, retains collapsed DOM as inert, renders one boundary restore
|
||||
button at wide widths, and transfers focus.
|
||||
|
||||
- [ ] **Step 2: Run and verify RED**
|
||||
- [x] **Step 2: Run and verify RED**
|
||||
|
||||
```bash
|
||||
pnpm nx test workspace-shell-feature --runInBand
|
||||
@@ -367,7 +367,7 @@ pnpm nx test workspace-shell-feature --runInBand
|
||||
|
||||
Expected: FAIL because the Groups disclosure contract does not exist.
|
||||
|
||||
- [ ] **Step 3: Add the Groups header output**
|
||||
- [x] **Step 3: Add the Groups header output**
|
||||
|
||||
Add:
|
||||
|
||||
@@ -383,7 +383,7 @@ readonly showLiveGroupsToggle = computed(
|
||||
Render the translated 40px button last in `.context-header__actions`, with
|
||||
panel-based test ID and ARIA attributes.
|
||||
|
||||
- [ ] **Step 4: Add state, responsive suppression, restore rail, and focus**
|
||||
- [x] **Step 4: Add state, responsive suppression, restore rail, and focus**
|
||||
|
||||
Inject `LiveLayoutPanelStateService` from data-access and `BreakpointObserver`.
|
||||
Resolve effective Groups visibility from intent, master suppression,
|
||||
@@ -391,7 +391,7 @@ applicability, and `(max-width: 1023px)`. Keep the `aside` mounted with
|
||||
`inert`/`aria-hidden`, reserve a 40px restore column only when restoration is
|
||||
currently fulfillable, and focus `live-groups-panel-hide` after restoration.
|
||||
|
||||
- [ ] **Step 5: Run and verify GREEN**
|
||||
- [x] **Step 5: Run and verify GREEN**
|
||||
|
||||
```bash
|
||||
pnpm nx test workspace-shell-feature --runInBand
|
||||
@@ -400,7 +400,7 @@ pnpm nx lint workspace-shell-feature
|
||||
|
||||
Expected: both commands exit 0.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
- [x] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add libs/workspace/shell/feature
|
||||
|
||||
+18
@@ -64,6 +64,7 @@
|
||||
type="button"
|
||||
mat-icon-button
|
||||
class="context-header__action"
|
||||
data-testid="manage-live-groups"
|
||||
(click)="openManageCategories()"
|
||||
[disabled]="!canManageXtreamCategories()"
|
||||
[matTooltip]="
|
||||
@@ -76,6 +77,23 @@
|
||||
<mat-icon>tune</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@if (showLiveGroupsToggle()) {
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
class="context-header__action context-header__action--panel"
|
||||
data-testid="live-groups-panel-hide"
|
||||
aria-controls="live-groups-panel"
|
||||
[attr.aria-expanded]="groupsPanelExpanded()"
|
||||
[attr.aria-label]="
|
||||
'LAYOUT.HIDE_GROUPS_PANEL' | translate
|
||||
"
|
||||
[matTooltip]="'LAYOUT.HIDE_GROUPS_PANEL' | translate"
|
||||
(click)="groupsPanelExpandedChange.emit(false)"
|
||||
>
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- @if (subtitle(); as text) {
|
||||
|
||||
+8
@@ -65,6 +65,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.context-header__action.context-header__action--panel {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.context-header__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
+49
-9
@@ -194,9 +194,9 @@ describe('WorkspaceContextPanelComponent', () => {
|
||||
const status = fixture.nativeElement.querySelector(
|
||||
'.context-inline-status'
|
||||
) as HTMLElement | null;
|
||||
const manageButton = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('.context-header__action')
|
||||
).at(-1) as HTMLButtonElement | undefined;
|
||||
const manageButton = fixture.nativeElement.querySelector(
|
||||
'[data-testid="manage-live-groups"]'
|
||||
) as HTMLButtonElement | null;
|
||||
|
||||
expect(countPlaceholders).toHaveLength(2);
|
||||
expect(categoryButtons.every((button) => button.disabled)).toBe(true);
|
||||
@@ -213,6 +213,46 @@ describe('WorkspaceContextPanelComponent', () => {
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps the live Groups disclosure last across loading and zero-result states', () => {
|
||||
fixture.componentRef.setInput('section', 'live');
|
||||
const groupsPanelExpandedChange = jest.fn();
|
||||
fixture.componentInstance.groupsPanelExpandedChange.subscribe(
|
||||
groupsPanelExpandedChange
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
let actions = Array.from(
|
||||
fixture.nativeElement.querySelectorAll(
|
||||
'.context-header__actions > button'
|
||||
)
|
||||
) as HTMLButtonElement[];
|
||||
let toggle = actions.at(-1) as HTMLButtonElement;
|
||||
expect(toggle.dataset['testid']).toBe('live-groups-panel-hide');
|
||||
expect(toggle.getAttribute('aria-controls')).toBe('live-groups-panel');
|
||||
expect(toggle.getAttribute('aria-expanded')).toBe('true');
|
||||
|
||||
fixture.componentInstance.categorySearchTerm.set(
|
||||
'no category matches this'
|
||||
);
|
||||
fixture.detectChanges();
|
||||
actions = Array.from(
|
||||
fixture.nativeElement.querySelectorAll(
|
||||
'.context-header__actions > button'
|
||||
)
|
||||
) as HTMLButtonElement[];
|
||||
toggle = actions.at(-1) as HTMLButtonElement;
|
||||
toggle.click();
|
||||
expect(groupsPanelExpandedChange).toHaveBeenCalledWith(false);
|
||||
|
||||
fixture.componentRef.setInput('section', 'vod');
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-testid="live-groups-panel-hide"]'
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps local xtream loading states quiet when no import is running', () => {
|
||||
fixture.componentRef.setInput('section', 'vod');
|
||||
xtreamIsImporting.set(false);
|
||||
@@ -251,9 +291,9 @@ describe('WorkspaceContextPanelComponent', () => {
|
||||
const categoryButtons = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('.category-item')
|
||||
) as HTMLButtonElement[];
|
||||
const manageButton = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('.context-header__action')
|
||||
).at(-1) as HTMLButtonElement | undefined;
|
||||
const manageButton = fixture.nativeElement.querySelector(
|
||||
'[data-testid="manage-live-groups"]'
|
||||
) as HTMLButtonElement | null;
|
||||
|
||||
expect(countTexts).toEqual(['3', '0']);
|
||||
expect(categoryButtons.every((button) => !button.disabled)).toBe(true);
|
||||
@@ -460,9 +500,9 @@ describe('WorkspaceContextPanelComponent', () => {
|
||||
el.textContent?.includes('For adults')
|
||||
);
|
||||
|
||||
expect(documentary?.querySelector('.item-count')?.textContent).toContain(
|
||||
'190'
|
||||
);
|
||||
expect(
|
||||
documentary?.querySelector('.item-count')?.textContent
|
||||
).toContain('190');
|
||||
expect(adults?.querySelector('.item-count')).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
+9
@@ -5,6 +5,7 @@ import {
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
viewChild,
|
||||
ElementRef,
|
||||
@@ -72,6 +73,8 @@ export class WorkspaceContextPanelComponent {
|
||||
|
||||
readonly context = input.required<WorkspaceContextRoute>();
|
||||
readonly section = input.required<string>();
|
||||
readonly groupsPanelExpanded = input(true);
|
||||
readonly groupsPanelExpandedChange = output<boolean>();
|
||||
|
||||
readonly isXtreamCategories = computed(
|
||||
() =>
|
||||
@@ -88,6 +91,12 @@ export class WorkspaceContextPanelComponent {
|
||||
this.section() === 'itv' ||
|
||||
this.section() === 'radio')
|
||||
);
|
||||
readonly showLiveGroupsToggle = computed(
|
||||
() =>
|
||||
(this.isXtreamCategories() && this.section() === 'live') ||
|
||||
(this.isStalkerCategories() &&
|
||||
(this.section() === 'itv' || this.section() === 'radio'))
|
||||
);
|
||||
|
||||
readonly xtreamCategories = this.xtreamStore.getCategoriesBySelectedType;
|
||||
readonly xtreamCategoryItemCounts = this.xtreamStore.getCategoryItemCounts;
|
||||
|
||||
+23
-1
@@ -12,9 +12,27 @@
|
||||
} @else if (variant() === 'category') {
|
||||
@if (context(); as context) {
|
||||
@if (section(); as section) {
|
||||
@if (canRestoreGroupsPanel()) {
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
class="context-panel-restore"
|
||||
data-testid="live-groups-panel-restore"
|
||||
aria-controls="live-groups-panel"
|
||||
aria-expanded="false"
|
||||
[attr.aria-label]="'LAYOUT.SHOW_GROUPS_PANEL' | translate"
|
||||
[matTooltip]="'LAYOUT.SHOW_GROUPS_PANEL' | translate"
|
||||
(click)="onGroupsPanelExpandedChange(true)"
|
||||
>
|
||||
<mat-icon>chevron_right</mat-icon>
|
||||
</button>
|
||||
}
|
||||
<aside
|
||||
id="live-groups-panel"
|
||||
class="context-panel context-panel--route"
|
||||
[class.context-panel--collapsed]="isContextPanelCollapsed()"
|
||||
[class.context-panel--collapsed]="!groupsPanelExpanded()"
|
||||
[attr.aria-hidden]="!groupsPanelExpanded()"
|
||||
[attr.inert]="groupsPanelExpanded() ? null : ''"
|
||||
appResizable
|
||||
[minWidth]="240"
|
||||
[maxWidth]="560"
|
||||
@@ -24,6 +42,10 @@
|
||||
<app-workspace-context-panel
|
||||
[context]="context"
|
||||
[section]="section"
|
||||
[groupsPanelExpanded]="groupsPanelExpanded()"
|
||||
(groupsPanelExpandedChange)="
|
||||
onGroupsPanelExpandedChange($event)
|
||||
"
|
||||
/>
|
||||
</aside>
|
||||
}
|
||||
|
||||
+13
-1
@@ -1,9 +1,20 @@
|
||||
:host {
|
||||
display: block;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.context-panel-restore {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
height: 100%;
|
||||
min-height: 40px;
|
||||
flex: 0 0 40px;
|
||||
border-radius: 0;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
}
|
||||
|
||||
.context-panel {
|
||||
border-right: 1px solid var(--mat-sys-outline-variant);
|
||||
background: var(--app-rail-bg, var(--mat-sys-surface-container-low));
|
||||
@@ -28,6 +39,7 @@
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
border-right-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
.resize-handle {
|
||||
display: none;
|
||||
|
||||
+96
-15
@@ -1,10 +1,15 @@
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { Component, Directive, input, output } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LiveLayoutSidebarStateService } from '@iptvnator/portal/shared/util';
|
||||
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import {
|
||||
LiveLayoutPanelStateService,
|
||||
LIVE_LAYOUT_PANEL,
|
||||
} from '@iptvnator/portal/shared/data-access';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { WorkspaceShellContextSidebarComponent } from './workspace-shell-context-sidebar.component';
|
||||
|
||||
@Directive({
|
||||
@@ -20,12 +25,15 @@ class MockResizableDirective {
|
||||
|
||||
@Component({
|
||||
selector: 'app-workspace-context-panel',
|
||||
template: '',
|
||||
template:
|
||||
'<button data-testid="live-groups-panel-hide">Hide Groups</button>',
|
||||
standalone: true,
|
||||
})
|
||||
class MockWorkspaceContextPanelComponent {
|
||||
readonly context = input.required<unknown>();
|
||||
readonly section = input.required<string>();
|
||||
readonly groupsPanelExpanded = input(true);
|
||||
readonly groupsPanelExpandedChange = output<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -51,13 +59,31 @@ class MockWorkspaceSourcesFiltersPanelComponent {}
|
||||
|
||||
describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
let fixture: ComponentFixture<WorkspaceShellContextSidebarComponent>;
|
||||
let liveSidebarService: LiveLayoutSidebarStateService;
|
||||
let livePanelState: LiveLayoutPanelStateService;
|
||||
let breakpointState: BehaviorSubject<BreakpointState>;
|
||||
|
||||
beforeEach(async () => {
|
||||
localStorage.removeItem('live-sidebar-state');
|
||||
localStorage.removeItem('live-groups-panel-state');
|
||||
localStorage.removeItem('live-channels-panel-state');
|
||||
breakpointState = new BehaviorSubject<BreakpointState>({
|
||||
breakpoints: {},
|
||||
matches: false,
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [WorkspaceShellContextSidebarComponent],
|
||||
imports: [
|
||||
WorkspaceShellContextSidebarComponent,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: BreakpointObserver,
|
||||
useValue: {
|
||||
observe: () => breakpointState.asObservable(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideComponent(WorkspaceShellContextSidebarComponent, {
|
||||
set: {
|
||||
@@ -67,14 +93,20 @@ describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
MockWorkspaceCollectionContextPanelComponent,
|
||||
MockWorkspaceSettingsContextPanelComponent,
|
||||
MockWorkspaceSourcesFiltersPanelComponent,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatTooltip,
|
||||
TranslateModule,
|
||||
],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(WorkspaceShellContextSidebarComponent);
|
||||
liveSidebarService = TestBed.inject(LiveLayoutSidebarStateService);
|
||||
liveSidebarService.setState('expanded');
|
||||
fixture = TestBed.createComponent(
|
||||
WorkspaceShellContextSidebarComponent
|
||||
);
|
||||
livePanelState = TestBed.inject(LiveLayoutPanelStateService);
|
||||
livePanelState.showPanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
});
|
||||
|
||||
it('renders the settings panel for the settings variant', () => {
|
||||
@@ -127,7 +159,7 @@ describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
it('collapses the categories rail when the shared service is collapsed on a live route', () => {
|
||||
setupLiveCategory('live');
|
||||
|
||||
liveSidebarService.setState('collapsed');
|
||||
livePanelState.hidePanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
fixture.detectChanges();
|
||||
|
||||
const aside = fixture.nativeElement.querySelector(
|
||||
@@ -141,7 +173,7 @@ describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
it('also collapses on the Stalker itv section', () => {
|
||||
setupLiveCategory('itv');
|
||||
|
||||
liveSidebarService.setState('collapsed');
|
||||
livePanelState.hidePanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
fixture.detectChanges();
|
||||
|
||||
const aside = fixture.nativeElement.querySelector(
|
||||
@@ -155,7 +187,7 @@ describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
it('does not collapse the categories rail on non-live sections', () => {
|
||||
setupLiveCategory('vod');
|
||||
|
||||
liveSidebarService.setState('collapsed');
|
||||
livePanelState.hidePanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
fixture.detectChanges();
|
||||
|
||||
const aside = fixture.nativeElement.querySelector(
|
||||
@@ -176,5 +208,54 @@ describe('WorkspaceShellContextSidebarComponent', () => {
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('suppresses Groups responsively without changing persisted intent', () => {
|
||||
setupLiveCategory('live');
|
||||
breakpointState.next({
|
||||
breakpoints: { '(max-width: 1023px)': true },
|
||||
matches: true,
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const aside = fixture.nativeElement.querySelector(
|
||||
'aside.context-panel--route'
|
||||
) as HTMLElement;
|
||||
expect(aside.classList.contains('context-panel--collapsed')).toBe(
|
||||
true
|
||||
);
|
||||
expect(aside.hasAttribute('inert')).toBe(true);
|
||||
expect(livePanelState.groupsIntent()).toBe('expanded');
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-testid="live-groups-panel-restore"]'
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('restores Groups from a boundary rail and transfers focus both ways', async () => {
|
||||
setupLiveCategory('live');
|
||||
livePanelState.hidePanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
fixture.detectChanges();
|
||||
await Promise.resolve();
|
||||
|
||||
const restore = fixture.nativeElement.querySelector(
|
||||
'[data-testid="live-groups-panel-restore"]'
|
||||
) as HTMLButtonElement;
|
||||
expect(restore.getAttribute('aria-controls')).toBe(
|
||||
'live-groups-panel'
|
||||
);
|
||||
expect(document.activeElement).toBe(restore);
|
||||
|
||||
restore.click();
|
||||
fixture.detectChanges();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(livePanelState.groupsIntent()).toBe('expanded');
|
||||
expect(document.activeElement).toBe(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-testid="live-groups-panel-hide"]'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+67
-8
@@ -1,15 +1,24 @@
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { ResizableDirective } from '@iptvnator/ui/components';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
LiveLayoutSidebarStateService,
|
||||
PortalRailSection,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
LiveLayoutPanelStateService,
|
||||
LIVE_LAYOUT_PANEL,
|
||||
} from '@iptvnator/portal/shared/data-access';
|
||||
import { ResizableDirective } from '@iptvnator/ui/components';
|
||||
import { PortalRailSection } from '@iptvnator/portal/shared/util';
|
||||
import {
|
||||
WorkspacePortalContext,
|
||||
WorkspaceShellContextPanel,
|
||||
@@ -29,6 +38,10 @@ const LIVE_SECTIONS: ReadonlySet<PortalRailSection> = new Set([
|
||||
selector: 'app-workspace-shell-context-sidebar',
|
||||
imports: [
|
||||
ResizableDirective,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatTooltip,
|
||||
TranslatePipe,
|
||||
WorkspaceCollectionContextPanelComponent,
|
||||
WorkspaceContextPanelComponent,
|
||||
WorkspaceSettingsContextPanelComponent,
|
||||
@@ -39,9 +52,13 @@ const LIVE_SECTIONS: ReadonlySet<PortalRailSection> = new Set([
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WorkspaceShellContextSidebarComponent {
|
||||
private readonly liveSidebarStateService = inject(
|
||||
LiveLayoutSidebarStateService
|
||||
private readonly livePanelState = inject(LiveLayoutPanelStateService);
|
||||
private readonly hostEl = inject(ElementRef<HTMLElement>);
|
||||
private readonly compactViewport = toSignal(
|
||||
inject(BreakpointObserver).observe('(max-width: 1023px)'),
|
||||
{ initialValue: { breakpoints: {}, matches: false } }
|
||||
);
|
||||
private previousGroupsPanelExpanded: boolean | undefined;
|
||||
|
||||
readonly variant = input.required<WorkspaceShellContextPanel>();
|
||||
readonly context = input<WorkspacePortalContext | null>(null);
|
||||
@@ -56,9 +73,51 @@ export class WorkspaceShellContextSidebarComponent {
|
||||
LIVE_SECTIONS.has(section)
|
||||
);
|
||||
});
|
||||
readonly isContextPanelCollapsed = computed(
|
||||
readonly groupsResponsiveSuppressed = computed(
|
||||
() => this.compactViewport().matches
|
||||
);
|
||||
readonly groupsPanelExpanded = computed(() => {
|
||||
const applicable = this.isLiveCategoryRoute();
|
||||
return (
|
||||
!applicable ||
|
||||
this.livePanelState.isPanelExpanded(LIVE_LAYOUT_PANEL.GROUPS, {
|
||||
applicable,
|
||||
responsiveSuppressed: this.groupsResponsiveSuppressed(),
|
||||
})
|
||||
);
|
||||
});
|
||||
readonly canRestoreGroupsPanel = computed(
|
||||
() =>
|
||||
this.isLiveCategoryRoute() &&
|
||||
this.liveSidebarStateService.isCollapsed()
|
||||
!this.groupsResponsiveSuppressed() &&
|
||||
!this.groupsPanelExpanded()
|
||||
);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const expanded = this.groupsPanelExpanded();
|
||||
const previous = this.previousGroupsPanelExpanded;
|
||||
this.previousGroupsPanelExpanded = expanded;
|
||||
if (previous === undefined || previous === expanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
queueMicrotask(() => {
|
||||
const action = expanded ? 'hide' : 'restore';
|
||||
this.hostEl.nativeElement
|
||||
.querySelector<HTMLElement>(
|
||||
`[data-testid="live-groups-panel-${action}"]`
|
||||
)
|
||||
?.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onGroupsPanelExpandedChange(expanded: boolean): void {
|
||||
if (expanded) {
|
||||
this.livePanelState.showPanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
} else {
|
||||
this.livePanelState.hidePanel(LIVE_LAYOUT_PANEL.GROUPS);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user