@inboxsdk/core 0.2.29-alpha.2 → 0.2.29-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  import Kefir from 'kefir';
2
- import kefirStopper from 'kefir-stopper';
3
2
  import kefirBus from 'kefir-bus';
3
+ import cx from 'classnames';
4
4
  import EventEmitter from '../lib/safe-event-emitter';
5
5
  import { GmailAppMenuItemView, NATIVE_CLASS, INBOXSDK_CLASS, } from '../dom-driver/gmail/views/gmail-app-menu-item-view';
6
6
  import { NATIVE_CLASS as PANEL_NATIVE_CLASS, CollapsiblePanelView, } from './collapsible-panel-view';
7
7
  import { addCollapsiblePanel } from '../dom-driver/gmail/gmail-driver/add-collapsible-panel';
8
8
  import GmailElementGetter from '../dom-driver/gmail/gmail-element-getter';
9
+ import defer from '../../common/defer';
9
10
  var NativeAppMenuClassName;
10
11
  (function (NativeAppMenuClassName) {
11
12
  NativeAppMenuClassName["MAIL"] = "XJ";
@@ -33,12 +34,13 @@ const routeIDtoMenuItemClass = Object.fromEntries([
33
34
  ...SPACES_ROUTES.map((route) => [route, NativeAppMenuClassName.SPACES]),
34
35
  ...MEET_ROUTES.map((route) => [route, NativeAppMenuClassName.MEET]),
35
36
  ]);
36
- const initialRoutingStateStopper = kefirStopper();
37
37
  /**
38
38
  * Contains both a native Gmail app menu item and a collapsible panel.
39
39
  */
40
40
  export class AppMenuItemView extends EventEmitter {
41
41
  static #menuItemChangeStream = Kefir.pool();
42
+ static #menuItemChangeBus = kefirBus();
43
+ static #menuItemAddedDeferred = defer();
42
44
  static #routeViewDriverStream;
43
45
  static #menuItemToPanelMap = new Map();
44
46
  static #appMenuItemViews = new Set();
@@ -54,23 +56,18 @@ export class AppMenuItemView extends EventEmitter {
54
56
  const button = appMenuElement?.querySelector(`.${NATIVE_CLASS}.${ACTIVE}`);
55
57
  return button;
56
58
  }
59
+ static #isPanelLess() {
60
+ const activePanel = AppMenuItemView.#getActivePanel();
61
+ return !activePanel;
62
+ }
57
63
  static #setUpRouteViewDriverStream(driver) {
58
64
  if (AppMenuItemView.#routeViewDriverStream)
59
65
  return;
60
66
  AppMenuItemView.#routeViewDriverStream = driver.getRouteViewDriverStream();
61
- const bus = kefirBus();
62
- AppMenuItemView.#menuItemChangeStream.plug(bus);
63
- AppMenuItemView.#routeViewDriverStream
64
- .takeUntilBy(initialRoutingStateStopper)
65
- .onValue(async (routeView) => {
67
+ // Activate menu items on route changes. Used for deep-links, browser history navigation etc.
68
+ AppMenuItemView.#routeViewDriverStream.onValue(async (routeView) => {
66
69
  const routeViewID = routeView.getRouteID();
67
70
  const routeType = routeView.getRouteType();
68
- const routeViewParams = routeView.getParams();
69
- console.debug({
70
- routeViewID,
71
- routeType,
72
- routeViewParams,
73
- });
74
71
  /* Handling custom app menu items */
75
72
  const descriptors = Array.from(AppMenuItemView.#appMenuItemViews, async (appMenuItemView) => {
76
73
  const gmailView = await appMenuItemView.#gmailViewPromise;
@@ -81,26 +78,37 @@ export class AppMenuItemView extends EventEmitter {
81
78
  return descriptor?.isRouteActive(routeView);
82
79
  }) ?? [];
83
80
  if (gmailView?.element) {
84
- bus.emit(['click', gmailView.element]);
81
+ AppMenuItemView.#menuItemChangeBus.emit([
82
+ 'route-activated',
83
+ gmailView.element,
84
+ ]);
85
85
  return;
86
86
  }
87
87
  /* Handling native app menu items */
88
- const selector = `.${routeViewID
89
- ? routeIDtoMenuItemClass[routeViewID] ?? NativeAppMenuClassName.MAIL
90
- : NativeAppMenuClassName.MAIL}`;
91
- const element = document.querySelector(selector);
92
- if (element) {
93
- bus.emit(['click', element]);
94
- return;
88
+ let menuItemClass = '';
89
+ if (routeViewID) {
90
+ menuItemClass = routeIDtoMenuItemClass[routeViewID];
91
+ }
92
+ if ((!routeViewID || !menuItemClass) && routeType !== 'CUSTOM') {
93
+ menuItemClass = NativeAppMenuClassName.MAIL;
94
+ }
95
+ const menuItemElement = menuItemClass &&
96
+ document.querySelector(`.${menuItemClass}`);
97
+ if (menuItemElement) {
98
+ AppMenuItemView.#menuItemChangeBus.emit([
99
+ 'route-activated',
100
+ menuItemElement,
101
+ ]);
95
102
  }
96
103
  });
97
104
  }
98
105
  static {
99
- (async () => {
106
+ (async function init() {
100
107
  const gmailAppMenu = await GmailElementGetter.getAppMenuAsync();
101
108
  if (!gmailAppMenu) {
102
109
  return;
103
110
  }
111
+ AppMenuItemView.#menuItemChangeStream.plug(AppMenuItemView.#menuItemChangeBus);
104
112
  // Set up menu item panel -> menu collapsible panel element mappings
105
113
  for (const [menuItemClass, panelClass] of nativeButtonToPanelMap) {
106
114
  const menuItemElement = document.querySelector(`.${NATIVE_CLASS}.${menuItemClass}`);
@@ -111,97 +119,55 @@ export class AppMenuItemView extends EventEmitter {
111
119
  AppMenuItemView.#menuItemToPanelMap.set(menuItemElement, panelElement ?? undefined);
112
120
  }
113
121
  }
114
- // Set up event listeners we use to control menu UI
115
- const stream = Kefir.stream((emitter) => {
116
- // We intercept some events emitted on Gmail (native) menu items to gain full control of the menu (menu items and panels).
117
- // Thus, some native event handlers won't be invoked so we replicate the Gmail's behavior by ourselves.
118
- const appMenuElement = GmailElementGetter.getAppMenuContainer();
119
- for (const nativeMenuItem of appMenuElement?.querySelectorAll(`.${NATIVE_CLASS}:not(.${INBOXSDK_CLASS})`) ?? []) {
120
- // Gmail uses this listener for panel hover state activation. It's prevented from propagation and we handle UI updates
121
- nativeMenuItem.addEventListener('mouseenter', (e) => {
122
- e.stopImmediatePropagation();
123
- emitter.emit(['mouseenter', nativeMenuItem, e]);
124
- }, true);
125
- // Gmail uses this listener for panel hover state deactivation. It's prevented from propagation and we handle UI updates
126
- nativeMenuItem.addEventListener('mouseleave', (e) => {
127
- e.stopImmediatePropagation();
128
- emitter.emit(['mouseleave', nativeMenuItem, e]);
129
- }, true);
130
- // Click event isn't prevented from propagation to let Gmail handle routing. The styling is handled by us
131
- nativeMenuItem.addEventListener('click', () => {
132
- emitter.emit(['click', nativeMenuItem]);
133
- });
134
- // Handle moving cursor out of panel. We use it to enhance the styling since it won't be handled by Gmail
135
- const panel = AppMenuItemView.#menuItemToPanelMap.get(nativeMenuItem);
136
- panel?.addEventListener('mouseleave', (e) => {
137
- emitter.emit(['mouseleave', nativeMenuItem, e]);
138
- });
139
- }
140
- });
141
- AppMenuItemView.#menuItemChangeStream.plug(stream);
142
122
  })();
143
123
  }
124
+ static {
125
+ AppMenuItemView.#menuItemAddedDeferred.promise.then(() => {
126
+ // Set up event listeners we use to control menu UI
127
+ // We intercept some events emitted on Gmail (native) menu items to gain full control of the menu (menu items and panels).
128
+ // Thus, some native event handlers won't be invoked so we replicate the Gmail's behavior by ourselves.
129
+ const appMenuElement = GmailElementGetter.getAppMenuContainer();
130
+ for (const nativeMenuItem of appMenuElement?.querySelectorAll(`.${NATIVE_CLASS}:not(.${INBOXSDK_CLASS})`) ?? []) {
131
+ // Gmail uses this listener for panel hover state activation. It's prevented from propagation and we handle UI updates
132
+ const buttonElement = nativeMenuItem.querySelector('.V6.CL');
133
+ buttonElement?.addEventListener('mouseenter', (e) => {
134
+ e.stopImmediatePropagation();
135
+ AppMenuItemView.#menuItemChangeBus.emit([
136
+ 'mouseenter',
137
+ nativeMenuItem,
138
+ e,
139
+ ]);
140
+ }, true);
141
+ // Gmail uses this listener for panel hover state deactivation. It's prevented from propagation and we handle UI updates
142
+ nativeMenuItem.addEventListener('mouseleave', (e) => {
143
+ e.stopImmediatePropagation();
144
+ AppMenuItemView.#menuItemChangeBus.emit([
145
+ 'mouseleave',
146
+ nativeMenuItem,
147
+ e,
148
+ ]);
149
+ }, true);
150
+ // Click event isn't prevented from propagation to let Gmail handle routing. The styling is handled by us
151
+ nativeMenuItem.addEventListener('click', () => {
152
+ AppMenuItemView.#menuItemChangeBus.emit(['click', nativeMenuItem]);
153
+ });
154
+ // Handle moving cursor out of panel. We use it to enhance the styling since it won't be handled by Gmail
155
+ const panel = AppMenuItemView.#menuItemToPanelMap.get(nativeMenuItem);
156
+ panel?.addEventListener('mouseleave', (e) => {
157
+ AppMenuItemView.#menuItemChangeBus.emit([
158
+ 'mouseleave',
159
+ nativeMenuItem,
160
+ e,
161
+ ]);
162
+ });
163
+ }
164
+ });
165
+ }
144
166
  static {
145
167
  AppMenuItemView.#menuItemChangeStream.onValue(async (v) => {
146
168
  const [type, menuItem, mouseEvent] = v;
147
169
  const { ACTIVE, HOVER } = GmailAppMenuItemView.elementCss;
148
- if (type === 'mouseenter') {
149
- const panel = AppMenuItemView.#menuItemToPanelMap.get(menuItem);
150
- if (mouseEvent.relatedTarget instanceof Node &&
151
- (menuItem.contains(mouseEvent.relatedTarget) ||
152
- panel?.contains(mouseEvent.relatedTarget)))
153
- return;
154
- const activeMenuItem = AppMenuItemView.#getActiveMenuItem();
155
- const activePanel = AppMenuItemView.#getActivePanel();
156
- if (activeMenuItem === menuItem && activePanel === panel)
157
- return;
158
- // deactivate active panel
159
- if (activePanel) {
160
- activePanel.style.removeProperty('height');
161
- activePanel.classList.remove(CollapsiblePanelView.elementCss.ACTIVE);
162
- activePanel.classList.remove(CollapsiblePanelView.elementCss.HOVER);
163
- }
164
- // activate new panel
165
- if (panel) {
166
- const height = window.innerHeight - panel.getBoundingClientRect().top;
167
- panel.style.cssText = `height: ${height}px;`;
168
- panel.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
169
- panel.classList.add(CollapsiblePanelView.elementCss.HOVER);
170
- // hover menu item
171
- menuItem.classList.add(HOVER);
172
- }
173
- else {
174
- // panel doesn't exist (i.e. Meet) so activate active menu item's panel instead (as Gmail currently does -- FEB 2023)
175
- const panel = activeMenuItem &&
176
- AppMenuItemView.#menuItemToPanelMap.get(activeMenuItem);
177
- panel?.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
178
- }
179
- }
180
- if (type === 'mouseleave') {
181
- const activeMenuItem = AppMenuItemView.#getActiveMenuItem();
182
- const activePanel = AppMenuItemView.#getActivePanel();
183
- const activeMenuItemPanel = activeMenuItem &&
184
- AppMenuItemView.#menuItemToPanelMap.get(activeMenuItem);
185
- if (mouseEvent.relatedTarget instanceof Node &&
186
- (menuItem.contains(mouseEvent.relatedTarget) ||
187
- activePanel?.contains(mouseEvent.relatedTarget)))
188
- return;
189
- if (activePanel === activeMenuItemPanel)
190
- return;
191
- // unhover menu item
192
- menuItem.classList.remove(HOVER);
193
- // deactivate active panel
194
- if (activePanel) {
195
- activePanel.style.removeProperty('height');
196
- activePanel.classList.remove(CollapsiblePanelView.elementCss.ACTIVE);
197
- activePanel.classList.remove(CollapsiblePanelView.elementCss.HOVER);
198
- }
199
- // activate active menu item panel
200
- if (activeMenuItemPanel) {
201
- activeMenuItemPanel.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
202
- }
203
- }
204
- if (type === 'click') {
170
+ function handleActivate() {
205
171
  const appMenuElement = GmailElementGetter.getAppMenuContainer();
206
172
  // deactivate active panel
207
173
  const activePanel = AppMenuItemView.#getActivePanel();
@@ -223,6 +189,92 @@ export class AppMenuItemView extends EventEmitter {
223
189
  panel.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
224
190
  }
225
191
  }
192
+ switch (type) {
193
+ case 'mouseenter': {
194
+ const panel = AppMenuItemView.#menuItemToPanelMap.get(menuItem);
195
+ const activeMenuItem = AppMenuItemView.#getActiveMenuItem();
196
+ const activePanel = AppMenuItemView.#getActivePanel();
197
+ const burgerMenuOpen = GmailElementGetter.isAppBurgerMenuOpen();
198
+ // hover-styled panel is displayed for collapsed burger menu
199
+ if (activeMenuItem === menuItem &&
200
+ activePanel === panel &&
201
+ burgerMenuOpen)
202
+ return;
203
+ // deactivate active panel
204
+ if (activePanel) {
205
+ activePanel.style.removeProperty('height');
206
+ activePanel.classList.remove(CollapsiblePanelView.elementCss.ACTIVE);
207
+ activePanel.classList.remove(CollapsiblePanelView.elementCss.HOVER);
208
+ }
209
+ // activate new panel
210
+ if (panel) {
211
+ const height = window.innerHeight - panel.getBoundingClientRect().top;
212
+ panel.style.cssText = `height: ${height}px;`;
213
+ panel.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
214
+ panel.classList.add(CollapsiblePanelView.elementCss.HOVER);
215
+ // hover menu item
216
+ menuItem.classList.add(HOVER);
217
+ // handle burger menu collapsed
218
+ if (!burgerMenuOpen) {
219
+ for (const panel of document.querySelectorAll(CollapsiblePanelView.elementSelectors.NATIVE) ?? []) {
220
+ panel.classList.add(CollapsiblePanelView.elementCss.COLLAPSED_HOVER);
221
+ }
222
+ }
223
+ }
224
+ else {
225
+ // panel doesn't exist (i.e. Meet) so activate active menu item's panel instead (as Gmail currently does -- FEB 2023)
226
+ const panel = activeMenuItem &&
227
+ AppMenuItemView.#menuItemToPanelMap.get(activeMenuItem);
228
+ panel?.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
229
+ }
230
+ break;
231
+ }
232
+ case 'mouseleave': {
233
+ const activeMenuItem = AppMenuItemView.#getActiveMenuItem();
234
+ const activePanel = AppMenuItemView.#getActivePanel();
235
+ const activeMenuItemPanel = activeMenuItem &&
236
+ AppMenuItemView.#menuItemToPanelMap.get(activeMenuItem);
237
+ if (mouseEvent.relatedTarget instanceof Node &&
238
+ (menuItem.contains(mouseEvent.relatedTarget) ||
239
+ activePanel?.contains(mouseEvent.relatedTarget)))
240
+ return;
241
+ const burgerMenuOpen = GmailElementGetter.isAppBurgerMenuOpen();
242
+ if (activePanel === activeMenuItemPanel && burgerMenuOpen)
243
+ return;
244
+ // unhover menu item
245
+ menuItem.classList.remove(HOVER);
246
+ // handle burger menu collapsed
247
+ for (const panel of document.querySelectorAll(CollapsiblePanelView.elementSelectors.NATIVE) ?? []) {
248
+ panel.classList.remove(CollapsiblePanelView.elementCss.COLLAPSED_HOVER);
249
+ }
250
+ // deactivate active panel
251
+ if (activePanel) {
252
+ activePanel.style.removeProperty('height');
253
+ activePanel.classList.remove(CollapsiblePanelView.elementCss.ACTIVE);
254
+ activePanel.classList.remove(CollapsiblePanelView.elementCss.HOVER);
255
+ }
256
+ // activate active menu item panel
257
+ if (activeMenuItemPanel) {
258
+ activeMenuItemPanel.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
259
+ }
260
+ break;
261
+ }
262
+ case 'click': {
263
+ handleActivate();
264
+ // handle panel-less mode
265
+ const isPanelLess = AppMenuItemView.#isPanelLess();
266
+ for (const panel of document.querySelectorAll(CollapsiblePanelView.elementSelectors.NATIVE) ?? []) {
267
+ panel.classList.toggle(CollapsiblePanelView.elementCss.PANEL_LESS, isPanelLess);
268
+ }
269
+ GmailElementGetter.getAppMenu()?.classList.toggle('aTO', isPanelLess);
270
+ GmailElementGetter.getAppHeader()?.classList.toggle('aTO', isPanelLess);
271
+ break;
272
+ }
273
+ case 'route-activated': {
274
+ handleActivate();
275
+ break;
276
+ }
277
+ }
226
278
  });
227
279
  }
228
280
  get menuItemDescriptor() {
@@ -232,8 +284,6 @@ export class AppMenuItemView extends EventEmitter {
232
284
  #destroyed = false;
233
285
  #gmailViewPromise;
234
286
  #collapsiblePanel;
235
- #appMenuItemStream;
236
- #collapsiblePanelStream;
237
287
  #menuItemDescriptor;
238
288
  constructor(driver, menuItemDescriptor) {
239
289
  super();
@@ -249,41 +299,54 @@ export class AppMenuItemView extends EventEmitter {
249
299
  if (gmailElement) {
250
300
  AppMenuItemView.#menuItemToPanelMap.set(gmailElement, undefined);
251
301
  }
252
- this.#appMenuItemStream = Kefir.stream((emitter) => {
253
- gmailView.on('destroy', () => {
254
- emitter.end();
255
- this.remove();
256
- });
257
- gmailView.on('click', () => {
258
- emitter.emit(['click', gmailElement]);
259
- this.emit('click');
260
- });
261
- gmailView.on('hover', (e) => {
262
- emitter.emit(['mouseenter', gmailElement, e]);
263
- this.emit('hover');
264
- });
265
- gmailView.on('blur', (e) => {
266
- emitter.emit(['mouseleave', gmailElement, e]);
267
- this.emit('blur');
268
- });
302
+ gmailView.on('destroy', () => {
303
+ this.remove();
304
+ });
305
+ gmailView.on('click', () => {
306
+ AppMenuItemView.#menuItemChangeBus.emit(['click', gmailElement]);
307
+ this.emit('click');
308
+ });
309
+ gmailView.on('hover', (e) => {
310
+ AppMenuItemView.#menuItemChangeBus.emit([
311
+ 'mouseenter',
312
+ gmailElement,
313
+ e,
314
+ ]);
315
+ this.emit('hover');
316
+ });
317
+ gmailView.on('blur', (e) => {
318
+ AppMenuItemView.#menuItemChangeBus.emit([
319
+ 'mouseleave',
320
+ gmailElement,
321
+ e,
322
+ ]);
323
+ this.emit('blur');
269
324
  });
270
- AppMenuItemView.#menuItemChangeStream.plug(this.#appMenuItemStream);
271
- AppMenuItemView.#setUpRouteViewDriverStream(driver);
272
- AppMenuItemView.#appMenuItemViews.add(this);
273
325
  });
326
+ AppMenuItemView.#setUpRouteViewDriverStream(driver);
327
+ AppMenuItemView.#menuItemAddedDeferred.resolve(undefined);
328
+ AppMenuItemView.#appMenuItemViews.add(this);
274
329
  }
275
330
  async addCollapsiblePanel(panelDescriptor) {
276
- const collapsiblePanel = await addCollapsiblePanel(this.#driver, panelDescriptor);
331
+ const descriptor = {
332
+ ...panelDescriptor,
333
+ className: cx(panelDescriptor.className, {
334
+ [CollapsiblePanelView.elementCss.COLLAPSED]: !GmailElementGetter.isAppBurgerMenuOpen(),
335
+ [CollapsiblePanelView.elementCss.PANEL_LESS]: AppMenuItemView.#isPanelLess(),
336
+ }),
337
+ };
338
+ const collapsiblePanel = await addCollapsiblePanel(this.#driver, descriptor);
277
339
  if (collapsiblePanel) {
278
340
  this.#gmailViewPromise.then((gmailView) => {
279
341
  const gmailElement = gmailView.element;
280
342
  if (gmailElement) {
281
- this.#collapsiblePanelStream = Kefir.stream((emitter) => {
282
- collapsiblePanel.on('blur', (e) => {
283
- emitter.value(['mouseleave', gmailElement, e]);
284
- });
343
+ collapsiblePanel.on('blur', (e) => {
344
+ AppMenuItemView.#menuItemChangeBus.value([
345
+ 'mouseleave',
346
+ gmailElement,
347
+ e,
348
+ ]);
285
349
  });
286
- AppMenuItemView.#menuItemChangeStream.plug(this.#collapsiblePanelStream);
287
350
  }
288
351
  if (gmailElement && collapsiblePanel) {
289
352
  AppMenuItemView.#menuItemToPanelMap.set(gmailElement, collapsiblePanel.element);
@@ -296,6 +359,7 @@ export class AppMenuItemView extends EventEmitter {
296
359
  async update(menuItemDescriptor) {
297
360
  const gmailView = await this.#gmailViewPromise;
298
361
  gmailView.setMenuItemDescriptor(menuItemDescriptor);
362
+ this.#menuItemDescriptor = menuItemDescriptor;
299
363
  }
300
364
  remove() {
301
365
  if (this.#destroyed)
@@ -308,22 +372,6 @@ export class AppMenuItemView extends EventEmitter {
308
372
  AppMenuItemView.#menuItemToPanelMap.delete(gmailView.element);
309
373
  gmailView.remove();
310
374
  });
311
- this.#appMenuItemStream &&
312
- AppMenuItemView.#menuItemChangeStream.unplug(this.#appMenuItemStream);
313
- this.#collapsiblePanelStream &&
314
- AppMenuItemView.#menuItemChangeStream.unplug(this.#collapsiblePanelStream);
315
375
  AppMenuItemView.#appMenuItemViews.delete(this);
316
- initialRoutingStateStopper.destroy();
317
- }
318
- async #activate() {
319
- const gmailView = await this.#gmailViewPromise;
320
- const appMenuElement = GmailElementGetter.getAppMenuContainer();
321
- const { ACTIVE } = GmailAppMenuItemView.elementCss;
322
- // Note this affects both the active app menu item and the collapsible panel.
323
- for (const el of appMenuElement?.querySelectorAll(`.${ACTIVE}`) ?? []) {
324
- el.classList.remove(ACTIVE);
325
- }
326
- gmailView.activate();
327
- this.#collapsiblePanel?.activate();
328
376
  }
329
377
  }
@@ -19,19 +19,21 @@ export declare class CollapsiblePanelView extends CollapsiblePanelView_base {
19
19
  };
20
20
  static elementCss: {
21
21
  readonly ACTIVE: "apV";
22
- readonly COLLAPSED: "aBa";
22
+ readonly COLLAPSED: "aBA";
23
23
  /** A hover popover has both ACTIVE _and_ HOVER */
24
24
  readonly HOVER: "aJu";
25
+ readonly COLLAPSED_HOVER: "bym";
26
+ readonly PANEL_LESS: "a3W";
25
27
  };
26
- get element(): HTMLElement | undefined;
28
+ get loading(): boolean;
29
+ setLoading(loading: boolean): void;
30
+ get panelDescriptor(): AppMenuItemPanelDescriptor;
27
31
  set panelDescriptor(panelDescriptor: AppMenuItemPanelDescriptor);
28
- constructor(driver: GmailDriver);
29
- /**
30
- * @internal
31
- */
32
- activate(): void;
32
+ get element(): HTMLElement;
33
+ get scrollablePaneElement(): Element | null;
34
+ constructor(driver: GmailDriver, panelDescriptor: AppMenuItemPanelDescriptor);
33
35
  remove(): void;
34
- addNavItem(navItemDescriptor: NavItemDescriptor | Kefir.Observable<NavItemDescriptor, any>): NavItemView | undefined;
36
+ addNavItem(navItemDescriptor: NavItemDescriptor | Kefir.Observable<NavItemDescriptor, any>): NavItemView;
35
37
  }
36
38
  export {};
37
39
  //# sourceMappingURL=collapsible-panel-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collapsible-panel-view.d.ts","sourceRoot":"","sources":["../../../../../src/platform-implementation-js/views/collapsible-panel-view.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAIpE,OAAO,WAAW,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+CAA+C,CAAC;AAClF,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,eAAO,MAAM,YAAY,OAAiB,CAAC;AAC3C,eAAO,MAAM,cAAc,8BAAwC,CAAC;AAQpE,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CAC/B,CAAC;mDAEmE,aAAa,aAAa,CAAC;AAAhG,qBAAa,oBAAqB,SAAQ,yBAAuD;;IAC/F,MAAM,CAAC,gBAAgB;;;MAGZ;IACX,MAAM,CAAC,UAAU;;;QAGf,kDAAkD;;MAEzC;IAQX,IAAI,OAAO,4BAEV;IAED,IAAI,eAAe,CAAC,eAAe,EAAE,0BAA0B,EAG9D;gBAEW,MAAM,EAAE,WAAW;IAS/B;;OAEG;IACH,QAAQ;IAQR,MAAM;IAON,UAAU,CACR,iBAAiB,EACb,iBAAiB,GACjB,KAAK,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC;CA+J/C"}
1
+ {"version":3,"file":"collapsible-panel-view.d.ts","sourceRoot":"","sources":["../../../../../src/platform-implementation-js/views/collapsible-panel-view.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAIpE,OAAO,WAAW,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+CAA+C,CAAC;AAClF,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,eAAO,MAAM,YAAY,OAAiB,CAAC;AAC3C,eAAO,MAAM,cAAc,8BAAwC,CAAC;AAapE,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CAC/B,CAAC;mDAEmE,aAAa,aAAa,CAAC;AAAhG,qBAAa,oBAAqB,SAAQ,yBAAuD;;IAC/F,MAAM,CAAC,gBAAgB;;;MAGZ;IACX,MAAM,CAAC,UAAU;;;QAGf,kDAAkD;;;;MAIzC;IASX,IAAI,OAAO,YAEV;IAED,UAAU,CAAC,OAAO,EAAE,OAAO;IAS3B,IAAI,eAAe,IAIkB,0BAA0B,CAF9D;IAED,IAAI,eAAe,CAAC,eAAe,EAAE,0BAA0B,EAG9D;IAED,IAAI,OAAO,gBAEV;IAED,IAAI,qBAAqB,mBAExB;gBAGC,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,0BAA0B;IAY7C,MAAM;IAON,UAAU,CACR,iBAAiB,EACb,iBAAiB,GACjB,KAAK,CAAC,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC;CAsL/C"}
@@ -11,6 +11,10 @@ export const INBOXSDK_CLASS = 'inboxsdk__collapsiblePanel';
11
11
  const ELEMENT_CLASS = `${NATIVE_CLASS} ${INBOXSDK_CLASS} oy8Mbf`;
12
12
  const PRIMARY_BUTTON_ELEMENT_CLASS = 'T-I T-I-KE L3';
13
13
  const PRIMARY_BUTTON_ELEMENT_SELECTOR = '.T-I.T-I-KE.L3';
14
+ const scrollablePanelClass = 'at9';
15
+ const scrollablePanelSelector = `.${scrollablePanelClass}`;
16
+ const loadingElementClass = 'inboxsdk__collapsiblePanel_loading_container';
17
+ const loadingElementSelector = `.${loadingElementClass}`;
14
18
  const NAV_MENU_CONTAINER_ELEMENT_SELECTOR = '.at9 .n3 .TK';
15
19
  export class CollapsiblePanelView extends EventEmitter {
16
20
  static elementSelectors = {
@@ -19,9 +23,11 @@ export class CollapsiblePanelView extends EventEmitter {
19
23
  };
20
24
  static elementCss = {
21
25
  ACTIVE: 'apV',
22
- COLLAPSED: 'aBa',
26
+ COLLAPSED: 'aBA',
23
27
  /** A hover popover has both ACTIVE _and_ HOVER */
24
28
  HOVER: 'aJu',
29
+ COLLAPSED_HOVER: 'bym',
30
+ PANEL_LESS: 'a3W',
25
31
  };
26
32
  #panelDescriptor;
27
33
  #element;
@@ -29,42 +35,50 @@ export class CollapsiblePanelView extends EventEmitter {
29
35
  #id = Math.random().toFixed(3);
30
36
  #ARIA_LABELLED_BY_ID = Math.random().toFixed(3);
31
37
  #driver;
32
- get element() {
33
- return this.#element;
38
+ #loading;
39
+ get loading() {
40
+ return this.#loading;
41
+ }
42
+ setLoading(loading) {
43
+ const changed = this.#loading !== loading;
44
+ this.#loading = loading;
45
+ if (changed) {
46
+ this.#update();
47
+ }
48
+ }
49
+ get panelDescriptor() {
50
+ return this.#panelDescriptor;
34
51
  }
35
52
  set panelDescriptor(panelDescriptor) {
36
53
  this.#panelDescriptor = panelDescriptor;
37
54
  this.#update();
38
55
  }
39
- constructor(driver) {
56
+ get element() {
57
+ return this.#element;
58
+ }
59
+ get scrollablePaneElement() {
60
+ return this.element.querySelector(scrollablePanelSelector);
61
+ }
62
+ constructor(driver, panelDescriptor) {
40
63
  super();
41
64
  this.#driver = driver;
65
+ this.#panelDescriptor = panelDescriptor;
66
+ this.#loading = panelDescriptor.loadingIcon != null;
42
67
  this.#element = this.#setupElement();
43
68
  this.#element.addEventListener('mouseleave', (e) => {
44
69
  this.emit('blur', e);
45
70
  });
46
71
  }
47
- /**
48
- * @internal
49
- */
50
- activate() {
51
- const element = this.element;
52
- if (!element)
53
- return;
54
- element?.classList.remove(CollapsiblePanelView.elementCss.HOVER);
55
- element.classList.add(CollapsiblePanelView.elementCss.ACTIVE);
56
- }
57
72
  remove() {
58
73
  if (this.#destroyed)
59
74
  return;
60
75
  this.#destroyed = true;
61
- this.element?.remove();
76
+ this.element.remove();
62
77
  this.emit('destroy');
63
78
  }
64
79
  addNavItem(navItemDescriptor) {
65
- const element = this.element;
66
- if (!element)
67
- return;
80
+ this.setLoading(false);
81
+ const { element } = this;
68
82
  const navMenuContainerElement = querySelector(element, NAV_MENU_CONTAINER_ELEMENT_SELECTOR);
69
83
  const appId = `collapsible-panel-${this.#id}`;
70
84
  const navItemDescriptorPropertyStream = kefirCast(Kefir, navItemDescriptor).toProperty();
@@ -73,9 +87,11 @@ export class CollapsiblePanelView extends EventEmitter {
73
87
  return navItemView;
74
88
  }
75
89
  #setupElement() {
76
- const { iconUrl: themedIcons, className, name = '', } = this.#panelDescriptor?.primaryButton ?? {};
90
+ const { panelDescriptor } = this;
91
+ const { loadingIcon } = panelDescriptor;
92
+ const { iconUrl: themedIcons, className, name = '', } = panelDescriptor?.primaryButton ?? {};
77
93
  const element = document.createElement('div');
78
- element.className = ELEMENT_CLASS;
94
+ element.className = cx(ELEMENT_CLASS, this.panelDescriptor.className);
79
95
  const primaryButtonClass = cx(PRIMARY_BUTTON_ELEMENT_CLASS, className);
80
96
  let iconUrl;
81
97
  if (themedIcons) {
@@ -95,7 +111,8 @@ export class CollapsiblePanelView extends EventEmitter {
95
111
  </div>
96
112
  </div>
97
113
  </div>
98
- <div class="at9">
114
+ <div class="${scrollablePanelClass}">
115
+ <div class="${loadingElementClass}">${loadingIcon ? { __html: loadingIcon } : ''}</div>
99
116
  <div class="Ls77Lb aZ6">
100
117
  <div class="pp" style="user-select: none;">
101
118
  <div>
@@ -127,25 +144,21 @@ export class CollapsiblePanelView extends EventEmitter {
127
144
  return element;
128
145
  }
129
146
  #onPrimaryButtonClick = (e) => {
130
- this.#panelDescriptor?.primaryButton?.onClick?.(e);
147
+ this.panelDescriptor.primaryButton?.onClick?.(e);
131
148
  };
132
149
  #update() {
133
150
  const element = this.element;
134
151
  if (!element)
135
152
  return;
136
- const { ACTIVE, COLLAPSED } = CollapsiblePanelView.elementCss;
137
- const isActive = element.classList.contains(ACTIVE);
138
- const isCollapsed = element.classList.contains(COLLAPSED);
139
- element.className = cx(ELEMENT_CLASS, this.#panelDescriptor?.className, {
140
- [ACTIVE]: isActive,
141
- [COLLAPSED]: isCollapsed,
142
- });
153
+ const existingClassNames = Object.values(CollapsiblePanelView.elementCss).filter((className) => element.classList.contains(className));
154
+ element.className = cx(ELEMENT_CLASS, this.panelDescriptor.className, ...existingClassNames);
143
155
  this.#updateName(element);
144
156
  this.#updateIcon(element);
157
+ this.#updateScrollablePanelLoading(element);
145
158
  }
146
159
  #updateIcon(element) {
147
160
  const iconContainerEl = querySelector(element, PRIMARY_BUTTON_ELEMENT_SELECTOR);
148
- const { iconUrl, className } = this.#panelDescriptor?.primaryButton ?? {};
161
+ const { iconUrl, className } = this.panelDescriptor.primaryButton ?? {};
149
162
  if (iconUrl) {
150
163
  const backgroundImage = `url(${GmailElementGetter.isDarkTheme()
151
164
  ? iconUrl.darkTheme
@@ -162,6 +175,19 @@ export class CollapsiblePanelView extends EventEmitter {
162
175
  if (!buttonTextEl) {
163
176
  return;
164
177
  }
165
- buttonTextEl.textContent = this.#panelDescriptor?.primaryButton?.name ?? '';
178
+ buttonTextEl.textContent = this.panelDescriptor.primaryButton?.name ?? '';
179
+ }
180
+ #updateScrollablePanelLoading(element) {
181
+ const scrollablePanelLoadingEl = element.querySelector(loadingElementSelector);
182
+ if (!scrollablePanelLoadingEl) {
183
+ return;
184
+ }
185
+ const panelLoadingClass = `${loadingElementClass}--active`;
186
+ if (this.loading) {
187
+ scrollablePanelLoadingEl.classList.add(panelLoadingClass);
188
+ }
189
+ else {
190
+ scrollablePanelLoadingEl.classList.remove(panelLoadingClass);
191
+ }
166
192
  }
167
193
  }