Skip to content

Commit 9361e98

Browse files
committed
(kb) regionFocusSwitcher internals: keep everything in state
not sure this is a good way to handle that, but it was a bit convoluted to keep track of state change initiator in "_currentUpdateInitiator". Now that it's in the state object it's more logical I think. But i'm not sure of the implications of putting a mouse event object in an observable?
1 parent bb81588 commit 9361e98

File tree

1 file changed

+42
-30
lines changed

1 file changed

+42
-30
lines changed

app/client/components/RegionFocusSwitcher.ts

+42-30
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@ interface SectionRegion {
2222
id: any // this matches a grist document view section id
2323
}
2424
type Region = PanelRegion | SectionRegion;
25-
type UpdateInitiator = {type: 'cycle'} | {type: 'mouse', event?: MouseEvent};
25+
type StateUpdateInitiator = {type: 'cycle'} | {type: 'mouse', event?: MouseEvent};
26+
interface State {
27+
region?: Region;
28+
initiator?: StateUpdateInitiator;
29+
}
2630

2731
/**
2832
* RegionFocusSwitcher enables keyboard navigation between app panels and doc widgets.
2933
*
3034
* It also follow mouse clicks to focus panels accordingly.
3135
*/
3236
export class RegionFocusSwitcher extends Disposable {
33-
// Currently focused region
34-
public readonly current: Observable<Region | undefined>;
37+
// State with currently focused region
38+
private readonly _state: Observable<State>;
3539

3640
private _gristDocObs: Observable<GristDoc | null>;
37-
private _currentUpdateInitiator?: UpdateInitiator;
3841
// Previously focused elements for each panel (not used for view section ids)
3942
private _prevFocusedElements: Record<Panel, Element | null> = {
4043
left: null,
@@ -48,7 +51,10 @@ export class RegionFocusSwitcher extends Disposable {
4851

4952
constructor(private _app: App) {
5053
super();
51-
this.current = Observable.create(this, undefined);
54+
this._state = Observable.create(this, {
55+
region: undefined,
56+
initiator: undefined,
57+
});
5258
this._initiated = Observable.create(this, false);
5359
}
5460

@@ -82,7 +88,7 @@ export class RegionFocusSwitcher extends Disposable {
8288
cancel: this._onEscapeKeypress.bind(this),
8389
}, this, true));
8490

85-
this.autoDispose(this.current.addListener(this._onCurrentUpdate.bind(this)));
91+
this.autoDispose(this._state.addListener(this._onStateChange.bind(this)));
8692

8793
const focusActiveSection = () => this.focusActiveSection();
8894
this._app.on('clipboard_focus', focusActiveSection);
@@ -99,7 +105,7 @@ export class RegionFocusSwitcher extends Disposable {
99105

100106
public focusRegion(
101107
region: Region | undefined,
102-
options: {initiator?: UpdateInitiator} = {}
108+
options: {initiator?: StateUpdateInitiator} = {}
103109
) {
104110
if (region?.type === 'panel' && !getPanelElement(region.id)) {
105111
return;
@@ -113,8 +119,7 @@ export class RegionFocusSwitcher extends Disposable {
113119
throw new Error('view section id is not supported when no view layout is rendered');
114120
}
115121

116-
this._currentUpdateInitiator = options.initiator;
117-
this.current.set(region);
122+
this._state.set({region, initiator: options.initiator});
118123
}
119124

120125
public focusActiveSection() {
@@ -124,7 +129,7 @@ export class RegionFocusSwitcher extends Disposable {
124129
}
125130
}
126131

127-
public reset(initiator?: UpdateInitiator) {
132+
public reset(initiator?: StateUpdateInitiator) {
128133
this.focusRegion(undefined, {initiator});
129134
}
130135

@@ -155,7 +160,7 @@ export class RegionFocusSwitcher extends Disposable {
155160
if (!gristDoc) {
156161
return true;
157162
}
158-
const current = use(this.current);
163+
const current = use(this._state).region;
159164
// on a grist doc, panel content is focusable only if it's the current region
160165
if (current?.type === 'panel' && current.id === id) {
161166
return true;
@@ -186,8 +191,8 @@ export class RegionFocusSwitcher extends Disposable {
186191
}),
187192
dom.cls(`${cssFocusedPanel.className}-focused`, use => {
188193
use(this._initiated);
189-
const current = use(this.current);
190-
return this._currentUpdateInitiator?.type === 'cycle' && current?.type === 'panel' && current.id === id;
194+
const current = use(this._state);
195+
return current.initiator?.type === 'cycle' && current.region?.type === 'panel' && current.region.id === id;
191196
}),
192197
];
193198
}
@@ -196,7 +201,7 @@ export class RegionFocusSwitcher extends Disposable {
196201
const gristDoc = this._getGristDoc();
197202
const cycleRegions = getCycleRegions(gristDoc);
198203
this.focusRegion(getSibling(
199-
this.current.get(),
204+
this._state.get().region,
200205
cycleRegions,
201206
direction,
202207
gristDoc
@@ -212,7 +217,7 @@ export class RegionFocusSwitcher extends Disposable {
212217
* - make sure the internal current region info is set when user clicks on the view layout.
213218
*/
214219
private _onClick(event: MouseEvent) {
215-
const current = this.current.get();
220+
const current = this._state.get().region;
216221
const gristDoc = this._getGristDoc();
217222
if (!gristDoc) {
218223
return;
@@ -255,12 +260,12 @@ export class RegionFocusSwitcher extends Disposable {
255260
}
256261

257262
private _onEscapeKeypress() {
258-
const current = this.current.get();
263+
const {region: current, initiator} = this._state.get();
259264
// Do nothing if we are not focused on a panel
260265
if (current?.type !== 'panel') {
261266
return;
262267
}
263-
const comesFromKeyboard = this._currentUpdateInitiator?.type === 'cycle';
268+
const comesFromKeyboard = initiator?.type === 'cycle';
264269
const panelElement = getPanelElement(current.id);
265270
const activeElement = document.activeElement;
266271
const activeElementIsInPanel = panelElement?.contains(activeElement) && activeElement !== panelElement;
@@ -304,40 +309,47 @@ export class RegionFocusSwitcher extends Disposable {
304309
this._prevFocusedElements[prev.id] = document.activeElement;
305310
}
306311

307-
private _onCurrentUpdate(current: Region | undefined, prev: Region | undefined) {
312+
private _onStateChange(current: State | undefined, prev: State | undefined) {
308313
if (isEqual(current, prev)) {
309314
return;
310315
}
311316

317+
console.log('mhl current update', current);
318+
319+
312320
const gristDoc = this._getGristDoc();
313-
const mouseEvent = this._currentUpdateInitiator?.type === 'mouse'
314-
? this._currentUpdateInitiator.event
321+
const mouseEvent = current?.initiator?.type === 'mouse'
322+
? current.initiator.event
315323
: undefined;
316324

317325
removeFocusRings();
318326
removeTabIndexes();
319327
if (!mouseEvent) {
320-
this._savePrevElementState(prev);
321-
if (prev?.type === 'panel') {
322-
blurPanelChild(prev);
328+
this._savePrevElementState(prev?.region);
329+
if (prev?.region?.type === 'panel') {
330+
blurPanelChild(prev.region);
323331
}
324332
}
325333

326-
const isPanel = current?.type === 'panel';
327-
const panelElement = isPanel && getPanelElement(current.id);
334+
const isPanel = current?.region?.type === 'panel';
335+
const panelElement = isPanel && current.region?.id && getPanelElement(current.region.id);
328336

329337
// actually focus panel element if using keyboard
330-
if (!mouseEvent && isPanel && panelElement) {
331-
focusPanel(current, this._prevFocusedElements[current.id] as HTMLElement | null, gristDoc);
338+
if (!mouseEvent && isPanel && panelElement && current.region) {
339+
focusPanel(
340+
current.region as PanelRegion,
341+
this._prevFocusedElements[current.region.id as Panel] as HTMLElement | null,
342+
gristDoc
343+
);
332344
}
333345

334346
// just make sure view layout commands are disabled if we click on a panel
335347
if (mouseEvent && isPanel && panelElement && gristDoc) {
336348
escapeViewLayout(gristDoc, !!(mouseEvent.target as Element)?.closest(`[${ATTRS.regionId}="right"]`));
337349
}
338350

339-
if (current?.type === 'section' && gristDoc) {
340-
focusSection(current, gristDoc);
351+
if (current?.region?.type === 'section' && gristDoc) {
352+
focusSection(current.region, gristDoc);
341353
}
342354

343355
if (current === undefined && gristDoc) {
@@ -356,7 +368,7 @@ export class RegionFocusSwitcher extends Disposable {
356368
}
357369

358370
private _toggleCreatorPanel() {
359-
const current = this.current.get();
371+
const current = this._state.get().region;
360372
const gristDoc = this._getGristDoc();
361373
if (current?.type === 'panel' && current.id === 'right') {
362374
return this.focusRegion(gristDoc

0 commit comments

Comments
 (0)