@tylertech/forge 3.11.0 → 3.12.1

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.
Files changed (184) hide show
  1. package/custom-elements.json +315 -145
  2. package/dist/app-bar/forge-app-bar.css +2 -0
  3. package/dist/button/forge-button.css +2 -0
  4. package/dist/checkbox/forge-checkbox.css +2 -0
  5. package/dist/chips/forge-chips.css +2 -0
  6. package/dist/floating-action-button/forge-floating-action-button.css +2 -0
  7. package/dist/icon-button/forge-icon-button.css +2 -0
  8. package/dist/lib.js +17 -17
  9. package/dist/lib.js.map +3 -3
  10. package/dist/list/forge-list.css +2 -0
  11. package/dist/radio/forge-radio.css +2 -0
  12. package/dist/skip-link/forge-skip-link.css +2 -0
  13. package/dist/state-layer/forge-state-layer.css +2 -0
  14. package/dist/switch/forge-switch.css +2 -0
  15. package/dist/vscode.html-custom-data.json +74 -59
  16. package/esm/accordion/accordion.d.ts +1 -1
  17. package/esm/accordion/accordion.js +1 -1
  18. package/esm/app-bar/app-bar/app-bar.d.ts +1 -1
  19. package/esm/app-bar/app-bar/app-bar.js +1 -1
  20. package/esm/autocomplete/autocomplete-core.js +16 -3
  21. package/esm/autocomplete/autocomplete.d.ts +1 -1
  22. package/esm/autocomplete/autocomplete.js +1 -1
  23. package/esm/avatar/avatar.d.ts +1 -1
  24. package/esm/avatar/avatar.js +1 -1
  25. package/esm/backdrop/backdrop.d.ts +1 -1
  26. package/esm/backdrop/backdrop.js +1 -1
  27. package/esm/badge/badge.d.ts +1 -1
  28. package/esm/badge/badge.js +1 -1
  29. package/esm/banner/banner.d.ts +1 -1
  30. package/esm/banner/banner.js +1 -1
  31. package/esm/button/button.d.ts +1 -6
  32. package/esm/button/button.js +1 -6
  33. package/esm/button-area/button-area.d.ts +1 -4
  34. package/esm/button-area/button-area.js +1 -4
  35. package/esm/button-toggle/button-toggle/button-toggle.d.ts +1 -1
  36. package/esm/button-toggle/button-toggle/button-toggle.js +1 -1
  37. package/esm/button-toggle/button-toggle-group/button-toggle-group.d.ts +1 -1
  38. package/esm/button-toggle/button-toggle-group/button-toggle-group.js +1 -1
  39. package/esm/chip-field/chip-field.d.ts +1 -1
  40. package/esm/chip-field/chip-field.js +1 -1
  41. package/esm/chips/chip/chip-adapter.d.ts +3 -0
  42. package/esm/chips/chip/chip-adapter.js +12 -2
  43. package/esm/chips/chip/chip-constants.d.ts +2 -0
  44. package/esm/chips/chip/chip-constants.js +2 -1
  45. package/esm/chips/chip/chip-core.d.ts +4 -0
  46. package/esm/chips/chip/chip-core.js +9 -0
  47. package/esm/chips/chip/chip.d.ts +4 -0
  48. package/esm/chips/chip/chip.js +8 -0
  49. package/esm/chips/chip-set/chip-set.d.ts +1 -1
  50. package/esm/chips/chip-set/chip-set.js +1 -1
  51. package/esm/circular-progress/circular-progress.d.ts +1 -2
  52. package/esm/circular-progress/circular-progress.js +1 -2
  53. package/esm/color-picker/color-picker.d.ts +1 -1
  54. package/esm/color-picker/color-picker.js +1 -1
  55. package/esm/core/testing/test-harness.d.ts +11 -0
  56. package/esm/core/testing/test-harness.js +14 -0
  57. package/esm/core/testing/utils.d.ts +7 -0
  58. package/esm/core/testing/utils.js +14 -0
  59. package/esm/date-picker/date-picker.d.ts +1 -1
  60. package/esm/date-picker/date-picker.js +1 -1
  61. package/esm/dialog/dialog.d.ts +1 -1
  62. package/esm/dialog/dialog.js +1 -1
  63. package/esm/divider/divider.d.ts +1 -1
  64. package/esm/divider/divider.js +1 -1
  65. package/esm/drawer/base/base-drawer-adapter.d.ts +2 -0
  66. package/esm/drawer/base/base-drawer-adapter.js +3 -0
  67. package/esm/drawer/base/base-drawer-core.js +3 -0
  68. package/esm/drawer/drawer/drawer.d.ts +1 -1
  69. package/esm/drawer/drawer/drawer.js +1 -1
  70. package/esm/drawer/modal-drawer/modal-drawer.d.ts +1 -1
  71. package/esm/drawer/modal-drawer/modal-drawer.js +1 -1
  72. package/esm/expansion-panel/expansion-panel.d.ts +1 -1
  73. package/esm/expansion-panel/expansion-panel.js +1 -1
  74. package/esm/field/field.d.ts +1 -2
  75. package/esm/field/field.js +1 -2
  76. package/esm/file-picker/file-picker.d.ts +2 -3
  77. package/esm/file-picker/file-picker.js +2 -3
  78. package/esm/floating-action-button/floating-action-button.d.ts +1 -1
  79. package/esm/floating-action-button/floating-action-button.js +1 -1
  80. package/esm/focus-indicator/focus-indicator.d.ts +1 -1
  81. package/esm/focus-indicator/focus-indicator.js +22 -21
  82. package/esm/icon/icon.d.ts +1 -1
  83. package/esm/icon/icon.js +1 -1
  84. package/esm/icon-button/icon-button.d.ts +1 -1
  85. package/esm/icon-button/icon-button.js +1 -1
  86. package/esm/inline-message/inline-message.d.ts +1 -1
  87. package/esm/inline-message/inline-message.js +1 -1
  88. package/esm/label/label.d.ts +1 -1
  89. package/esm/label/label.js +1 -1
  90. package/esm/label-value/label-value.d.ts +1 -1
  91. package/esm/label-value/label-value.js +1 -1
  92. package/esm/linear-progress/linear-progress.d.ts +1 -2
  93. package/esm/linear-progress/linear-progress.js +1 -2
  94. package/esm/list/list/list.d.ts +1 -1
  95. package/esm/list/list/list.js +1 -1
  96. package/esm/list/list-item/list-item-constants.js +1 -1
  97. package/esm/list-dropdown/list-dropdown-constants.d.ts +5 -1
  98. package/esm/list-dropdown/list-dropdown-core.js +1 -1
  99. package/esm/list-dropdown/list-dropdown-utils.d.ts +3 -1
  100. package/esm/list-dropdown/list-dropdown-utils.js +41 -20
  101. package/esm/list-dropdown/list-dropdown.js +1 -1
  102. package/esm/menu/menu-adapter.d.ts +2 -0
  103. package/esm/menu/menu-adapter.js +12 -8
  104. package/esm/menu/menu-constants.d.ts +1 -0
  105. package/esm/menu/menu-constants.js +3 -2
  106. package/esm/menu/menu-core.d.ts +5 -0
  107. package/esm/menu/menu-core.js +41 -2
  108. package/esm/menu/menu.d.ts +16 -1
  109. package/esm/menu/menu.js +14 -2
  110. package/esm/open-icon/open-icon.d.ts +2 -1
  111. package/esm/open-icon/open-icon.js +2 -1
  112. package/esm/overlay/overlay.d.ts +1 -2
  113. package/esm/overlay/overlay.js +1 -2
  114. package/esm/page-state/page-state.d.ts +1 -1
  115. package/esm/page-state/page-state.js +1 -1
  116. package/esm/paginator/paginator-core.d.ts +16 -0
  117. package/esm/paginator/paginator-core.js +29 -9
  118. package/esm/paginator/paginator.d.ts +37 -1
  119. package/esm/paginator/paginator.js +45 -1
  120. package/esm/popover/popover-adapter.js +1 -1
  121. package/esm/popover/popover-constants.d.ts +4 -0
  122. package/esm/popover/popover-constants.js +4 -2
  123. package/esm/popover/popover-core.d.ts +5 -1
  124. package/esm/popover/popover-core.js +13 -0
  125. package/esm/popover/popover.d.ts +6 -2
  126. package/esm/popover/popover.js +9 -1
  127. package/esm/profile-card/profile-card.d.ts +2 -1
  128. package/esm/profile-card/profile-card.js +2 -1
  129. package/esm/radio/radio/radio.d.ts +1 -2
  130. package/esm/radio/radio/radio.js +1 -2
  131. package/esm/scaffold/scaffold.d.ts +1 -1
  132. package/esm/scaffold/scaffold.js +1 -1
  133. package/esm/select/option/option.d.ts +1 -1
  134. package/esm/select/option/option.js +1 -1
  135. package/esm/select/select/select.d.ts +1 -1
  136. package/esm/select/select/select.js +2 -2
  137. package/esm/skeleton/skeleton.d.ts +1 -1
  138. package/esm/skeleton/skeleton.js +1 -1
  139. package/esm/skip-link/skip-link.d.ts +1 -1
  140. package/esm/skip-link/skip-link.js +1 -1
  141. package/esm/slider/slider.d.ts +1 -1
  142. package/esm/slider/slider.js +1 -1
  143. package/esm/split-button/split-button.d.ts +1 -1
  144. package/esm/split-button/split-button.js +1 -1
  145. package/esm/split-view/split-view-panel/split-view-panel.js +1 -1
  146. package/esm/stack/stack.d.ts +1 -8
  147. package/esm/stack/stack.js +1 -8
  148. package/esm/state-layer/state-layer.d.ts +1 -1
  149. package/esm/state-layer/state-layer.js +2 -2
  150. package/esm/table/table-adapter.d.ts +4 -4
  151. package/esm/table/table-adapter.js +4 -4
  152. package/esm/table/table-core.js +2 -2
  153. package/esm/table/table-utils.d.ts +2 -2
  154. package/esm/table/table-utils.js +22 -18
  155. package/esm/table/table.d.ts +2 -2
  156. package/esm/table/table.js +1 -1
  157. package/esm/tabs/tab/tab.d.ts +1 -1
  158. package/esm/tabs/tab/tab.js +1 -1
  159. package/esm/tabs/tab-bar/tab-bar.d.ts +1 -1
  160. package/esm/tabs/tab-bar/tab-bar.js +1 -1
  161. package/esm/text-field/text-field-adapter.d.ts +6 -4
  162. package/esm/text-field/text-field-adapter.js +11 -4
  163. package/esm/text-field/text-field-core.d.ts +4 -0
  164. package/esm/text-field/text-field-core.js +13 -2
  165. package/esm/text-field/text-field.d.ts +1 -1
  166. package/esm/text-field/text-field.js +1 -1
  167. package/esm/time-picker/time-picker-adapter.js +1 -0
  168. package/esm/time-picker/time-picker-core.js +3 -3
  169. package/esm/toast/toast-adapter.d.ts +20 -0
  170. package/esm/toast/toast-adapter.js +30 -0
  171. package/esm/toast/toast-core.d.ts +17 -0
  172. package/esm/toast/toast-core.js +66 -0
  173. package/esm/toast/toast.d.ts +9 -2
  174. package/esm/toast/toast.js +10 -1
  175. package/esm/toolbar/toolbar.d.ts +1 -3
  176. package/esm/toolbar/toolbar.js +1 -3
  177. package/esm/tooltip/tooltip.d.ts +1 -1
  178. package/esm/tooltip/tooltip.js +1 -1
  179. package/esm/view-switcher/view/view.d.ts +1 -1
  180. package/esm/view-switcher/view/view.js +1 -1
  181. package/esm/view-switcher/view-switcher.d.ts +1 -1
  182. package/esm/view-switcher/view-switcher.js +1 -1
  183. package/package.json +1 -1
  184. package/sass/state-layer/_core.scss +2 -0
@@ -135,7 +135,7 @@ export class ListDropdownCore {
135
135
  get dropdownElement() {
136
136
  return this._adapter.dropdownElement;
137
137
  }
138
- scrollSelectedOptionIntoView(animate = true) {
138
+ scrollSelectedOptionIntoView(animate) {
139
139
  this._adapter.scrollSelectedOptionIntoView(animate);
140
140
  }
141
141
  setScrollBottomListener(listener, threshold) {
@@ -6,7 +6,7 @@
6
6
  import { ILinearProgressComponent } from '../linear-progress';
7
7
  import { IListComponent } from '../list/list';
8
8
  import { IPopoverComponent } from '../popover/popover';
9
- import { IListDropdownOpenConfig, IListDropdownOption, IListDropdownOptionGroup, ListDropdownAsyncStyle } from './list-dropdown-constants';
9
+ import { IListDropdownOpenConfig, IListDropdownOption, IListDropdownOptionGroup, ListDropdownAsyncStyle, ListDropdownTooltipConfig } from './list-dropdown-constants';
10
10
  export declare enum ListDropdownOptionType {
11
11
  Option = 0,
12
12
  Group = 1
@@ -24,6 +24,8 @@ export declare function createList(config: IListDropdownOpenConfig): IListCompon
24
24
  * @param config
25
25
  */
26
26
  export declare function createListItems(config: IListDropdownOpenConfig, listElement: IListComponent, options?: Array<IListDropdownOption | IListDropdownOptionGroup>, startIndex?: number, renderSelected?: boolean): void;
27
+ export declare function createListItemTooltip(tooltip: ListDropdownTooltipConfig, configId: string, optionIdIndex: number, listItemElement: HTMLElement, buttonElement: HTMLElement): HTMLElement;
28
+ export declare function asyncCreateListItemTooltipIfTextOverflows(tooltip: ListDropdownTooltipConfig, configId: string, optionIdIndex: number, listItemElement: HTMLElement, buttonElement: HTMLElement, secondaryLabelElement: HTMLElement): Promise<void>;
27
29
  export declare function createCheckboxElement(selected: boolean): HTMLElement;
28
30
  export declare function createAsyncElement(asyncStyle?: ListDropdownAsyncStyle): HTMLElement;
29
31
  export declare function createBusyElement(): ILinearProgressComponent;
@@ -11,6 +11,7 @@ import { LIST_CONSTANTS } from '../list/list';
11
11
  import { POPOVER_CONSTANTS } from '../popover';
12
12
  import { SKELETON_CONSTANTS } from '../skeleton';
13
13
  import { ListDropdownAsyncStyle, ListDropdownType, LIST_DROPDOWN_CONSTANTS } from './list-dropdown-constants';
14
+ import { frame } from '../core/utils/utils';
14
15
  export var ListDropdownOptionType;
15
16
  (function (ListDropdownOptionType) {
16
17
  ListDropdownOptionType[ListDropdownOptionType["Option"] = 0] = "Option";
@@ -66,6 +67,9 @@ export function createPopupDropdown(config, targetElement) {
66
67
  if (config.popupShift !== undefined) {
67
68
  popoverElement.shift = config.popupShift;
68
69
  }
70
+ if (config.anchorAccessibility) {
71
+ popoverElement.anchorAccessibility = config.anchorAccessibility;
72
+ }
69
73
  // Set the animations based on our type
70
74
  if (config.type === ListDropdownType.None) {
71
75
  popoverElement.animationType = 'none';
@@ -220,7 +224,7 @@ export function createListItems(config, listElement, options, startIndex = 0, re
220
224
  buttonElement.textContent = option.label || '';
221
225
  }
222
226
  else {
223
- const result = config.transform(option.label);
227
+ const result = config.transform(option.label, option, isSelected);
224
228
  if (typeof result === 'string') {
225
229
  buttonElement.textContent = result;
226
230
  }
@@ -229,27 +233,9 @@ export function createListItems(config, listElement, options, startIndex = 0, re
229
233
  }
230
234
  }
231
235
  }
232
- // Check for a tooltip configuration
233
- if (option.tooltip) {
234
- const { text, type = 'presentation', ...restConfig } = option.tooltip;
235
- const tooltipElement = document.createElement('forge-tooltip');
236
- tooltipElement.id = `list-dropdown-option-${config.id}-${optionIdIndex++}-tooltip`;
237
- tooltipElement.textContent = option.tooltip.text;
238
- // We always anchor to the list item element unless an anchor element is provided
239
- if (!option.tooltip.anchor && !option.tooltip.anchorElement) {
240
- tooltipElement.anchorElement = listItemElement;
241
- }
242
- // We need to attach the tooltip ARIA attributes to the button element, not the anchor element
243
- if (type === 'label' || type === 'description') {
244
- const a11yAttr = type === 'label' ? 'aria-labelledby' : 'aria-describedby';
245
- buttonElement.setAttribute(a11yAttr, tooltipElement.id);
246
- }
247
- Object.assign(tooltipElement, restConfig);
248
- listItemElement.appendChild(tooltipElement);
249
- }
236
+ const secondaryLabelElement = document.createElement('span');
250
237
  // Check for secondary (subtitle) text
251
238
  if (option.secondaryLabel) {
252
- const secondaryLabelElement = document.createElement('span');
253
239
  secondaryLabelElement.slot = 'secondary-text';
254
240
  secondaryLabelElement.textContent = option.secondaryLabel;
255
241
  secondaryLabelElement.id = `list-dropdown-option-${config.id}-${optionIdIndex++}-secondary`;
@@ -257,6 +243,16 @@ export function createListItems(config, listElement, options, startIndex = 0, re
257
243
  listItemElement.appendChild(secondaryLabelElement);
258
244
  buttonElement.setAttribute('aria-describedby', secondaryLabelElement.id);
259
245
  }
246
+ // Check for a tooltip configuration
247
+ if (option.tooltip) {
248
+ if (!isDefined(option.tooltip?.visibilityMode) || option.tooltip?.visibilityMode === 'always') {
249
+ const tooltipElement = createListItemTooltip(option.tooltip, config.id, optionIdIndex, listItemElement, buttonElement);
250
+ listItemElement.appendChild(tooltipElement);
251
+ }
252
+ else if (option.tooltip?.visibilityMode === 'overflow-only') {
253
+ asyncCreateListItemTooltipIfTextOverflows(option.tooltip, config.id, optionIdIndex, listItemElement, buttonElement, secondaryLabelElement);
254
+ }
255
+ }
260
256
  // If multiple selections are enabled then we need to create and append a leading checkbox element
261
257
  if (config.multiple) {
262
258
  const checkboxElement = createCheckboxElement(isSelected);
@@ -330,6 +326,31 @@ export function createListItems(config, listElement, options, startIndex = 0, re
330
326
  }
331
327
  }
332
328
  }
329
+ export function createListItemTooltip(tooltip, configId, optionIdIndex, listItemElement, buttonElement) {
330
+ const { text, type = 'presentation', ...restConfig } = tooltip;
331
+ const tooltipElement = document.createElement('forge-tooltip');
332
+ tooltipElement.id = `list-dropdown-option-${configId}-${optionIdIndex++}-tooltip`;
333
+ tooltipElement.textContent = tooltip.text;
334
+ // We always anchor to the list item element unless an anchor element is provided
335
+ if (!tooltip.anchor && !tooltip.anchorElement) {
336
+ tooltipElement.anchorElement = listItemElement;
337
+ }
338
+ // We need to attach the tooltip ARIA attributes to the button element, not the anchor element
339
+ if (type === 'label' || type === 'description') {
340
+ const a11yAttr = type === 'label' ? 'aria-labelledby' : 'aria-describedby';
341
+ buttonElement.setAttribute(a11yAttr, tooltipElement.id);
342
+ }
343
+ Object.assign(tooltipElement, restConfig);
344
+ return tooltipElement;
345
+ }
346
+ export async function asyncCreateListItemTooltipIfTextOverflows(tooltip, configId, optionIdIndex, listItemElement, buttonElement, secondaryLabelElement) {
347
+ await frame();
348
+ // Only append the tooltip if the button or secondary label overflow the width of the parent list item.
349
+ if (buttonElement.scrollWidth > buttonElement.clientWidth || secondaryLabelElement.scrollWidth > secondaryLabelElement.clientWidth) {
350
+ const tooltipElement = createListItemTooltip(tooltip, configId, optionIdIndex, listItemElement, buttonElement);
351
+ listItemElement.appendChild(tooltipElement);
352
+ }
353
+ }
333
354
  export function createCheckboxElement(selected) {
334
355
  const checkboxElement = document.createElement('forge-icon');
335
356
  checkboxElement.setAttribute(LIST_DROPDOWN_CONSTANTS.attributes.CHECKBOX_ELEMENT, '');
@@ -69,7 +69,7 @@ export class ListDropdown {
69
69
  this._core.appendOptions(options);
70
70
  }
71
71
  scrollSelectedOptionIntoView(animate) {
72
- this._core.scrollSelectedOptionIntoView();
72
+ this._core.scrollSelectedOptionIntoView(animate);
73
73
  }
74
74
  setScrollBottomListener(listener, threshold) {
75
75
  this._core.setScrollBottomListener(listener, threshold);
@@ -35,6 +35,7 @@ export interface IMenuAdapter extends IBaseAdapter {
35
35
  createChildMenu(index: number, parentValue: any, openCb: (index: number) => void, closeCb: (index: number) => void, selectCb: (data: IMenuSelectEventData) => void): IMenuComponent;
36
36
  closeOtherChildMenus(excludeIndex?: number): void;
37
37
  setSelectedValues(values: any[]): void;
38
+ resolvePopupTargetById(id: string | null): HTMLElement | null;
38
39
  }
39
40
  export declare class MenuAdapter extends BaseAdapter<IMenuComponent> implements IMenuAdapter {
40
41
  private _targetElement;
@@ -70,4 +71,5 @@ export declare class MenuAdapter extends BaseAdapter<IMenuComponent> implements
70
71
  private _getOpenChildMenu;
71
72
  private _getOwnList;
72
73
  private _getListItems;
74
+ resolvePopupTargetById(id: string | null): HTMLElement | null;
73
75
  }
@@ -40,20 +40,16 @@ export class MenuAdapter extends BaseAdapter {
40
40
  }
41
41
  }
42
42
  addTargetListener(event, callback, bubbles = false) {
43
- if (this._targetElement) {
44
- this._targetElement.addEventListener(event, callback, bubbles);
45
- }
43
+ this._targetElement?.addEventListener(event, callback, bubbles);
46
44
  }
47
45
  removeTargetListener(event, callback) {
48
- if (this._targetElement) {
49
- this._targetElement.removeEventListener(event, callback);
50
- }
46
+ this._targetElement?.removeEventListener(event, callback);
51
47
  }
52
48
  attachMenu(config) {
53
- if (this._listDropdown || !this._targetElement) {
49
+ if (this._listDropdown || !this._targetElement || !config.referenceElement) {
54
50
  return;
55
51
  }
56
- this._listDropdown = new ListDropdown(this._targetElement, config);
52
+ this._listDropdown = new ListDropdown(config.referenceElement, config);
57
53
  this._listDropdown.open();
58
54
  this._targetElement.setAttribute('aria-expanded', 'true');
59
55
  this._targetElement.setAttribute('aria-controls', `list-dropdown-popup-${config.id}`);
@@ -201,4 +197,12 @@ export class MenuAdapter extends BaseAdapter {
201
197
  }
202
198
  return [];
203
199
  }
200
+ resolvePopupTargetById(id) {
201
+ if (!id) {
202
+ return null;
203
+ }
204
+ const root = this._component.getRootNode();
205
+ const contextDocument = root instanceof Document || root instanceof ShadowRoot ? root : document;
206
+ return contextDocument.getElementById(id);
207
+ }
204
208
  }
@@ -27,6 +27,7 @@ export declare const MENU_CONSTANTS: {
27
27
  OPTION_LIMIT: string;
28
28
  OBSERVE_SCROLL: string;
29
29
  OBSERVE_SCROLL_THRESHOLD: string;
30
+ POPUP_TARGET: string;
30
31
  };
31
32
  events: {
32
33
  SELECT: string;
@@ -9,7 +9,7 @@ const classes = {
9
9
  POPUP: 'forge-menu__popup'
10
10
  };
11
11
  const selectors = {
12
- TOGGLE: `.${elementName}__toggle,[${elementName}-toggle],forge-button,forge-icon-button,forge-fab,button,[type=button],[role=button],a,forge-list-item,[tabindex]:not([tabindex^="-"])`,
12
+ TOGGLE: `.${elementName}__toggle,[${elementName}-toggle],forge-button,forge-icon-button,forge-fab,button,[type=button],[role=button],a,forge-list-item > button,[tabindex]:not([tabindex^="-"])`,
13
13
  MENU_LIST: 'forge-list'
14
14
  };
15
15
  const attributes = {
@@ -25,7 +25,8 @@ const attributes = {
25
25
  POPUP_CLASSES: 'popup-classes',
26
26
  OPTION_LIMIT: 'option-limit',
27
27
  OBSERVE_SCROLL: 'observe-scroll',
28
- OBSERVE_SCROLL_THRESHOLD: 'observe-scroll-threshold'
28
+ OBSERVE_SCROLL_THRESHOLD: 'observe-scroll-threshold',
29
+ POPUP_TARGET: 'popup-target'
29
30
  };
30
31
  const events = {
31
32
  SELECT: `${elementName}-select`,
@@ -26,6 +26,7 @@ export interface IMenuCore {
26
26
  mode: MenuMode;
27
27
  popupOffset: IOverlayOffset;
28
28
  optionBuilder: MenuOptionBuilder | undefined;
29
+ popupTarget: string | null;
29
30
  activateFirstOption(): void;
30
31
  }
31
32
  export declare class MenuCore extends CascadingListDropdownAwareCore<IMenuOption | IMenuOptionGroup> implements IMenuCore {
@@ -40,6 +41,7 @@ export declare class MenuCore extends CascadingListDropdownAwareCore<IMenuOption
40
41
  private _mode;
41
42
  private _popupOffset;
42
43
  private _optionBuilder;
44
+ private _popupTarget;
43
45
  private _identifier;
44
46
  private _clickListener;
45
47
  private _blurListener;
@@ -82,6 +84,7 @@ export declare class MenuCore extends CascadingListDropdownAwareCore<IMenuOption
82
84
  protected _setCascadeTargetInactive(): void;
83
85
  protected _isOwnElement(element: Element): boolean;
84
86
  private _createCascadingElement;
87
+ private _tryResolvePopupTarget;
85
88
  private _mapIconToLeadingIcon;
86
89
  get open(): boolean;
87
90
  set open(value: boolean);
@@ -106,6 +109,8 @@ export declare class MenuCore extends CascadingListDropdownAwareCore<IMenuOption
106
109
  set mode(value: MenuMode);
107
110
  get popupOffset(): IOverlayOffset;
108
111
  set popupOffset(value: IOverlayOffset);
112
+ get popupTarget(): string | null;
113
+ set popupTarget(value: string | null);
109
114
  get optionBuilder(): MenuOptionBuilder | undefined;
110
115
  set optionBuilder(cb: MenuOptionBuilder | undefined);
111
116
  get popupElement(): HTMLElement | null;
@@ -21,6 +21,7 @@ export class MenuCore extends CascadingListDropdownAwareCore {
21
21
  this._iconClass = ICON_CLASS_NAME;
22
22
  this._persistSelection = false;
23
23
  this._mode = 'click';
24
+ this._popupTarget = null;
24
25
  this._identifier = randomChars();
25
26
  this._clickListener = evt => this._onTargetClick(evt);
26
27
  this._blurListener = evt => this._onTargetBlur(evt);
@@ -218,9 +219,22 @@ export class MenuCore extends CascadingListDropdownAwareCore {
218
219
  }
219
220
  this._mapIconToLeadingIcon();
220
221
  const selectedValues = this._persistSelection ? this._getSelectedValues() : [];
222
+ let referenceElement = this._adapter.targetElement;
223
+ if (this._popupTarget) {
224
+ const popupTargetElement = this._adapter.resolvePopupTargetById(this._popupTarget);
225
+ if (popupTargetElement) {
226
+ referenceElement = popupTargetElement;
227
+ }
228
+ }
229
+ else {
230
+ const resolvedTarget = this._tryResolvePopupTarget();
231
+ if (resolvedTarget) {
232
+ referenceElement = resolvedTarget;
233
+ }
234
+ }
221
235
  const config = {
222
236
  id: this._identifier,
223
- referenceElement: this._adapter.targetElement,
237
+ referenceElement,
224
238
  type: ListDropdownType.Menu,
225
239
  options: this._options,
226
240
  selectedValues,
@@ -246,7 +260,8 @@ export class MenuCore extends CascadingListDropdownAwareCore {
246
260
  activeChangeCallback: this._activeChangeListener,
247
261
  selectCallback: this._selectListener,
248
262
  popupOffset: this._popupOffset,
249
- cascadingElementFactory: params => this._createCascadingElement(params)
263
+ cascadingElementFactory: params => this._createCascadingElement(params),
264
+ anchorAccessibility: 'none'
250
265
  };
251
266
  this._adapter.setHostAttribute(MENU_CONSTANTS.attributes.OPEN, '');
252
267
  this._adapter.attachMenu(config);
@@ -391,6 +406,15 @@ export class MenuCore extends CascadingListDropdownAwareCore {
391
406
  menu.iconClass = this._iconClass;
392
407
  return menu;
393
408
  }
409
+ _tryResolvePopupTarget() {
410
+ if (this._popupTarget) {
411
+ return;
412
+ }
413
+ // Automatically detect forge-list-item > button case and set popup target if not explicitly set
414
+ if (this._adapter.targetElement?.matches('button')) {
415
+ return this._adapter.targetElement.closest('forge-list-item') ?? undefined;
416
+ }
417
+ }
394
418
  _mapIconToLeadingIcon() {
395
419
  // For backwards compatibility with old API, map the old "icon" property to the new "leadingIcon" property (if exists)
396
420
  this._flatOptions.filter(o => o.icon).forEach(o => (o.leadingIcon = o.icon));
@@ -516,6 +540,21 @@ export class MenuCore extends CascadingListDropdownAwareCore {
516
540
  set popupOffset(value) {
517
541
  this._popupOffset = value;
518
542
  }
543
+ get popupTarget() {
544
+ return this._popupTarget ?? null;
545
+ }
546
+ set popupTarget(value) {
547
+ value = value ?? null;
548
+ if (this._popupTarget !== value) {
549
+ this._popupTarget = value;
550
+ if (this._popupTarget) {
551
+ this._adapter.setHostAttribute(MENU_CONSTANTS.attributes.POPUP_TARGET, this._popupTarget);
552
+ }
553
+ else {
554
+ this._adapter.removeHostAttribute(MENU_CONSTANTS.attributes.POPUP_TARGET);
555
+ }
556
+ }
557
+ }
519
558
  get optionBuilder() {
520
559
  return this._optionBuilder;
521
560
  }
@@ -21,6 +21,7 @@ export interface IMenuComponent extends IListDropdownAware {
21
21
  popupOffset: IOverlayOffset;
22
22
  optionBuilder: MenuOptionBuilder | undefined;
23
23
  popupElement: HTMLElement | undefined;
24
+ popupTarget: string | null;
24
25
  propagateKeyEvent(evt: KeyboardEvent): void;
25
26
  activateFirstOption(): void;
26
27
  }
@@ -38,10 +39,15 @@ declare global {
38
39
  /**
39
40
  * @tag forge-menu
40
41
  *
41
- * @summary Menus display a list of options or actions that users can select from a dropdown.
42
+ * @summary Menus display a list of options or actions that users can select from a dropdown. Menus wrap button or list item elements to provide the trigger for displaying the menu options.
42
43
  *
43
44
  * @dependency forge-popover
44
45
  * @dependency forge-list
46
+ *
47
+ * @event {CustomEvent<IMenuSelectEventData>} forge-menu-select - Dispatches when a menu option is selected.
48
+ * @event {CustomEvent<void>} forge-menu-open - Dispatches when the menu is opened.
49
+ * @event {CustomEvent<void>} forge-menu-close - Dispatches when the menu is closed.
50
+ * @event {CustomEvent<IMenuActiveChangeEventData>} forge-menu-active-change - Dispatches when the active menu option changes.
45
51
  */
46
52
  export declare class MenuComponent extends ListDropdownAware implements IMenuComponent {
47
53
  static get observedAttributes(): string[];
@@ -119,6 +125,15 @@ export declare class MenuComponent extends ListDropdownAware implements IMenuCom
119
125
  * @readonly
120
126
  */
121
127
  popupElement: HTMLElement | undefined;
128
+ /**
129
+ * Gets/sets the ID of the element to use as the popup anchor for positioning.
130
+ * When null or empty, the target element (button) is used for both interaction and positioning.
131
+ * This is useful for cases like forge-list-item > button where the menu should be
132
+ * attached to the button for listeners but positioned relative to the list item.
133
+ * @default null
134
+ * @attribute popup-target
135
+ */
136
+ popupTarget: string | null;
122
137
  /**
123
138
  * Force propagates the key event from another element to this component.
124
139
  */
package/esm/menu/menu.js CHANGED
@@ -19,10 +19,15 @@ const styles = ':host{display:inline-flex}:host([hidden]){display:none}';
19
19
  /**
20
20
  * @tag forge-menu
21
21
  *
22
- * @summary Menus display a list of options or actions that users can select from a dropdown.
22
+ * @summary Menus display a list of options or actions that users can select from a dropdown. Menus wrap button or list item elements to provide the trigger for displaying the menu options.
23
23
  *
24
24
  * @dependency forge-popover
25
25
  * @dependency forge-list
26
+ *
27
+ * @event {CustomEvent<IMenuSelectEventData>} forge-menu-select - Dispatches when a menu option is selected.
28
+ * @event {CustomEvent<void>} forge-menu-open - Dispatches when the menu is opened.
29
+ * @event {CustomEvent<void>} forge-menu-close - Dispatches when the menu is closed.
30
+ * @event {CustomEvent<IMenuActiveChangeEventData>} forge-menu-active-change - Dispatches when the active menu option changes.
26
31
  */
27
32
  let MenuComponent = class MenuComponent extends ListDropdownAware {
28
33
  static get observedAttributes() {
@@ -40,7 +45,8 @@ let MenuComponent = class MenuComponent extends ListDropdownAware {
40
45
  MENU_CONSTANTS.attributes.POPUP_CLASSES,
41
46
  MENU_CONSTANTS.attributes.OPTION_LIMIT,
42
47
  MENU_CONSTANTS.attributes.OBSERVE_SCROLL,
43
- MENU_CONSTANTS.attributes.OBSERVE_SCROLL_THRESHOLD
48
+ MENU_CONSTANTS.attributes.OBSERVE_SCROLL_THRESHOLD,
49
+ MENU_CONSTANTS.attributes.POPUP_TARGET
44
50
  ];
45
51
  }
46
52
  constructor() {
@@ -84,6 +90,9 @@ let MenuComponent = class MenuComponent extends ListDropdownAware {
84
90
  case MENU_CONSTANTS.attributes.MODE:
85
91
  this.mode = newValue;
86
92
  break;
93
+ case MENU_CONSTANTS.attributes.POPUP_TARGET:
94
+ this._core.popupTarget = isDefined(newValue) ? newValue : null;
95
+ break;
87
96
  }
88
97
  }
89
98
  disconnectedCallback() {
@@ -141,6 +150,9 @@ __decorate([
141
150
  __decorate([
142
151
  coreProperty({ set: false })
143
152
  ], MenuComponent.prototype, "popupElement", void 0);
153
+ __decorate([
154
+ coreProperty()
155
+ ], MenuComponent.prototype, "popupTarget", void 0);
144
156
  MenuComponent = __decorate([
145
157
  customElement({
146
158
  name: MENU_CONSTANTS.elementName,
@@ -18,7 +18,8 @@ declare global {
18
18
  /**
19
19
  * @tag forge-open-icon
20
20
  *
21
- * @summary Open icons are used to indicate whether a section is open or closed.
21
+ * @summary Open icons are icons used to indicate whether a section is open or closed. They provide an animated transition between the two states to enhance the user experience.
22
+
22
23
  *
23
24
  * @property {boolean} [open=false] - Whether the icon is open or closed.
24
25
  * @property {OpenIconOrientation} [orientation=vertical] - The orientation of the rotation.
@@ -14,7 +14,8 @@ const styles = ':host{display:inline-flex}:host([hidden]){display:none}.forge-op
14
14
  /**
15
15
  * @tag forge-open-icon
16
16
  *
17
- * @summary Open icons are used to indicate whether a section is open or closed.
17
+ * @summary Open icons are icons used to indicate whether a section is open or closed. They provide an animated transition between the two states to enhance the user experience.
18
+
18
19
  *
19
20
  * @property {boolean} [open=false] - Whether the icon is open or closed.
20
21
  * @property {OpenIconOrientation} [orientation=vertical] - The orientation of the rotation.
@@ -21,8 +21,7 @@ declare global {
21
21
  /**
22
22
  * @tag forge-overlay
23
23
  *
24
- * @summary Overlays are used to render content in an element that rendered above all content on the page,
25
- * and positioned around a specified anchor element.
24
+ * @summary Overlays are used to show content in an element that is rendered above all other content on the page, and positioned around a specified anchor element. This is a low-level building block component that does not provide any visual styles, but is used within other components such as popovers, and tooltips.
26
25
  *
27
26
  * @description
28
27
  * An overlay is a low-level building block component that does not provide any visual styles. Its only
@@ -15,8 +15,7 @@ const styles = ':host{display:contents}:host([hidden]){display:none}.forge-overl
15
15
  /**
16
16
  * @tag forge-overlay
17
17
  *
18
- * @summary Overlays are used to render content in an element that rendered above all content on the page,
19
- * and positioned around a specified anchor element.
18
+ * @summary Overlays are used to show content in an element that is rendered above all other content on the page, and positioned around a specified anchor element. This is a low-level building block component that does not provide any visual styles, but is used within other components such as popovers, and tooltips.
20
19
  *
21
20
  * @description
22
21
  * An overlay is a low-level building block component that does not provide any visual styles. Its only
@@ -14,7 +14,7 @@ declare global {
14
14
  /**
15
15
  * @tag forge-page-state
16
16
  *
17
- * @summary Page states display full-page messages for empty states, errors, or loading scenarios.
17
+ * @summary Page states display full-height messages for empty states, errors, or other general information. They can be used as full page content, or within smaller containers and will adapt accordingly.
18
18
  *
19
19
  * @cssproperty --forge-page-state-width - The width of the page state.
20
20
  * @cssproperty --forge-page-state-height - The height of the page state.
@@ -12,7 +12,7 @@ const styles = ':host{--_page-state-width:var(--forge-page-state-width, 576px);-
12
12
  /**
13
13
  * @tag forge-page-state
14
14
  *
15
- * @summary Page states display full-page messages for empty states, errors, or loading scenarios.
15
+ * @summary Page states display full-height messages for empty states, errors, or other general information. They can be used as full page content, or within smaller containers and will adapt accordingly.
16
16
  *
17
17
  * @cssproperty --forge-page-state-width - The width of the page state.
18
18
  * @cssproperty --forge-page-state-height - The height of the page state.
@@ -18,6 +18,14 @@ export interface IPaginatorCore {
18
18
  alternative: boolean;
19
19
  rangeLabelCallback: PaginatorRangeLabelBuilder;
20
20
  focus(options?: FocusOptions): void;
21
+ goToFirstPage(): void;
22
+ goToPreviousPage(): void;
23
+ goToNextPage(): void;
24
+ goToLastPage(): void;
25
+ canGoToFirstPage(): boolean;
26
+ canGoToPreviousPage(): boolean;
27
+ canGoToNextPage(): boolean;
28
+ canGoToLastPage(): boolean;
21
29
  }
22
30
  export declare class PaginatorCore {
23
31
  private _adapter;
@@ -41,9 +49,13 @@ export declare class PaginatorCore {
41
49
  initialize(): void;
42
50
  focus(options?: FocusOptions): void;
43
51
  private _attachListeners;
52
+ goToFirstPage(): void;
44
53
  private _onFirstPage;
54
+ goToPreviousPage(): void;
45
55
  private _onPreviousPage;
56
+ goToNextPage(): void;
46
57
  private _onNextPage;
58
+ goToLastPage(): void;
47
59
  private _onLastPage;
48
60
  private _onPageSizeChanged;
49
61
  private _dispatchChangeEvent;
@@ -84,4 +96,8 @@ export declare class PaginatorCore {
84
96
  set alternative(value: boolean);
85
97
  get rangeLabelCallback(): PaginatorRangeLabelBuilder;
86
98
  set rangeLabelCallback(value: PaginatorRangeLabelBuilder);
99
+ canGoToFirstPage(): boolean;
100
+ canGoToPreviousPage(): boolean;
101
+ canGoToNextPage(): boolean;
102
+ canGoToLastPage(): boolean;
87
103
  }
@@ -46,9 +46,7 @@ export class PaginatorCore {
46
46
  this._adapter.attachNextPageListener(this._nextPageListener);
47
47
  this._adapter.attachLastPageListener(this._lastPageListener);
48
48
  }
49
- _onFirstPage(evt) {
50
- evt.stopPropagation();
51
- /* c8 ignore next 3 */
49
+ goToFirstPage() {
52
50
  if (!this._hasFirstPage()) {
53
51
  return;
54
52
  }
@@ -58,9 +56,11 @@ export class PaginatorCore {
58
56
  this._applyPageIndex(firstPage);
59
57
  }
60
58
  }
61
- _onPreviousPage(evt) {
59
+ _onFirstPage(evt) {
62
60
  evt.stopPropagation();
63
- /* c8 ignore next 3 */
61
+ this.goToFirstPage();
62
+ }
63
+ goToPreviousPage() {
64
64
  if (!this._hasPreviousPage()) {
65
65
  return;
66
66
  }
@@ -70,9 +70,11 @@ export class PaginatorCore {
70
70
  this._applyPageIndex(prevPage);
71
71
  }
72
72
  }
73
- _onNextPage(evt) {
73
+ _onPreviousPage(evt) {
74
74
  evt.stopPropagation();
75
- /* c8 ignore next 3 */
75
+ this.goToPreviousPage();
76
+ }
77
+ goToNextPage() {
76
78
  if (!this._hasNextPage()) {
77
79
  return;
78
80
  }
@@ -82,9 +84,11 @@ export class PaginatorCore {
82
84
  this._applyPageIndex(nextPage);
83
85
  }
84
86
  }
85
- _onLastPage(evt) {
87
+ _onNextPage(evt) {
86
88
  evt.stopPropagation();
87
- /* c8 ignore next 3 */
89
+ this.goToNextPage();
90
+ }
91
+ goToLastPage() {
88
92
  if (!this._hasLastPage()) {
89
93
  return;
90
94
  }
@@ -94,6 +98,10 @@ export class PaginatorCore {
94
98
  this._applyPageIndex(lastPage);
95
99
  }
96
100
  }
101
+ _onLastPage(evt) {
102
+ evt.stopPropagation();
103
+ this.goToLastPage();
104
+ }
97
105
  _onPageSizeChanged(evt) {
98
106
  evt.stopPropagation();
99
107
  const pageSize = Number(evt.detail);
@@ -384,4 +392,16 @@ export class PaginatorCore {
384
392
  this._rangeLabelCallback = value;
385
393
  this._updateRangeLabel();
386
394
  }
395
+ canGoToFirstPage() {
396
+ return this._hasFirstPage();
397
+ }
398
+ canGoToPreviousPage() {
399
+ return this._hasPreviousPage();
400
+ }
401
+ canGoToNextPage() {
402
+ return this._hasNextPage();
403
+ }
404
+ canGoToLastPage() {
405
+ return this._hasLastPage();
406
+ }
387
407
  }
@@ -17,6 +17,14 @@ export interface IPaginatorComponent extends IBaseComponent {
17
17
  disabled: boolean;
18
18
  alternative: boolean;
19
19
  rangeLabelCallback: PaginatorRangeLabelBuilder;
20
+ goToFirstPage(): void;
21
+ goToPreviousPage(): void;
22
+ goToNextPage(): void;
23
+ goToLastPage(): void;
24
+ canGoToFirstPage(): boolean;
25
+ canGoToPreviousPage(): boolean;
26
+ canGoToNextPage(): boolean;
27
+ canGoToLastPage(): boolean;
20
28
  }
21
29
  declare global {
22
30
  interface HTMLElementTagNameMap {
@@ -29,7 +37,7 @@ declare global {
29
37
  /**
30
38
  * @tag forge-paginator
31
39
  *
32
- * @summary Paginators provide navigation controls for dividing content across multiple pages.
40
+ * @summary Paginators provide navigation controls for dividing content across multiple pages. Typically used alongside data tables or lists.
33
41
  *
34
42
  * @slot label - Overrides the label text when in the default variant.
35
43
  * @slot range-label - Overrides the default range label with a custom label when in the default variant.
@@ -113,4 +121,32 @@ export declare class PaginatorComponent extends BaseComponent implements IPagina
113
121
  rangeLabelCallback: PaginatorRangeLabelBuilder;
114
122
  /** Sets focus to the first focusable element within the paginator. */
115
123
  focus(options?: FocusOptions): void;
124
+ /** Navigates to the first page. */
125
+ goToFirstPage(): void;
126
+ /** Navigates to the previous page. */
127
+ goToPreviousPage(): void;
128
+ /** Navigates to the next page. */
129
+ goToNextPage(): void;
130
+ /** Navigates to the last page. */
131
+ goToLastPage(): void;
132
+ /**
133
+ * Checks if navigation to the first page is possible.
134
+ * @returns True if can navigate to first page
135
+ */
136
+ canGoToFirstPage(): boolean;
137
+ /**
138
+ * Checks if navigation to the previous page is possible.
139
+ * @returns True if can navigate to previous page
140
+ */
141
+ canGoToPreviousPage(): boolean;
142
+ /**
143
+ * Checks if navigation to the next page is possible.
144
+ * @returns True if can navigate to next page
145
+ */
146
+ canGoToNextPage(): boolean;
147
+ /**
148
+ * Checks if navigation to the last page is possible.
149
+ * @returns True if can navigate to last page
150
+ */
151
+ canGoToLastPage(): boolean;
116
152
  }