@genesislcap/foundation-ui 15.75.0 → 15.76.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.
@@ -1,4 +1,6 @@
1
+ import type { IconStyle } from '@fortawesome/fontawesome-svg-core';
1
2
  import { FoundationElement } from '@microsoft/fast-foundation';
3
+ import type { FaSize } from '../icon';
2
4
  /**
3
5
  * The Action Menu Item component.
4
6
  * @remarks Used within the Action Menu component.
@@ -9,18 +11,44 @@ export type ActionMenuItem = {
9
11
  callback: (rowData: any) => void | any;
10
12
  isDisabled?: (rowData: any) => boolean;
11
13
  color?: string;
14
+ icon?: {
15
+ variant?: IconStyle;
16
+ name: string;
17
+ size?: FaSize;
18
+ };
12
19
  };
20
+ /**
21
+ * A function of the row data returning the actions for that row (for row-scoped
22
+ * eligibility/permission filtering).
23
+ * @public
24
+ */
25
+ export type ActionMenuItemsResolver = (rowData: any) => ActionMenuItem[];
26
+ /**
27
+ * The actions a grid column's Actions Menu shows: a fixed array, or a resolver per row.
28
+ * @public
29
+ */
30
+ export type ActionMenuItems = ActionMenuItem[] | ActionMenuItemsResolver;
13
31
  /**
14
32
  * @tagname %%prefix%%-actions-menu
15
33
  */
16
34
  export declare class ActionsMenu extends FoundationElement {
17
35
  actions: ActionMenuItem[];
36
+ /**
37
+ * Resolves the actions per row instead of `actions`, from the row data in `definition`.
38
+ * @remarks Set from a function passed as the cell params' `actions`.
39
+ */
40
+ actionsResolver?: ActionMenuItemsResolver;
18
41
  isVertical: boolean;
19
42
  addDefaultActions: boolean;
20
43
  autoCloseOnAction: boolean;
21
44
  classNames: string;
22
45
  definition: any;
23
46
  name: string;
47
+ /**
48
+ * When set, the opener renders this icon instead of the `name` text (e.g. `ellipsis`
49
+ * for an icon-only "kebab" opener).
50
+ */
51
+ iconName: string;
24
52
  open: boolean;
25
53
  buttonAppearance: string;
26
54
  hideDisabled: boolean;
@@ -28,12 +56,30 @@ export declare class ActionsMenu extends FoundationElement {
28
56
  singleOption: ActionMenuItem;
29
57
  actionsMenu: HTMLElement;
30
58
  actionsOpener: HTMLElement;
59
+ private positionTracker?;
60
+ /** Set when the keyboard opens the menu, so focus moves into it as screen readers announce. */
61
+ private focusMenuOnOpen;
31
62
  disconnectedCallback(): void;
32
63
  private updateMenuPosition;
64
+ private startTrackingPosition;
65
+ private stopTrackingPosition;
33
66
  protected definitionChanged(oldValue: any, newValue: any): void;
34
67
  protected openChanged(): void;
35
68
  toggleActionsMenuVisibility(): void;
69
+ /**
70
+ * Syncs `open` when the native `popover="auto"` light-dismiss (outside click / Escape) closes
71
+ * the vertical menu without going through `toggleActionsMenuVisibility()`.
72
+ */
73
+ onPopoverToggle(event: Event): void;
74
+ /** Opens the icon-only opener from the keyboard, as a native button would. */
75
+ onIconOpenerKeydown(event: KeyboardEvent): boolean;
36
76
  onActionMenuItemClick(callback: (rowData: any) => void | any): void;
77
+ /** Resolves an action's `isDisabled` for the current row. */
78
+ isActionDisabled(action: ActionMenuItem): boolean;
79
+ /** The actions for the current row: from `actionsResolver` when set, else `actions`. */
80
+ get resolvedActions(): ActionMenuItem[];
81
+ /** Whether the row has an action to show: an enabled one, or any when disabled ones show. */
82
+ get hasVisibleActions(): boolean;
37
83
  get allActions(): ActionMenuItem[];
38
84
  get rowData(): any;
39
85
  getLabel(name: string | ((rowData: any) => string)): string;
@@ -1 +1 @@
1
- {"version":3,"file":"actions-menu.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAS/D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAC1C,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;IACvC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,qBAAa,WAAY,SAAQ,iBAAiB;IACpC,OAAO,EAAE,cAAc,EAAE,CAAC;IACe,UAAU,EAAE,OAAO,CAAS;IACpB,iBAAiB,EAAE,OAAO,CAAQ;IACjC,iBAAiB,EAAE,OAAO,CAAQ;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,GAAG,CAAC;IACtB,IAAI,SAAa;IACuB,IAAI,EAAE,OAAO,CAAS;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACwB,YAAY,EAAE,OAAO,CAAS;IACpB,oBAAoB,EAAE,OAAO,CACtF;IACI,YAAY,EAAE,cAAc,CAAC;IAEzC,WAAW,EAAG,WAAW,CAAC;IAC1B,aAAa,EAAG,WAAW,CAAC;IAE5B,oBAAoB,IAAI,IAAI;IAO5B,OAAO,CAAC,kBAAkB;IAQ1B,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG;IAyBxD,SAAS,CAAC,WAAW;IAWrB,2BAA2B;IAI3B,qBAAqB,CAAC,QAAQ,EAAE,CAAC,OAAO,KAAA,KAAK,IAAI,GAAG,GAAG;IAKvD,IAAI,UAAU,IAAI,cAAc,EAAE,CASjC;IAED,IAAI,OAAO,QAKV;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC;IAQlD,WAAW,CAAC,MAAM,EAAE,MAAM;CAsB3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,2TAIhC,CAAC"}
1
+ {"version":3,"file":"actions-menu.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAStC;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAC1C,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;IACvC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,SAAS,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,cAAc,EAAE,CAAC;AAEzE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,GAAG,uBAAuB,CAAC;AAEzE;;GAEG;AACH,qBAAa,WAAY,SAAQ,iBAAiB;IACpC,OAAO,EAAE,cAAc,EAAE,CAAC;IACtC;;;OAGG;IACS,eAAe,CAAC,EAAE,uBAAuB,CAAC;IACD,UAAU,EAAE,OAAO,CAAS;IACpB,iBAAiB,EAAE,OAAO,CAAQ;IACjC,iBAAiB,EAAE,OAAO,CAAQ;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,GAAG,CAAC;IACtB,IAAI,SAAa;IACvB;;;OAGG;IAC+B,QAAQ,EAAE,MAAM,CAAC;IACL,IAAI,EAAE,OAAO,CAAS;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACwB,YAAY,EAAE,OAAO,CAAS;IACpB,oBAAoB,EAAE,OAAO,CACtF;IACI,YAAY,EAAE,cAAc,CAAC;IAEzC,WAAW,EAAG,WAAW,CAAC;IAC1B,aAAa,EAAG,WAAW,CAAC;IAE5B,OAAO,CAAC,eAAe,CAAC,CAAiC;IACzD,+FAA+F;IAC/F,OAAO,CAAC,eAAe,CAAS;IAEhC,oBAAoB,IAAI,IAAI;IAQ5B,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,oBAAoB;IAK5B,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG;IA0DxD,SAAS,CAAC,WAAW;IAyBrB,2BAA2B;IAI3B;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE,KAAK;IAO5B,8EAA8E;IAC9E,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO;IAOlD,qBAAqB,CAAC,QAAQ,EAAE,CAAC,OAAO,KAAA,KAAK,IAAI,GAAG,GAAG;IAKvD,6DAA6D;IAC7D,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO;IAIjD,wFAAwF;IACxF,IAAI,eAAe,IAAI,cAAc,EAAE,CAEtC;IAED,6FAA6F;IAC7F,IAAI,iBAAiB,IAAI,OAAO,CAK/B;IAED,IAAI,UAAU,IAAI,cAAc,EAAE,CAWjC;IAED,IAAI,OAAO,QAKV;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC;IAQlD,WAAW,CAAC,MAAM,EAAE,MAAM;CAsB3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,2TAIhC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"actions-menu.styles.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAEpC,eAAO,MAAM,qBAAqB,GAChC,SAAS,wBAAwB,EACjC,YAAY,2BAA2B,KACtC,aA4FF,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,SAAS,wBAAwB,EACjC,YAAY,2BAA2B,KACtC,aAEF,CAAC"}
1
+ {"version":3,"file":"actions-menu.styles.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAGpC,eAAO,MAAM,qBAAqB,GAChC,SAAS,wBAAwB,EACjC,YAAY,2BAA2B,KACtC,aA4HF,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,SAAS,wBAAwB,EACjC,YAAY,2BAA2B,KACtC,aAEF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import type { ViewTemplate } from '@microsoft/fast-element';
2
2
  import type { ActionsMenu } from './actions-menu';
3
- export declare const menuTemplate: (prefix: string) => ViewTemplate<ActionsMenu, any>;
4
- export declare const singleOptionTemplate: (prefix: string) => ViewTemplate<ActionsMenu, any>;
3
+ export declare const menuTemplate: (prefix: string) => ViewTemplate<ActionsMenu>;
4
+ export declare const singleOptionTemplate: (prefix: string) => ViewTemplate<ActionsMenu>;
5
5
  export declare const foundationActionsMenuTemplate: ViewTemplate<ActionsMenu>;
6
6
  //# sourceMappingURL=actions-menu.template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"actions-menu.template.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,mCAoD1C,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,mCAclD,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,YAAY,CAAC,WAAW,CAOnE,CAAC"}
1
+ {"version":3,"file":"actions-menu.template.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/actions-menu.template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AA6DlD,eAAO,MAAM,YAAY,WAhDP,MAAM,KAAG,YAAY,CAAC,WAAW,CAmHlD,CAAC;AAEF,eAAO,MAAM,oBAAoB,WArHf,MAAM,KAAG,YAAY,CAAC,WAAW,CAqIlD,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,YAAY,CAAC,WAAW,CAOnE,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ElementStyles } from '@microsoft/fast-element';
2
+ /**
3
+ * Styles a menu to match AG Grid's own menu (ag-menu / ag-menu-list / ag-menu-option), so a menu
4
+ * drawn over a grid looks like the grid's. AG Grid's `--ag-*` variables apply where they are set;
5
+ * elsewhere the design system's tokens do, with fallbacks for when neither is in scope.
6
+ *
7
+ * @remarks Shared by the vertical actions menu and grid-pro's right-click menu. Each adds its own
8
+ * layout (display, sizing) around these rules.
9
+ *
10
+ * @param menuSelector - Selects the menu surface. May be a selector list.
11
+ * @param itemSelector - Selects a menu item. Must be a single selector, as states are appended to it.
12
+ * @returns The menu surface and item styles.
13
+ * @public
14
+ */
15
+ export declare const agMenuStyles: (menuSelector: string, itemSelector: string) => ElementStyles;
16
+ //# sourceMappingURL=ag-menu.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ag-menu.styles.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/ag-menu.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,cAAc,MAAM,EAAE,cAAc,MAAM,KAAG,aA6CzE,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from './actions-menu.styles';
2
2
  export * from './actions-menu.template';
3
3
  export * from './actions-menu';
4
+ export * from './ag-menu.styles';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/actions-menu/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const mapType: (TYPE: string) => "boolean" | "number" | "date" | "text" | "enum";
1
+ export declare const mapType: (TYPE: string) => "boolean" | "number" | "text" | "date" | "enum";
2
2
  //# sourceMappingURL=filter-bar.utils.d.ts.map
@@ -133,6 +133,15 @@ interface HTMLWCProps extends React.AriaAttributes {
133
133
  onInput?(e: Event): void;
134
134
  }
135
135
 
136
+ export declare const Accordion: React.ForwardRefExoticComponent<
137
+ React.PropsWithChildren<
138
+ Omit<PublicOf<AccordionWC>, 'children' | 'style'> &
139
+ HTMLWCProps & {
140
+ }
141
+ > & React.RefAttributes<AccordionWC>
142
+ >;
143
+ export type AccordionRef = AccordionWC;
144
+
136
145
  export declare const AccordionItem: React.ForwardRefExoticComponent<
137
146
  React.PropsWithChildren<
138
147
  Omit<PublicOf<AccordionItemWC>, 'children' | 'style'> &
@@ -162,15 +171,6 @@ export declare const AiCriteriaSearch: React.ForwardRefExoticComponent<
162
171
  >;
163
172
  export type AiCriteriaSearchRef = AiCriteriaSearchWC;
164
173
 
165
- export declare const Accordion: React.ForwardRefExoticComponent<
166
- React.PropsWithChildren<
167
- Omit<PublicOf<AccordionWC>, 'children' | 'style'> &
168
- HTMLWCProps & {
169
- }
170
- > & React.RefAttributes<AccordionWC>
171
- >;
172
- export type AccordionRef = AccordionWC;
173
-
174
174
  export declare const AiIndicator: React.ForwardRefExoticComponent<
175
175
  React.PropsWithChildren<
176
176
  Omit<PublicOf<AiIndicatorWC>, 'children' | 'style'> &
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Keeps a Popover-API element anchored to its trigger, and reports back when the trigger has been
3
+ * removed from the DOM (e.g. AG Grid row virtualization recycling the row that owns it).
4
+ * @remarks Checks the trigger once per animation frame while started, and repositions only when
5
+ * the trigger or the viewport moved. Events alone miss moves: native `scroll` events don't cross
6
+ * shadow-DOM boundaries, and a grid re-sort moves a row by `transform`, which fires nothing on the
7
+ * rows that didn't change. Callers start it when the popover opens and stop it when it closes.
8
+ * @internal
9
+ */
10
+ export declare class AnchoredPopoverPositionTracker {
11
+ private readonly anchor;
12
+ private readonly onReposition;
13
+ private readonly onAnchorDetached;
14
+ private view?;
15
+ private frame?;
16
+ private lastPosition?;
17
+ private readonly check;
18
+ constructor(anchor: HTMLElement, onReposition: () => void, onAnchorDetached: () => void);
19
+ start(): void;
20
+ stop(): void;
21
+ }
22
+ //# sourceMappingURL=popover-position.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popover-position.d.ts","sourceRoot":"","sources":["../../../src/utils/popover-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,qBAAa,8BAA8B;IAwBvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAzBnC,OAAO,CAAC,IAAI,CAAC,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAS;IAE9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAgBpB;gBAGiB,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,MAAM,IAAI,EACxB,gBAAgB,EAAE,MAAM,IAAI;IAG/C,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;CAQb"}
@@ -1,7 +1,8 @@
1
1
  import { __decorate } from "tslib";
2
- import { attr, observable } from '@microsoft/fast-element';
2
+ import { attr, DOM, observable } from '@microsoft/fast-element';
3
3
  import { FoundationElement } from '@microsoft/fast-foundation';
4
- import { classNames } from '@microsoft/fast-web-utilities';
4
+ import { classNames, limit } from '@microsoft/fast-web-utilities';
5
+ import { AnchoredPopoverPositionTracker } from '../utils/popover-position';
5
6
  import { foundationActionsMenuStyles as styles } from './actions-menu.styles';
6
7
  import { singleOptionTemplate, menuTemplate, foundationActionsMenuTemplate as template, } from './actions-menu.template';
7
8
  /**
@@ -17,24 +18,60 @@ export class ActionsMenu extends FoundationElement {
17
18
  this.open = false;
18
19
  this.hideDisabled = false;
19
20
  this.hideMenuSingleOption = false;
21
+ /** Set when the keyboard opens the menu, so focus moves into it as screen readers announce. */
22
+ this.focusMenuOnOpen = false;
20
23
  }
21
24
  disconnectedCallback() {
22
25
  var _a;
26
+ this.stopTrackingPosition();
23
27
  if ((_a = this.actionsMenu) === null || _a === void 0 ? void 0 : _a.matches(':popover-open')) {
24
28
  this.actionsMenu.hidePopover();
25
29
  }
26
30
  super.disconnectedCallback();
27
31
  }
28
32
  updateMenuPosition() {
29
- const rect = this.actionsOpener.getBoundingClientRect();
30
- this.actionsMenu.style.position = 'fixed';
31
- this.actionsMenu.style.top = `${rect.top}px`;
32
- this.actionsMenu.style.left = 'auto';
33
- this.actionsMenu.style.right = `${window.innerWidth - rect.right}px`;
33
+ const opener = this.actionsOpener.getBoundingClientRect();
34
+ const menu = this.actionsMenu;
35
+ menu.style.position = 'fixed';
36
+ menu.style.left = 'auto';
37
+ if (!this.isVertical) {
38
+ // The horizontal menu overlays its opener.
39
+ menu.style.top = `${opener.top}px`;
40
+ menu.style.right = `${window.innerWidth - opener.right}px`;
41
+ return;
42
+ }
43
+ // The vertical menu drops down below its opener, flips above when only that side fits, and is
44
+ // clamped to the viewport (scrolling when taller than it) so it never renders off-screen.
45
+ const { innerHeight, innerWidth } = window;
46
+ menu.style.maxHeight = `${innerHeight}px`;
47
+ const { width, height } = menu.getBoundingClientRect();
48
+ const fitsBelow = height <= innerHeight - opener.bottom;
49
+ const fitsAbove = height <= opener.top;
50
+ const top = fitsBelow || !fitsAbove ? opener.bottom : opener.top - height;
51
+ menu.style.top = `${limit(0, Math.max(innerHeight - height, 0), top)}px`;
52
+ menu.style.right = `${limit(0, Math.max(innerWidth - width, 0), innerWidth - opener.right)}px`;
53
+ }
54
+ startTrackingPosition() {
55
+ this.positionTracker = new AnchoredPopoverPositionTracker(this.actionsOpener, () => this.updateMenuPosition(), () => (this.open = false));
56
+ this.positionTracker.start();
57
+ }
58
+ stopTrackingPosition() {
59
+ var _a;
60
+ (_a = this.positionTracker) === null || _a === void 0 ? void 0 : _a.stop();
61
+ this.positionTracker = undefined;
34
62
  }
35
63
  definitionChanged(oldValue, newValue) {
36
64
  var _a;
37
- this.actions = (_a = newValue === null || newValue === void 0 ? void 0 : newValue.actions) !== null && _a !== void 0 ? _a : [];
65
+ if (!newValue)
66
+ return;
67
+ if (typeof newValue.actions === 'function') {
68
+ this.actionsResolver = newValue.actions;
69
+ this.actions = [];
70
+ }
71
+ else {
72
+ this.actionsResolver = undefined;
73
+ this.actions = (_a = newValue.actions) !== null && _a !== void 0 ? _a : [];
74
+ }
38
75
  delete this.definition.actions;
39
76
  if (newValue.buttonText) {
40
77
  this.name = newValue.buttonText;
@@ -44,6 +81,18 @@ export class ActionsMenu extends FoundationElement {
44
81
  this.isVertical = newValue.isVertical;
45
82
  delete this.definition.isVertical;
46
83
  }
84
+ if (newValue.iconName) {
85
+ this.iconName = newValue.iconName;
86
+ delete this.definition.iconName;
87
+ }
88
+ if (newValue.addDefaultActions !== undefined) {
89
+ this.addDefaultActions = newValue.addDefaultActions;
90
+ delete this.definition.addDefaultActions;
91
+ }
92
+ if (newValue.hideDisabled !== undefined) {
93
+ this.hideDisabled = newValue.hideDisabled;
94
+ delete this.definition.hideDisabled;
95
+ }
47
96
  if (newValue.buttonAppearance) {
48
97
  this.buttonAppearance = newValue.buttonAppearance;
49
98
  delete this.definition.buttonAppearance;
@@ -52,35 +101,98 @@ export class ActionsMenu extends FoundationElement {
52
101
  this.hideMenuSingleOption = newValue.hideMenuSingleOption;
53
102
  delete this.definition.hideMenuSingleOption;
54
103
  }
104
+ // A grid refresh hands over the latest row data: close a menu the row no longer has actions
105
+ // for (its opener is about to hide), and follow a row that moved.
106
+ if (this.open) {
107
+ if (this.isVertical && !this.hasVisibleActions) {
108
+ this.open = false;
109
+ }
110
+ else {
111
+ DOM.queueUpdate(() => this.open && this.updateMenuPosition());
112
+ }
113
+ }
55
114
  }
56
115
  openChanged() {
57
- var _a;
116
+ var _a, _b, _c, _d;
58
117
  this.classNames = classNames(['visible', this.open], ['hidden', !this.open]);
59
118
  if (this.open) {
60
119
  this.updateMenuPosition();
61
120
  this.actionsMenu.showPopover();
121
+ this.startTrackingPosition();
122
+ // The menu only takes its size once the class update above renders it.
123
+ DOM.queueUpdate(() => {
124
+ if (!this.open)
125
+ return;
126
+ this.updateMenuPosition();
127
+ if (this.focusMenuOnOpen)
128
+ this.actionsMenu.focus();
129
+ this.focusMenuOnOpen = false;
130
+ });
62
131
  }
63
- else if ((_a = this.actionsMenu) === null || _a === void 0 ? void 0 : _a.matches(':popover-open')) {
64
- this.actionsMenu.hidePopover();
132
+ else {
133
+ this.focusMenuOnOpen = false;
134
+ this.stopTrackingPosition();
135
+ const focusInMenu = (_a = this.actionsMenu) === null || _a === void 0 ? void 0 : _a.contains((_b = this.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement);
136
+ if ((_c = this.actionsMenu) === null || _c === void 0 ? void 0 : _c.matches(':popover-open')) {
137
+ this.actionsMenu.hidePopover();
138
+ }
139
+ if (focusInMenu)
140
+ (_d = this.actionsOpener) === null || _d === void 0 ? void 0 : _d.focus();
65
141
  }
66
142
  }
67
143
  toggleActionsMenuVisibility() {
68
144
  this.open = !this.open;
69
145
  }
146
+ /**
147
+ * Syncs `open` when the native `popover="auto"` light-dismiss (outside click / Escape) closes
148
+ * the vertical menu without going through `toggleActionsMenuVisibility()`.
149
+ */
150
+ onPopoverToggle(event) {
151
+ const newState = event.newState;
152
+ if (newState) {
153
+ this.open = newState === 'open';
154
+ }
155
+ }
156
+ /** Opens the icon-only opener from the keyboard, as a native button would. */
157
+ onIconOpenerKeydown(event) {
158
+ if (event.key !== 'Enter' && event.key !== ' ')
159
+ return true;
160
+ this.focusMenuOnOpen = !this.open;
161
+ this.toggleActionsMenuVisibility();
162
+ return false;
163
+ }
70
164
  onActionMenuItemClick(callback) {
71
165
  if (this.autoCloseOnAction)
72
166
  this.open = false;
73
167
  callback(this.rowData);
74
168
  }
75
- get allActions() {
169
+ /** Resolves an action's `isDisabled` for the current row. */
170
+ isActionDisabled(action) {
171
+ var _a;
172
+ return !!((_a = action.isDisabled) === null || _a === void 0 ? void 0 : _a.call(action, this.rowData));
173
+ }
174
+ /** The actions for the current row: from `actionsResolver` when set, else `actions`. */
175
+ get resolvedActions() {
76
176
  var _a;
177
+ return (_a = (this.actionsResolver ? this.actionsResolver(this.rowData) : this.actions)) !== null && _a !== void 0 ? _a : [];
178
+ }
179
+ /** Whether the row has an action to show: an enabled one, or any when disabled ones show. */
180
+ get hasVisibleActions() {
181
+ const actions = this.resolvedActions;
182
+ return this.hideDisabled
183
+ ? actions.some((action) => !this.isActionDisabled(action))
184
+ : actions.length > 0;
185
+ }
186
+ get allActions() {
77
187
  const defaultActions = [
78
188
  {
79
189
  name: '✖',
80
190
  callback: () => (this.open = false),
81
191
  },
82
192
  ];
83
- return this.addDefaultActions ? [...((_a = this.actions) !== null && _a !== void 0 ? _a : []), ...defaultActions] : this.actions;
193
+ return this.addDefaultActions
194
+ ? [...this.resolvedActions, ...defaultActions]
195
+ : this.resolvedActions;
84
196
  }
85
197
  get rowData() {
86
198
  if (!this.definition)
@@ -95,15 +207,14 @@ export class ActionsMenu extends FoundationElement {
95
207
  return name;
96
208
  }
97
209
  getTemplate(prefix) {
210
+ // The vertical menu hides its opener entirely rather than open onto nothing actionable.
211
+ if (this.isVertical && !this.hasVisibleActions) {
212
+ return;
213
+ }
214
+ const enabledRows = this.resolvedActions.filter((action) => !this.isActionDisabled(action));
98
215
  if (!this.hideMenuSingleOption) {
99
216
  return menuTemplate(prefix);
100
217
  }
101
- const enabledRows = this.actions.filter((action) => {
102
- if (!action.isDisabled) {
103
- return true;
104
- }
105
- return !action.isDisabled(this.rowData);
106
- });
107
218
  this.singleOption = null;
108
219
  if (enabledRows.length === 0) {
109
220
  return;
@@ -118,6 +229,9 @@ export class ActionsMenu extends FoundationElement {
118
229
  __decorate([
119
230
  observable
120
231
  ], ActionsMenu.prototype, "actions", void 0);
232
+ __decorate([
233
+ observable
234
+ ], ActionsMenu.prototype, "actionsResolver", void 0);
121
235
  __decorate([
122
236
  attr({ attribute: 'is-vertical', mode: 'boolean' })
123
237
  ], ActionsMenu.prototype, "isVertical", void 0);
@@ -136,6 +250,9 @@ __decorate([
136
250
  __decorate([
137
251
  attr
138
252
  ], ActionsMenu.prototype, "name", void 0);
253
+ __decorate([
254
+ attr({ attribute: 'icon-name' })
255
+ ], ActionsMenu.prototype, "iconName", void 0);
139
256
  __decorate([
140
257
  attr({ attribute: 'open', mode: 'boolean' })
141
258
  ], ActionsMenu.prototype, "open", void 0);
@@ -1,4 +1,5 @@
1
1
  import { css } from '@microsoft/fast-element';
2
+ import { agMenuStyles } from './ag-menu.styles';
2
3
  export const actionsMenuBaseStyles = (context, definition) => css `
3
4
  :host {
4
5
  --actions-opener-height-multiplier: 0.7px;
@@ -21,11 +22,9 @@ export const actionsMenuBaseStyles = (context, definition) => css `
21
22
  margin: 0;
22
23
  }
23
24
 
24
- .actions-vertical .actions-menu {
25
- padding: calc(var(--design-unit) * 1px);
26
- max-height: 175px;
27
- z-index: 100;
28
- }
25
+ /* The vertical menu follows AG Grid's menu, so it matches the grid it usually sits in.
26
+ Its box-sizing lets updateMenuPosition cap max-height at the viewport, border included. */
27
+ ${agMenuStyles('.actions-vertical .actions-menu', '.actions-vertical .menu-item')}
29
28
 
30
29
  .actions-menu:not(.hidden):hover {
31
30
  display: inline-flex;
@@ -48,6 +47,31 @@ export const actionsMenuBaseStyles = (context, definition) => css `
48
47
  );
49
48
  }
50
49
 
50
+ :host([icon-name]) .actions-container {
51
+ justify-content: center;
52
+ }
53
+
54
+ /* The icon-only opener mirrors grid-pro's icon renderer: a bare icon with a hover chip. */
55
+ .actions-opener.icon-only {
56
+ align-items: center;
57
+ justify-content: center;
58
+ height: auto;
59
+ padding: 5px;
60
+ border-radius: 5px;
61
+ line-height: 1;
62
+ color: inherit;
63
+ cursor: pointer;
64
+ }
65
+
66
+ .actions-opener.icon-only:hover,
67
+ .actions-opener.icon-only.opened {
68
+ background-color: var(--neutral-stroke-hover);
69
+ }
70
+
71
+ .actions-opener.icon-only:focus-visible {
72
+ outline: calc(var(--focus-stroke-width) * 1px) solid var(--accent-fill-rest);
73
+ }
74
+
51
75
  svg {
52
76
  width: 100%;
53
77
  height: 100%;
@@ -80,10 +104,19 @@ export const actionsMenuBaseStyles = (context, definition) => css `
80
104
  min-height: 0;
81
105
  }
82
106
 
107
+ /* The open menu is a flex column, so a flex-basis would override the AG row height. */
83
108
  .actions-vertical .menu-item {
84
- flex: 1 1 auto;
109
+ flex: none;
110
+ max-height: none;
111
+ }
112
+
113
+ .actions-vertical .menu-item::part(start) {
114
+ padding-left: calc(var(--ag-spacing, 4px) * 2);
115
+ }
116
+
117
+ .actions-vertical .menu-item [slot='start'] {
85
118
  justify-content: center;
86
- margin: calc(var(--design-unit) * 1px);
119
+ width: var(--ag-icon-size, 16px);
87
120
  }
88
121
 
89
122
  .actions-vertical .visible,
@@ -1,6 +1,42 @@
1
1
  import { html, ref, repeat, when } from '@microsoft/fast-element';
2
2
  import { getPrefix } from '../utils';
3
- export const menuTemplate = (prefix) => html `
3
+ /**
4
+ * Builds a template once per prefix. The bindings that pick a template re-run on every grid
5
+ * refresh; returning the same instance keeps the rendered view (and an open menu) in place.
6
+ * @remarks Don't remove this cache. FAST compares a binding's returned template by identity and
7
+ * re-creates the view whenever it gets a different instance. grid-pro's renderer `refresh()` hands
8
+ * the menu new params on every row update, so without it a streaming row would tear down the
9
+ * rendered view on each tick: the open popover is removed, `actionsMenu`/`actionsOpener` refs
10
+ * point at fresh elements, and `open` stays `true` with nothing visible.
11
+ */
12
+ const perPrefix = (build) => {
13
+ const templates = new Map();
14
+ return (prefix) => {
15
+ if (!templates.has(prefix))
16
+ templates.set(prefix, build(prefix));
17
+ return templates.get(prefix);
18
+ };
19
+ };
20
+ /**
21
+ * A bare icon opener (e.g. a "kebab"), styled like grid-pro's icon renderer rather than a button.
22
+ */
23
+ const iconOpenerTemplate = perPrefix((prefix) => html `
24
+ <div
25
+ ${ref('actionsOpener')}
26
+ class="actions-opener icon-only${(x) => (x.open ? ' opened' : '')}"
27
+ part="icon-opener"
28
+ role="button"
29
+ tabindex="0"
30
+ aria-label="${(x) => x.name}"
31
+ aria-haspopup="menu"
32
+ aria-expanded="${(x) => String(x.open)}"
33
+ @click=${(x) => x.toggleActionsMenuVisibility()}
34
+ @keydown=${(x, c) => x.onIconOpenerKeydown(c.event)}
35
+ >
36
+ <${prefix}-icon name="${(x) => x.iconName}" size="lg"></${prefix}-icon>
37
+ </div>
38
+ `);
39
+ const buttonOpenerTemplate = perPrefix((prefix) => html `
4
40
  <${prefix}-button
5
41
  ${ref('actionsOpener')}
6
42
  id="actiosnMenuTrigger"
@@ -15,32 +51,57 @@ export const menuTemplate = (prefix) => html `
15
51
  ${x.name}
16
52
  `}
17
53
  </${prefix}-button>
18
- <${prefix}-menu ${ref('actionsMenu')} popover="manual" class="actions-menu ${(x) => x.classNames}" part="actions-menu">
54
+ `);
55
+ export const menuTemplate = perPrefix((prefix) => html `
56
+ ${(x) => (x.iconName ? iconOpenerTemplate(prefix) : buttonOpenerTemplate(prefix))}
57
+ <${prefix}-menu
58
+ ${ref('actionsMenu')}
59
+ popover="${(x) => (x.isVertical ? 'auto' : 'manual')}"
60
+ class="actions-menu ${(x) => x.classNames}"
61
+ part="actions-menu"
62
+ @toggle=${(x, c) => x.onPopoverToggle(c.event)}
63
+ >
19
64
  ${repeat((x) => x.allActions, html `
20
- ${when((x, context) => x.isDisabled && x.isDisabled(context.parent.rowData), html `
65
+ ${when((x, context) => context.parent.isActionDisabled(x), html `
21
66
  <${prefix}-menu-item
22
67
  class="menu-item"
23
68
  tabindex="${(x, context) => context.index}"
24
69
  disabled
25
70
  style=${(x, ctx) => (ctx.parent.hideDisabled ? 'display:none' : '')}
26
71
  >
72
+ ${when((x) => x.icon, html `
73
+ <${prefix}-icon
74
+ name=${(x) => x.icon.name}
75
+ size=${(x) => x.icon.size}
76
+ variant=${(x) => x.icon.variant}
77
+ slot="start"
78
+ ></${prefix}-icon>
79
+ `)}
27
80
  ${(x, ctx) => ctx.parent.getLabel(x.name)}
28
81
  </${prefix}-menu-item>
29
82
  `)}
30
- ${when((x, context) => !x.isDisabled || !x.isDisabled(context.parent.rowData), html `
83
+ ${when((x, context) => !context.parent.isActionDisabled(x), html `
31
84
  <${prefix}-menu-item
32
85
  class="menu-item"
33
86
  tabindex="${(x, context) => context.index}"
34
87
  @click=${(x, context) => context.parent.onActionMenuItemClick(x.callback)}
35
88
  style="${(x) => (x.color ? `color: ${x.color};` : '')}"
36
89
  >
90
+ ${when((x) => x.icon, html `
91
+ <${prefix}-icon
92
+ name=${(x) => x.icon.name}
93
+ size=${(x) => x.icon.size}
94
+ variant=${(x) => x.icon.variant}
95
+ slot="start"
96
+ ></${prefix}-icon>
97
+ `)}
37
98
  ${(x, ctx) => ctx.parent.getLabel(x.name)}
38
99
  </${prefix}-menu-item>
39
100
  `)}
40
101
  `, { positioning: true })}
41
102
  </${prefix}-menu>
42
- `;
43
- export const singleOptionTemplate = (prefix) => html `
103
+ `);
104
+ export const singleOptionTemplate = perPrefix((prefix) => html `
44
105
  <${prefix}-button
45
106
  appearance="${(x) => x.buttonAppearance}"
46
107
  part="actions-opener"
@@ -49,12 +110,12 @@ export const singleOptionTemplate = (prefix) => html `
49
110
  return x === null || x === void 0 ? void 0 : x.singleOption.callback(x.rowData);
50
111
  }}
51
112
  ?disabled=${(x) => {
52
- return (x === null || x === void 0 ? void 0 : x.singleOption.isDisabled) && (x === null || x === void 0 ? void 0 : x.singleOption.isDisabled(x.rowData));
113
+ return x.isActionDisabled(x.singleOption);
53
114
  }}
54
115
  >
55
116
  ${(x) => x.getLabel(x === null || x === void 0 ? void 0 : x.singleOption.name)}
56
117
  </${prefix}-button>
57
- `;
118
+ `);
58
119
  export const foundationActionsMenuTemplate = html `
59
120
  <div
60
121
  class="actions-container${(x) => (x.isVertical ? ' actions-vertical' : '')}"