@genesislcap/grid-pro 15.72.0 → 15.73.0

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.
@@ -0,0 +1,379 @@
1
+ import { css, DOM } from '@microsoft/fast-element';
2
+ import { logger } from '../utils/logger';
3
+ /**
4
+ * Same surface as the vertical actions menu: AG Grid's context-menu variables, with design-system
5
+ * tokens when those variables are not set. The menu is drawn in the top layer, outside the themed
6
+ * grid element, so the values are copied from the cell that was clicked.
7
+ */
8
+ const contextMenuStyles = css `
9
+ :host {
10
+ display: block;
11
+ }
12
+
13
+ :host > .grid-context-menu,
14
+ .grid-context-menu-item > .grid-context-menu {
15
+ display: flex;
16
+ flex-direction: column;
17
+ box-sizing: border-box;
18
+ margin: 0;
19
+ padding: var(--ag-spacing, 4px) 0;
20
+ max-width: 368px;
21
+ min-width: 64px;
22
+ background-color: var(--ag-menu-background-color, var(--fill-color));
23
+ border: var(
24
+ --ag-menu-border,
25
+ calc(var(--stroke-width, 1) * 1px) solid var(--neutral-stroke-rest, transparent)
26
+ );
27
+ border-radius: var(--ag-border-radius, calc(var(--control-corner-radius, 2) * 1px));
28
+ box-shadow: var(--ag-menu-shadow, var(--ag-popup-shadow, 0 2px 8px rgb(0 0 0 / 30%)));
29
+ color: var(--ag-menu-text-color, var(--neutral-foreground-rest));
30
+ font-family: var(--ag-font-family, var(--body-font));
31
+ }
32
+
33
+ .grid-context-menu > .grid-context-menu-item {
34
+ display: flex;
35
+ box-sizing: border-box;
36
+ align-items: center;
37
+ height: calc(var(--ag-icon-size, 16px) + (var(--ag-spacing, 4px) + 2px) * 2);
38
+ margin: 0;
39
+ background: transparent;
40
+ border: none;
41
+ border-radius: 0;
42
+ color: inherit;
43
+ font-family: inherit;
44
+ font-size: var(--ag-font-size, var(--type-ramp-base-font-size));
45
+ font-weight: 500;
46
+ user-select: none;
47
+ }
48
+
49
+ .grid-context-menu > .grid-context-menu-separator {
50
+ margin: var(--ag-spacing, 4px) 0;
51
+ border-top: var(
52
+ --ag-menu-border,
53
+ calc(var(--stroke-width, 1) * 1px) solid var(--neutral-stroke-rest, currentColor)
54
+ );
55
+ opacity: 50%;
56
+ }
57
+
58
+ .grid-context-menu > .grid-context-menu-item:hover,
59
+ .grid-context-menu > .grid-context-menu-item:focus {
60
+ background: var(--ag-row-hover-color, var(--neutral-fill-stealth-hover));
61
+ border: none;
62
+ }
63
+
64
+ .grid-context-menu > .grid-context-menu-item[disabled] {
65
+ background: transparent;
66
+ cursor: default;
67
+ opacity: 50%;
68
+ }
69
+
70
+ .grid-context-menu > .grid-context-menu-item::part(content) {
71
+ flex: 1;
72
+ margin-inline: 0;
73
+ padding-inline: calc(var(--ag-spacing, 4px) * 2) calc(var(--ag-spacing, 4px) * 4);
74
+ line-height: var(--ag-icon-size, 16px);
75
+ }
76
+
77
+ /* A plain item in a menu with checkboxes lines its text up with theirs. */
78
+ .grid-context-menu
79
+ > .grid-context-menu-item.indent-1:not([role='menuitemcheckbox'])::part(content) {
80
+ padding-inline-start: calc(var(--ag-spacing, 4px) * 4 + var(--ag-icon-size, 16px));
81
+ }
82
+
83
+ .grid-context-menu > .grid-context-menu-item::part(end) {
84
+ padding-inline-end: calc(var(--ag-spacing, 4px) * 2);
85
+ }
86
+
87
+ .grid-context-menu > .grid-context-menu-item [slot='end'] {
88
+ white-space: nowrap;
89
+ }
90
+
91
+ /* The menu item's own boxed, accent-filled checkbox and wide chevron, reduced to AG Grid's
92
+ icon column: a bare tick and a small arrow at the grid's icon size. */
93
+ .grid-context-menu > .grid-context-menu-item::part(input-container) {
94
+ margin: 0;
95
+ padding-inline-start: calc(var(--ag-spacing, 4px) * 2);
96
+ }
97
+
98
+ .grid-context-menu > .grid-context-menu-item::part(checkbox),
99
+ .grid-context-menu > .grid-context-menu-item[aria-checked='true']::part(checkbox) {
100
+ width: var(--ag-icon-size, 16px);
101
+ height: var(--ag-icon-size, 16px);
102
+ margin: 0;
103
+ background: transparent;
104
+ border: none;
105
+ }
106
+
107
+ .grid-context-menu > .grid-context-menu-item::part(expand-collapse-glyph-container) {
108
+ margin: 0;
109
+ padding-inline-end: calc(var(--ag-spacing, 4px) * 2);
110
+ }
111
+
112
+ .grid-context-menu > .grid-context-menu-item::part(expand-collapse) {
113
+ width: var(--ag-icon-size, 16px);
114
+ height: var(--ag-icon-size, 16px);
115
+ margin: 0;
116
+ }
117
+
118
+ .grid-context-menu-icon {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ width: var(--ag-icon-size, 16px);
123
+ height: var(--ag-icon-size, 16px);
124
+ font-size: var(--ag-icon-size, 16px);
125
+ color: inherit;
126
+ }
127
+
128
+ /* Font Awesome's chevron fills its box; the check does not, so the arrow is drawn smaller. */
129
+ .grid-context-menu-icon.chevron {
130
+ font-size: calc(var(--ag-icon-size, 16px) * 0.75);
131
+ }
132
+ `;
133
+ /** The design-system icon (`${prefix}-icon`), which draws Font Awesome. */
134
+ function menuIcon(prefix, slot, name, variant) {
135
+ const icon = document.createElement(`${prefix}-icon`);
136
+ icon.slot = slot;
137
+ icon.classList.add('grid-context-menu-icon');
138
+ if (variant) {
139
+ icon.classList.add(variant);
140
+ }
141
+ icon.setAttribute('name', name);
142
+ icon.setAttribute('size', '1x');
143
+ icon.setAttribute('aria-hidden', 'true');
144
+ return icon;
145
+ }
146
+ /**
147
+ * Custom properties the menu reads. AG Grid sets its theme on its wrapper element, which is not an
148
+ * ancestor of the menu, so they are copied from the clicked cell.
149
+ */
150
+ const MENU_THEME_PROPERTIES = [
151
+ '--ag-spacing',
152
+ '--ag-icon-size',
153
+ '--ag-font-size',
154
+ '--ag-font-family',
155
+ '--ag-menu-background-color',
156
+ '--ag-menu-border',
157
+ '--ag-menu-text-color',
158
+ '--ag-menu-shadow',
159
+ '--ag-popup-shadow',
160
+ '--ag-border-radius',
161
+ '--ag-row-hover-color',
162
+ '--fill-color',
163
+ '--neutral-stroke-rest',
164
+ '--neutral-foreground-rest',
165
+ '--neutral-fill-stealth-hover',
166
+ '--body-font',
167
+ '--type-ramp-base-font-size',
168
+ '--stroke-width',
169
+ '--control-corner-radius',
170
+ ];
171
+ function applyMenuTheme(host, source) {
172
+ if (!source) {
173
+ return;
174
+ }
175
+ const computed = getComputedStyle(source);
176
+ for (const name of MENU_THEME_PROPERTIES) {
177
+ const value = computed.getPropertyValue(name).trim();
178
+ if (value) {
179
+ host.style.setProperty(name, value);
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * A CSV Export item, using AG Grid's community CSV export. Assign it to `contextMenuItems` as the
185
+ * whole menu, or spread its result into a menu of your own. Empty when the grid sets
186
+ * `suppressCsvExport`.
187
+ *
188
+ * @public
189
+ */
190
+ export function defaultGridContextMenuItems(event) {
191
+ var _a;
192
+ if ((_a = event.api) === null || _a === void 0 ? void 0 : _a.getGridOption('suppressCsvExport')) {
193
+ return [];
194
+ }
195
+ return [
196
+ {
197
+ name: 'CSV Export',
198
+ action: () => { var _a; return (_a = event.api) === null || _a === void 0 ? void 0 : _a.exportDataAsCsv(); },
199
+ },
200
+ ];
201
+ }
202
+ /** Each prefix is reported once, not on every right-click. */
203
+ const prefixesReportedMissing = new Set();
204
+ function menuComponentsRegistered(prefix) {
205
+ if (customElements.get(`${prefix}-menu`) && customElements.get(`${prefix}-menu-item`)) {
206
+ return true;
207
+ }
208
+ if (!prefixesReportedMissing.has(prefix)) {
209
+ prefixesReportedMissing.add(prefix);
210
+ logger.warn(`Grid Pro: the right-click menu needs <${prefix}-menu> and <${prefix}-menu-item> registered ` +
211
+ 'in the design system. The browser menu is shown instead.');
212
+ }
213
+ return false;
214
+ }
215
+ /**
216
+ * Opens a design-system menu at the pointer and returns a function that removes it, or
217
+ * `undefined` when the design system has not registered the menu components.
218
+ *
219
+ * A pointer press outside, a scroll outside, a window resize, Escape or Tab closes it. Choosing an item runs its
220
+ * action and closes it; an item with a submenu opens the submenu instead.
221
+ *
222
+ * @internal
223
+ */
224
+ export function openGridContextMenu(options) {
225
+ var _a, _b;
226
+ const { prefix, x, y, items, themeSource } = options;
227
+ if (!menuComponentsRegistered(prefix)) {
228
+ return undefined;
229
+ }
230
+ const host = document.createElement('div');
231
+ host.classList.add('grid-context-menu-host');
232
+ host.style.cssText = MENU_HOST_STYLE;
233
+ applyMenuTheme(host, themeSource);
234
+ host.addEventListener('mousedown', (event) => event.stopPropagation());
235
+ const shadow = host.attachShadow({ mode: 'open' });
236
+ contextMenuStyles.addStylesTo(shadow);
237
+ const container = (_a = options.container) !== null && _a !== void 0 ? _a : document.body;
238
+ const containerRoot = container.getRootNode();
239
+ const returnFocusTo = (_b = themeSource === null || themeSource === void 0 ? void 0 : themeSource.closest('.ag-cell')) !== null && _b !== void 0 ? _b : null;
240
+ let closed = false;
241
+ const close = (restoreFocus = false) => {
242
+ if (closed) {
243
+ return;
244
+ }
245
+ closed = true;
246
+ const focusWasInMenu = host.matches(':focus-within');
247
+ host.remove();
248
+ window.removeEventListener('mousedown', onPointerDownOutside);
249
+ window.removeEventListener('resize', onResize);
250
+ window.removeEventListener('scroll', onScroll, true);
251
+ if (containerRoot instanceof ShadowRoot) {
252
+ containerRoot.removeEventListener('scroll', onScroll, true);
253
+ }
254
+ window.removeEventListener('keydown', onKeyDown, true);
255
+ if (restoreFocus && focusWasInMenu) {
256
+ returnFocusTo === null || returnFocusTo === void 0 ? void 0 : returnFocusTo.focus({ preventScroll: true });
257
+ }
258
+ };
259
+ const onPointerDownOutside = () => close();
260
+ const onResize = () => close();
261
+ // Scroll does not cross shadow roots, so a grid in a shadow root is watched there as well.
262
+ const onScroll = (event) => {
263
+ if (!event.composedPath().includes(host)) {
264
+ close();
265
+ }
266
+ };
267
+ const onKeyDown = (event) => {
268
+ if (event.key === 'Escape' || event.key === 'Tab') {
269
+ event.preventDefault();
270
+ event.stopPropagation();
271
+ close(true);
272
+ }
273
+ };
274
+ const menu = document.createElement(`${prefix}-menu`);
275
+ menu.classList.add('grid-context-menu');
276
+ appendMenuItems(prefix, menu, items, () => close(true));
277
+ shadow.append(menu);
278
+ container.appendChild(host);
279
+ showInTopLayer(host);
280
+ placeWithinViewport(host, x, y);
281
+ window.addEventListener('mousedown', onPointerDownOutside);
282
+ window.addEventListener('resize', onResize);
283
+ window.addEventListener('scroll', onScroll, true);
284
+ if (containerRoot instanceof ShadowRoot) {
285
+ containerRoot.addEventListener('scroll', onScroll, true);
286
+ }
287
+ window.addEventListener('keydown', onKeyDown, true);
288
+ // The menu collects its items on the next update; focusing it before then does nothing.
289
+ DOM.queueUpdate(() => {
290
+ if (!closed) {
291
+ menu.focus();
292
+ }
293
+ });
294
+ return () => close();
295
+ }
296
+ /** Overrides the user-agent popover box; `z-index` only matters where popovers are unsupported. */
297
+ const MENU_HOST_STYLE = 'position: fixed; inset: auto; margin: 0; padding: 0; border: 0; overflow: visible; ' +
298
+ 'background: transparent; color: inherit; z-index: 10000;';
299
+ /**
300
+ * The top layer keeps the menu above dialogs and clear of transformed or clipping ancestors.
301
+ * Browsers without popovers fall back to the fixed, high z-index of {@link MENU_HOST_STYLE}.
302
+ */
303
+ function showInTopLayer(host) {
304
+ if (typeof host.showPopover === 'function') {
305
+ host.popover = 'manual';
306
+ host.showPopover();
307
+ }
308
+ }
309
+ /** Opens at the pointer, flipping left or up where the menu would leave the viewport. */
310
+ function placeWithinViewport(host, x, y) {
311
+ host.style.left = `${x}px`;
312
+ host.style.top = `${y}px`;
313
+ const { width, height } = host.getBoundingClientRect();
314
+ if (x + width > window.innerWidth) {
315
+ host.style.left = `${Math.max(0, x - width)}px`;
316
+ }
317
+ if (y + height > window.innerHeight) {
318
+ host.style.top = `${Math.max(0, y - height)}px`;
319
+ }
320
+ }
321
+ function appendMenuItems(prefix, menu, items, close) {
322
+ var _a;
323
+ // Separators only go between items, so an empty spread does not leave a stray line.
324
+ const entries = items.filter((item, index) => item !== 'separator' || (index > 0 && items[index - 1] !== 'separator'));
325
+ while (entries[0] === 'separator')
326
+ entries.shift();
327
+ while (entries.at(-1) === 'separator')
328
+ entries.pop();
329
+ for (const item of entries) {
330
+ if (item === 'separator') {
331
+ const separator = document.createElement('div');
332
+ separator.setAttribute('role', 'separator');
333
+ separator.classList.add('grid-context-menu-separator');
334
+ menu.append(separator);
335
+ continue;
336
+ }
337
+ const menuItem = document.createElement(`${prefix}-menu-item`);
338
+ menuItem.classList.add('grid-context-menu-item');
339
+ menuItem.textContent = item.name;
340
+ if (item.tooltip) {
341
+ menuItem.title = item.tooltip;
342
+ }
343
+ if (item.checked !== undefined) {
344
+ menuItem.setAttribute('role', 'menuitemcheckbox');
345
+ if (item.checked) {
346
+ menuItem.setAttribute('checked', '');
347
+ // Neither design system draws a checkbox glyph of its own.
348
+ menuItem.append(menuIcon(prefix, 'checkbox-indicator', 'check'));
349
+ }
350
+ }
351
+ if (item.shortcut) {
352
+ const shortcut = document.createElement('span');
353
+ shortcut.slot = 'end';
354
+ shortcut.textContent = item.shortcut;
355
+ menuItem.append(shortcut);
356
+ }
357
+ if (item.disabled) {
358
+ menuItem.setAttribute('disabled', '');
359
+ }
360
+ const hasSubMenu = !!((_a = item.subMenu) === null || _a === void 0 ? void 0 : _a.length);
361
+ if (hasSubMenu) {
362
+ const subMenu = document.createElement(`${prefix}-menu`);
363
+ subMenu.slot = 'submenu';
364
+ subMenu.classList.add('grid-context-menu');
365
+ appendMenuItems(prefix, subMenu, item.subMenu, close);
366
+ menuItem.append(menuIcon(prefix, 'expand-collapse-indicator', 'chevron-right', 'chevron'), subMenu);
367
+ }
368
+ // `change` is what the menu item emits for a click, Enter or Space alike.
369
+ menuItem.addEventListener('change', (event) => {
370
+ var _a;
371
+ if (event.target !== menuItem || hasSubMenu || item.disabled) {
372
+ return;
373
+ }
374
+ (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item);
375
+ close();
376
+ });
377
+ menu.append(menuItem);
378
+ }
379
+ }
@@ -0,0 +1 @@
1
+ export { defaultGridContextMenuItems, } from './context-menu';
@@ -13,6 +13,7 @@ import { GridProColumn, resolveVisibleColumnFields } from './column';
13
13
  import { EnumColumnFilter, EnumColumnFloatingFilter, GridProFilterTypes } from './column-filters';
14
14
  import { ColumnSelection, gridProColumnSelectionHostStyles } from './column-selection';
15
15
  import { GRID_PRO_COLUMN_DRAG_BRIDGE_CONTEXT_KEY, gridProColumnDragBridgeModule, } from './column-selection/grid-pro-column-drag-bridge';
16
+ import { openGridContextMenu } from './context-menu/context-menu';
16
17
  import { GridProClientSideDatasource, GridProInfiniteDatasource, GridProServerSideDatasource, } from './datasource';
17
18
  import { baseDatasourceEventNames } from './datasource/base.types';
18
19
  import { errorHandlerDialogStyles } from './datasource/error-handler.dialog';
@@ -159,6 +160,9 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
159
160
  }
160
161
  return undefined;
161
162
  }
163
+ contextMenuItemsChanged() {
164
+ this.syncContextMenuSuppression();
165
+ }
162
166
  enableColumnSelectionChanged() {
163
167
  this.syncColumnSelectionHostLayout();
164
168
  }
@@ -234,6 +238,8 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
234
238
  this.statusPanelState = {};
235
239
  this.warnedUnregisteredStatusBar = false;
236
240
  this.warnedGridOptionsStatusBar = false;
241
+ /** Whether the AG grid instance was created with its own context menu suppressed. */
242
+ this.gridCreatedWithMenuSuppressed = false;
237
243
  /**
238
244
  * Shows a Columns side panel on the trailing edge of the grid.
239
245
  * @public
@@ -247,6 +253,15 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
247
253
  this.registeredGridEvents = new Set();
248
254
  this.gridEventListeners = new Map();
249
255
  this.boundGlobalEventListener = this.globalEventListener.bind(this);
256
+ /**
257
+ * `globalListener` runs on a timeout, which is too late to cancel the browser menu.
258
+ * This one runs inside the right-click event.
259
+ */
260
+ this.boundContextMenuListener = (eventType, event) => {
261
+ if (eventType === 'cellContextMenu') {
262
+ this.showContextMenu(event);
263
+ }
264
+ };
250
265
  this._filterConfig = undefined;
251
266
  this.handleLuminanceChange = (record) => {
252
267
  const luminance = baseLayerLuminance.getValueFor(record.target);
@@ -414,11 +429,13 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
414
429
  this.toggleDatasourceEventListeners(true);
415
430
  }
416
431
  disconnectedCallback() {
417
- var _a;
432
+ var _a, _b;
433
+ (_a = this.closeContextMenu) === null || _a === void 0 ? void 0 : _a.call(this);
434
+ this.closeContextMenu = undefined;
418
435
  this.disposed = true;
419
436
  this.debouncedSaveColumnState.cancel();
420
437
  this.debouncedSaveFilterModel.cancel();
421
- const luminanceTarget = (_a = this.provider) !== null && _a !== void 0 ? _a : this;
438
+ const luminanceTarget = (_b = this.provider) !== null && _b !== void 0 ? _b : this;
422
439
  baseLayerLuminance.unsubscribe(this.luminanceObserver, luminanceTarget);
423
440
  if (this.luminanceSubscription) {
424
441
  this.luminanceSubscription.unsubscribe();
@@ -1171,6 +1188,7 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
1171
1188
  get gridParams() {
1172
1189
  return {
1173
1190
  globalListener: this.boundGlobalEventListener,
1191
+ globalSyncListener: this.boundContextMenuListener,
1174
1192
  modules: [gridProColumnDragBridgeModule, ColumnMoveModule, DragAndDropModule],
1175
1193
  };
1176
1194
  }
@@ -1238,6 +1256,9 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
1238
1256
  const derivedOptions = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.gridOptionsConfig), { defaultColDef: Object.assign({ enableCellChangeFlash: this.enableCellFlashing, filter: true, resizable: true, sortable: true }, defaultColDef), components: this.combineAllGridComponents(components), headerHeight: this.headerHeight, rowHeight: this.rowHeight, animateRows: false, suppressDragLeaveHidesColumns: true }), this.eventsAndCallbacks), { onGridReady: (event) => __awaiter(this, void 0, void 0, function* () {
1239
1257
  var _a, _b, _c;
1240
1258
  this.gridApi = event.api;
1259
+ if (!!this.contextMenuItems !== this.gridCreatedWithMenuSuppressed) {
1260
+ this.syncContextMenuSuppression();
1261
+ }
1241
1262
  this.applyStatusBarMode();
1242
1263
  if (this.gridEventsQueue.length) {
1243
1264
  this.gridEventsQueue.forEach((type) => {
@@ -1374,7 +1395,9 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
1374
1395
  this.gridSlot.removeChild(this.gridSlot.firstChild);
1375
1396
  }
1376
1397
  }
1377
- this.agGrid = createGrid(this.gridSlot, this.agGridOptions, this.gridParams);
1398
+ this.gridCreatedWithMenuSuppressed = !!this.contextMenuItems;
1399
+ this.agGrid = createGrid(this.gridSlot, this.gridCreatedWithMenuSuppressed
1400
+ ? Object.assign(Object.assign({}, this.agGridOptions), { suppressContextMenu: true }) : this.agGridOptions, this.gridParams);
1378
1401
  this.initialised = true;
1379
1402
  });
1380
1403
  }
@@ -1508,9 +1531,61 @@ export class GridPro extends LifecycleMixin(FoundationElement) {
1508
1531
  if (this.initialised) {
1509
1532
  // for subsequent (post gridOptions) changes - use setGridOption API for v35
1510
1533
  (_a = this.gridApi) === null || _a === void 0 ? void 0 : _a.setGridOption(gridPropertyName, newValue);
1534
+ if (gridPropertyName === 'suppressContextMenu' && this.contextMenuItems) {
1535
+ this.syncContextMenuSuppression();
1536
+ }
1511
1537
  }
1512
1538
  }
1513
1539
  }
1540
+ /**
1541
+ * AG Grid draws its own menu when `ContextMenu` is registered, so it is suppressed while
1542
+ * `contextMenuItems` is set and the app's own `suppressContextMenu` restored when it is cleared.
1543
+ * The suppression goes to AG Grid only, never into `agGridOptions`: that object is what the
1544
+ * `gridOptions` getter returns, and an app re-applying it would otherwise adopt the setting.
1545
+ * @internal
1546
+ */
1547
+ syncContextMenuSuppression() {
1548
+ var _a, _b, _c, _d;
1549
+ const appSetting = (_c = (_a = this.agAttributes.suppressContextMenu) !== null && _a !== void 0 ? _a : (_b = this.agGridOptions) === null || _b === void 0 ? void 0 : _b.suppressContextMenu) !== null && _c !== void 0 ? _c : false;
1550
+ (_d = this.gridApi) === null || _d === void 0 ? void 0 : _d.setGridOption('suppressContextMenu', this.contextMenuItems ? true : appSetting);
1551
+ }
1552
+ /**
1553
+ * Opens the grid's right-click menu for a cell. Called from the synchronous grid listener so
1554
+ * `preventDefault` still cancels the browser menu.
1555
+ * @internal
1556
+ */
1557
+ showContextMenu(event) {
1558
+ var _a, _b, _c, _d, _e;
1559
+ const items = (_a = this.contextMenuItems) === null || _a === void 0 ? void 0 : _a.call(this, event);
1560
+ if (!(items === null || items === void 0 ? void 0 : items.length)) {
1561
+ return;
1562
+ }
1563
+ const mouse = event.event instanceof MouseEvent ? event.event : null;
1564
+ const pathTarget = mouse === null || mouse === void 0 ? void 0 : mouse.composedPath()[0];
1565
+ const target = pathTarget instanceof Element ? pathTarget : null;
1566
+ let x = (_b = mouse === null || mouse === void 0 ? void 0 : mouse.clientX) !== null && _b !== void 0 ? _b : 0;
1567
+ let y = (_c = mouse === null || mouse === void 0 ? void 0 : mouse.clientY) !== null && _c !== void 0 ? _c : 0;
1568
+ // A menu opened from the keyboard can arrive without a pointer position.
1569
+ const cell = target === null || target === void 0 ? void 0 : target.closest('.ag-cell');
1570
+ if (x === 0 && y === 0 && cell) {
1571
+ const rect = cell.getBoundingClientRect();
1572
+ x = rect.left;
1573
+ y = rect.bottom;
1574
+ }
1575
+ (_d = this.closeContextMenu) === null || _d === void 0 ? void 0 : _d.call(this);
1576
+ const [prefix = 'foundation'] = this.tagName.toLowerCase().split('-');
1577
+ this.closeContextMenu = openGridContextMenu({
1578
+ prefix,
1579
+ x,
1580
+ y,
1581
+ items,
1582
+ container: (_e = this.shadowRoot) !== null && _e !== void 0 ? _e : undefined,
1583
+ themeSource: target,
1584
+ });
1585
+ if (this.closeContextMenu && (mouse === null || mouse === void 0 ? void 0 : mouse.cancelable)) {
1586
+ mouse.preventDefault();
1587
+ }
1588
+ }
1514
1589
  globalEventListener(eventType, event) {
1515
1590
  const eventLowerCase = eventType.toLowerCase();
1516
1591
  const browserEvent = new Event(eventLowerCase);
@@ -1597,6 +1672,9 @@ __decorate([
1597
1672
  __decorate([
1598
1673
  observable
1599
1674
  ], GridPro.prototype, "statusBarMode", void 0);
1675
+ __decorate([
1676
+ observable
1677
+ ], GridPro.prototype, "contextMenuItems", void 0);
1600
1678
  __decorate([
1601
1679
  attr({ mode: 'boolean', attribute: 'enable-column-selection' })
1602
1680
  ], GridPro.prototype, "enableColumnSelection", void 0);
package/dist/esm/index.js CHANGED
@@ -4,6 +4,7 @@ export * from './cell-editors';
4
4
  export * from './column';
5
5
  export * from './column-selection';
6
6
  export * from './column-filters';
7
+ export * from './context-menu';
7
8
  export * from './datasource';
8
9
  export * from './external';
9
10
  export * from './grid-components';