Skip to content

Commit bb81588

Browse files
committed
(kb) change where we initialize RegionFocusSwitcher
- now the RFS is created in the App, I guess it makes more sense here, because it's also where the Clipboard is, and now we can more easilly access it across the app if needed… - …but I have a whole mess when initializing now (more than before), to make sure panelAttrs are applied correctly after the grist doc loaded… i'm sure this can be improved somehow? - no more dirty window.regionFocusSwitcher
1 parent cf1a6a0 commit bb81588

File tree

9 files changed

+71
-37
lines changed

9 files changed

+71
-37
lines changed

app/client/components/Clipboard.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ function Clipboard(app) {
7777
onDefaultFocus: () => {
7878
this.copypasteField.value = ' ';
7979
this.copypasteField.select();
80-
if (window.gristRegionFocusSwitcher) {
81-
window.gristRegionFocusSwitcher.focusActiveSection();
82-
}
8380
this._app.trigger('clipboard_focus');
8481
},
8582
onDefaultBlur: () => {

app/client/components/RegionFocusSwitcher.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import {Disposable, dom, Observable, styled} from 'grainjs';
1+
import {Disposable, dom, Listener, Observable, styled} from 'grainjs';
22
import {mod} from 'app/common/gutil';
33
import {SpecialDocPage} from 'app/common/gristUrls';
44
import isEqual from 'lodash/isEqual';
55
import {makeT} from 'app/client/lib/localization';
66
import * as commands from 'app/client/components/commands';
77
import {triggerFocusGrab} from 'app/client/components/Clipboard';
8+
import {App} from 'app/client/ui/App';
89
import {GristDoc} from 'app/client/components/GristDoc';
910
import {theme} from 'app/client/ui2018/cssVars';
1011
import BaseView from 'app/client/components/BaseView';
@@ -31,7 +32,8 @@ type UpdateInitiator = {type: 'cycle'} | {type: 'mouse', event?: MouseEvent};
3132
export class RegionFocusSwitcher extends Disposable {
3233
// Currently focused region
3334
public readonly current: Observable<Region | undefined>;
34-
private readonly _gristDocObs?: Observable<GristDoc | null>;
35+
36+
private _gristDocObs: Observable<GristDoc | null>;
3537
private _currentUpdateInitiator?: UpdateInitiator;
3638
// Previously focused elements for each panel (not used for view section ids)
3739
private _prevFocusedElements: Record<Panel, Element | null> = {
@@ -41,18 +43,30 @@ export class RegionFocusSwitcher extends Disposable {
4143
main: null,
4244
};
4345

44-
constructor(gristDocObs?: Observable<GristDoc | null>) {
46+
private _initiated: Observable<boolean>;
47+
private _initListener?: Listener;
48+
49+
constructor(private _app: App) {
4550
super();
4651
this.current = Observable.create(this, undefined);
47-
if (gristDocObs) {
48-
this._gristDocObs = gristDocObs;
49-
}
52+
this._initiated = Observable.create(this, false);
5053
}
5154

5255
public init(pageContainer: HTMLElement) {
53-
// if we have a grist doc, wait for the current grist doc to be ready before doing anything
56+
if (this._initiated.get()) {
57+
if (this._initListener && !this._initListener.isDisposed()) {
58+
this._initListener.dispose();
59+
}
60+
return;
61+
}
62+
63+
if (this._app.pageModel?.gristDoc) {
64+
this._gristDocObs = this._app.pageModel.gristDoc;
65+
}
66+
67+
// if we have a grist doc, wait for it to be ready before doing anything
5468
if (this._gristDocObs && this._gristDocObs.get() === null) {
55-
this._gristDocObs.addListener((doc, prevDoc) => {
69+
this._initListener = this._gristDocObs.addListener((doc, prevDoc) => {
5670
if (doc && prevDoc === null) {
5771
doc.regionFocusSwitcher = this;
5872
this.init(pageContainer);
@@ -70,11 +84,17 @@ export class RegionFocusSwitcher extends Disposable {
7084

7185
this.autoDispose(this.current.addListener(this._onCurrentUpdate.bind(this)));
7286

87+
const focusActiveSection = () => this.focusActiveSection();
88+
this._app.on('clipboard_focus', focusActiveSection);
89+
this.onDispose(() => this._app.off('clipboard_focus', focusActiveSection));
90+
7391
if (this._gristDocObs) {
7492
const onClick = this._onClick.bind(this);
7593
pageContainer.addEventListener('mouseup', onClick);
7694
this.onDispose(() => pageContainer.removeEventListener('mouseup', onClick));
7795
}
96+
97+
this._initiated.set(true);
7898
}
7999

80100
public focusRegion(
@@ -114,6 +134,7 @@ export class RegionFocusSwitcher extends Disposable {
114134
dom.attr('aria-label', ariaLabel),
115135
dom.attr(ATTRS.regionId, id),
116136
dom.cls('kb-focus-highlighter-group', use => {
137+
use(this._initiated);
117138
// highlight focused elements everywhere except in the grist doc views
118139
if (id !== 'main') {
119140
return true;
@@ -128,6 +149,7 @@ export class RegionFocusSwitcher extends Disposable {
128149
return isSpecialPage(gristDoc);
129150
}),
130151
dom.cls('clipboard_group_focus', use => {
152+
use(this._initiated);
131153
const gristDoc = this._gristDocObs ? use(this._gristDocObs) : null;
132154
// if we are not on a grist doc, whole page is always focusable
133155
if (!gristDoc) {
@@ -145,6 +167,7 @@ export class RegionFocusSwitcher extends Disposable {
145167
return isSpecialPage(gristDoc);
146168
}),
147169
dom.cls('clipboard_forbid_focus', use => {
170+
use(this._initiated);
148171
// forbid focus only on the main panel when on an actual document view (and not a special page)
149172
if (id !== 'main') {
150173
return false;
@@ -162,6 +185,7 @@ export class RegionFocusSwitcher extends Disposable {
162185
return true;
163186
}),
164187
dom.cls(`${cssFocusedPanel.className}-focused`, use => {
188+
use(this._initiated);
165189
const current = use(this.current);
166190
return this._currentUpdateInitiator?.type === 'cycle' && current?.type === 'panel' && current.id === id;
167191
}),

app/client/ui/AccountPage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {detectCurrentLang, makeT} from 'app/client/lib/localization';
22
import {checkName} from 'app/client/lib/nameUtils';
33
import {AppModel, reportError} from 'app/client/models/AppModel';
4+
import {App} from 'app/client/ui/App';
45
import {urlState} from 'app/client/models/gristUrlState';
56
import * as css from 'app/client/ui/AccountPageCss';
67
import {ApiKey} from 'app/client/ui/ApiKey';
@@ -39,7 +40,7 @@ export class AccountPage extends Disposable {
3940
private _allowGoogleLogin = Computed.create(this, (use) => use(this._userObs)?.allowGoogleLogin ?? false)
4041
.onWrite((val) => this._updateAllowGooglelogin(val));
4142

42-
constructor(private _appModel: AppModel) {
43+
constructor(private _appModel: AppModel, private _appObj: App) {
4344
super();
4445
this._setPageTitle();
4546
this._fetchAll().catch(reportError);
@@ -58,6 +59,8 @@ export class AccountPage extends Disposable {
5859
headerMain: this._buildHeaderMain(),
5960
contentMain: this._buildContentMain(),
6061
testId,
62+
}, {
63+
regionFocusSwitcher: this._appObj.regionFocusSwitcher,
6164
});
6265
}
6366

app/client/ui/AdminPanel.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {localStorageJsonObs} from 'app/client/lib/localStorageObs';
44
import {getTimeFromNow} from 'app/client/lib/timeUtils';
55
import {AdminChecks, probeDetails, ProbeDetails} from 'app/client/models/AdminChecks';
66
import {AppModel, getHomeUrl, reportError} from 'app/client/models/AppModel';
7+
import {App} from 'app/client/ui/App';
78
import {AuditLogsModel} from 'app/client/models/AuditLogsModel';
89
import {urlState} from 'app/client/models/gristUrlState';
910
import {showEnterpriseToggle} from 'app/client/ui/ActivationPage';
@@ -35,7 +36,7 @@ const t = makeT('AdminPanel');
3536
export class AdminPanel extends Disposable {
3637
private _page = Computed.create<AdminPanelPage>(this, (use) => use(urlState().state).adminPanel || 'admin');
3738

38-
constructor(private _appModel: AppModel) {
39+
constructor(private _appModel: AppModel, private _appObj: App) {
3940
super();
4041
document.title = getAdminPanelName() + getPageTitleSuffix(getGristConfig());
4142
}
@@ -46,6 +47,8 @@ export class AdminPanel extends Disposable {
4647
headerMain: this._buildMainHeader(),
4748
contentTop: buildHomeBanners(this._appModel),
4849
contentMain: this._buildMainContent(),
50+
}, {
51+
regionFocusSwitcher: this._appObj.regionFocusSwitcher,
4952
});
5053
}
5154

app/client/ui/App.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {ClientScope} from 'app/client/components/ClientScope';
22
import * as Clipboard from 'app/client/components/Clipboard';
3+
import {RegionFocusSwitcher} from 'app/client/components/RegionFocusSwitcher';
4+
import {KeyboardFocusHighlighter} from 'app/client/components/KeyboardFocusHighlighter';
35
import {Comm} from 'app/client/components/Comm';
46
import * as commandList from 'app/client/components/commandList';
57
import * as commands from 'app/client/components/commands';
@@ -24,7 +26,6 @@ import {dom} from 'grainjs';
2426
import * as ko from 'knockout';
2527
import {makeT} from 'app/client/lib/localization';
2628
import { onClickOutside } from 'app/client/lib/domUtils';
27-
import {KeyboardFocusHighlighter} from 'app/client/components/KeyboardFocusHighlighter';
2829

2930
const t = makeT('App');
3031

@@ -39,6 +40,7 @@ export interface App extends DisposableWithEvents {
3940
features: ko.Computed<ISupportedFeatures>;
4041
topAppModel: TopAppModel;
4142
pageModel?: DocPageModel;
43+
regionFocusSwitcher?: RegionFocusSwitcher;
4244
}
4345

4446
/**
@@ -56,6 +58,8 @@ export class AppImpl extends DisposableWithEvents implements App {
5658
// Track the most recently created DocPageModel, for some error handling.
5759
public pageModel?: DocPageModel;
5860

61+
public regionFocusSwitcher?: RegionFocusSwitcher;
62+
5963
private _settings: ko.Observable<{features?: ISupportedFeatures}>;
6064

6165
// Track the version of the server we are communicating with, so that if it changes
@@ -76,9 +80,10 @@ export class AppImpl extends DisposableWithEvents implements App {
7680
this._settings = ko.observable({});
7781
this.features = ko.computed(() => this._settings().features || {});
7882

79-
this.autoDispose(new KeyboardFocusHighlighter());
83+
KeyboardFocusHighlighter.create(this);
8084

8185
if (isDesktop()) {
86+
this.regionFocusSwitcher = RegionFocusSwitcher.create(this, this);
8287
this.autoDispose(Clipboard.create(this));
8388
} else {
8489
// On mobile, we do not want to keep focus on a special textarea (which would cause unwanted

app/client/ui/AppUI.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ function createMainPage(appModel: AppModel, appObj: App) {
8383
} else if (pageType === 'billing') {
8484
return domAsync(loadBillingPage().then(bp => dom.create(bp.BillingPage, appModel)));
8585
} else if (pageType === 'welcome') {
86-
return dom.create(WelcomePage, appModel);
86+
return dom.create(WelcomePage, appModel, appObj);
8787
} else if (pageType === 'account') {
88-
return domAsync(loadAccountPage().then(ap => dom.create(ap.AccountPage, appModel)));
88+
return domAsync(loadAccountPage().then(ap => dom.create(ap.AccountPage, appModel, appObj)));
8989
} else if (pageType === 'admin') {
90-
return domAsync(loadAdminPanel().then(m => dom.create(m.AdminPanel, appModel)));
90+
return domAsync(loadAdminPanel().then(m => dom.create(m.AdminPanel, appModel, appObj)));
9191
} else if (pageType === 'activation') {
9292
return domAsync(loadActivationPage().then(ap => dom.create(ap.getActivationPage(), appModel)));
9393
} else if (pageType === 'audit-logs') {
94-
return domAsync(loadAuditLogsPage().then(m => dom.create(m.AuditLogsPage, appModel)));
94+
return domAsync(loadAuditLogsPage().then(m => dom.create(m.AuditLogsPage, appModel, appObj)));
9595
} else {
9696
return dom.create(pagePanelsDoc, appModel, appObj);
9797
}
@@ -128,6 +128,8 @@ function pagePanelsHome(owner: IDisposableOwner, appModel: AppModel, app: App) {
128128
contentMain: createDocMenu(pageModel),
129129
contentTop: buildHomeBanners(appModel),
130130
testId,
131+
}, {
132+
regionFocusSwitcher: app.regionFocusSwitcher,
131133
});
132134
}
133135

@@ -185,6 +187,6 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App)
185187
contentBottom: dom.create(createBottomBarDoc, pageModel, leftPanelOpen, rightPanelOpen),
186188
banner: dom.create(ViewAsBanner, pageModel),
187189
}, {
188-
gristDoc: pageModel.gristDoc,
190+
regionFocusSwitcher: appObj.regionFocusSwitcher,
189191
});
190192
}

app/client/ui/AuditLogsPage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { buildHomeBanners } from "app/client/components/Banners";
22
import { makeT } from "app/client/lib/localization";
33
import { AppModel, reportError } from "app/client/models/AppModel";
4+
import { App } from "app/client/ui/App";
45
import { AuditLogsModel } from "app/client/models/AuditLogsModel";
56
import { urlState } from "app/client/models/gristUrlState";
67
import { AppHeader } from "app/client/ui/AppHeader";
@@ -40,7 +41,7 @@ export class AuditLogsPage extends Disposable {
4041
(_use, s) => s.auditLogs
4142
);
4243

43-
constructor(private _appModel: AppModel) {
44+
constructor(private _appModel: AppModel, private _appObj: App) {
4445
super();
4546
this._setTitle();
4647
}
@@ -73,6 +74,8 @@ export class AuditLogsPage extends Disposable {
7374
headerMain: this._buildHeader(),
7475
contentTop: buildHomeBanners(this._appModel),
7576
contentMain: this._buildContent(),
77+
}, {
78+
regionFocusSwitcher: this._appObj.regionFocusSwitcher,
7679
});
7780
}
7881

app/client/ui/PagePanels.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import once from 'lodash/once';
1616
import {SessionObs} from 'app/client/lib/sessionObs';
1717
import debounce from 'lodash/debounce';
1818
import {RegionFocusSwitcher} from 'app/client/components/RegionFocusSwitcher';
19-
import {GristDoc} from 'app/client/components/GristDoc';
2019

2120
const t = makeT('PagePanels');
2221

@@ -50,10 +49,7 @@ export interface PageContents {
5049
}
5150

5251
interface PagePanelsOptions {
53-
/**
54-
* If provided, the region kb focus switcher will also cycle through the given doc's widgets.
55-
*/
56-
gristDoc?: Observable<GristDoc | null>;
52+
regionFocusSwitcher?: RegionFocusSwitcher;
5753
}
5854

5955
export function pagePanels(
@@ -69,6 +65,8 @@ export function pagePanels(
6965
const bannerHeight = Observable.create(null, 0);
7066
const isScreenResizingObs = isScreenResizing();
7167

68+
const regionFocusSwitcher = options.regionFocusSwitcher;
69+
7270
let lastLeftOpen = left.panelOpen.get();
7371
let lastRightOpen = right?.panelOpen.get() || false;
7472
let leftPaneDom: HTMLElement;
@@ -77,10 +75,6 @@ export function pagePanels(
7775
let contentTopDom: HTMLElement;
7876
let onLeftTransitionFinish = noop;
7977

80-
const regionFocusSwitcher = RegionFocusSwitcher.create(null, options.gristDoc);
81-
// @ts-expect-error just dirty code to check an idea. To remove later.
82-
window.gristRegionFocusSwitcher = regionFocusSwitcher;
83-
8478
// When switching to mobile mode, close panels; when switching to desktop, restore the
8579
// last desktop state.
8680
const sub1 = subscribe(isNarrowScreenObs(), (use, narrow) => {
@@ -103,7 +97,7 @@ export function pagePanels(
10397
if (narrow) {
10498
left.panelOpen.set(false);
10599
}
106-
regionFocusSwitcher.reset();
100+
regionFocusSwitcher?.reset();
107101
});
108102

109103
const pauseSavingLeft = (yesNo: boolean) => {
@@ -151,11 +145,11 @@ export function pagePanels(
151145
}),
152146
cssContentMain(
153147
(el) => {
154-
regionFocusSwitcher.init(el);
148+
regionFocusSwitcher?.init(el);
155149
},
156150
leftPaneDom = cssLeftPane(
157151
testId('left-panel'),
158-
regionFocusSwitcher.panelAttrs('left', t('Main navigation and document settings (left panel)')),
152+
regionFocusSwitcher?.panelAttrs('left', t('Main navigation and document settings (left panel)')),
159153
cssOverflowContainer(
160154
contentWrapper = cssLeftPanelContainer(
161155
cssLeftPaneHeader(
@@ -292,7 +286,7 @@ export function pagePanels(
292286
cssMainPane(
293287
mainHeaderDom = cssTopHeader(
294288
testId('top-header'),
295-
regionFocusSwitcher.panelAttrs('top', t('Document header')),
289+
regionFocusSwitcher?.panelAttrs('top', t('Document header')),
296290
(left.hideOpener ? null :
297291
cssPanelOpener('PanelRight', cssPanelOpener.cls('-open', left.panelOpen),
298292
testId('left-opener'),
@@ -315,7 +309,7 @@ export function pagePanels(
315309
),
316310

317311
cssContentMainPane(
318-
regionFocusSwitcher.panelAttrs('main', t('Main content')),
312+
regionFocusSwitcher?.panelAttrs('main', t('Main content')),
319313
page.contentMain,
320314
),
321315

@@ -332,7 +326,7 @@ export function pagePanels(
332326

333327
rightPaneDom = cssRightPane(
334328
testId('right-panel'),
335-
regionFocusSwitcher.panelAttrs('right', t('Creator panel (right panel)')),
329+
regionFocusSwitcher?.panelAttrs('right', t('Creator panel (right panel)')),
336330
cssRightPaneHeader(
337331
right.header,
338332
dom.style('margin-bottom', use => use(bannerHeight) + 'px')

app/client/ui/WelcomePage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Disposable, dom, domComputed, DomContents, MultiHolder, Observable, sty
22

33
import { handleSubmit } from "app/client/lib/formUtils";
44
import { AppModel } from "app/client/models/AppModel";
5+
import { App } from 'app/client/ui/App';
56
import { getLoginUrl, getSignupUrl, urlState } from "app/client/models/gristUrlState";
67
import { AccountWidget } from "app/client/ui/AccountWidget";
78
import { AppHeader } from 'app/client/ui/AppHeader';
@@ -36,7 +37,7 @@ function handleSubmitForm(
3637

3738
export class WelcomePage extends Disposable {
3839

39-
constructor(private _appModel: AppModel) {
40+
constructor(private _appModel: AppModel, private _appObj: App) {
4041
super();
4142
}
4243

@@ -58,6 +59,8 @@ export class WelcomePage extends Disposable {
5859
page === 'teams' ? dom.create(buildWelcomeSitePicker, this._appModel) :
5960
this._buildPageContent(page)
6061
),
62+
}, {
63+
regionFocusSwitcher: this._appObj.regionFocusSwitcher,
6164
});
6265
}
6366

0 commit comments

Comments
 (0)