calcite-action-menu.cjs.entry.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.97
  5. */
  6. 'use strict';
  7. Object.defineProperty(exports, '__esModule', { value: true });
  8. const index = require('./index-a0010f96.js');
  9. const resources = require('./resources-bd2202e9.js');
  10. const dom = require('./dom-2ec8c9ed.js');
  11. const array = require('./array-e708c699.js');
  12. const guid = require('./guid-f4f03a7a.js');
  13. const key = require('./key-6a462411.js');
  14. require('./resources-b5a5f8a7.js');
  15. const actionMenuCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host{box-sizing:border-box;display:flex;flex-direction:column;background-color:var(--calcite-ui-foreground-1);font-size:var(--calcite-font-size-1);color:var(--calcite-ui-text-2)}.menu ::slotted(calcite-action){margin:0.125rem;display:flex;outline-color:transparent}.menu ::slotted(calcite-action[active]){outline:2px solid var(--calcite-ui-brand);outline-offset:0px}.default-trigger{position:relative;block-size:100%;flex:0 1 auto;align-self:stretch}slot[name=trigger]::slotted(calcite-action),calcite-action::slotted([slot=trigger]){position:relative;block-size:100%;flex:0 1 auto;align-self:stretch}.menu{flex-direction:column;flex-wrap:nowrap;outline:2px solid transparent;outline-offset:2px}";
  16. const SUPPORTED_MENU_NAV_KEYS = ["ArrowUp", "ArrowDown", "End", "Home"];
  17. const ActionMenu = class {
  18. constructor(hostRef) {
  19. index.registerInstance(this, hostRef);
  20. this.calciteActionMenuOpenChange = index.createEvent(this, "calciteActionMenuOpenChange", 6);
  21. // --------------------------------------------------------------------------
  22. //
  23. // Properties
  24. //
  25. // --------------------------------------------------------------------------
  26. /**
  27. * When `true`, the component is expanded.
  28. */
  29. this.expanded = false;
  30. /**
  31. * When `true`, the component is open.
  32. */
  33. this.open = false;
  34. /**
  35. * Determines the type of positioning to use for the overlaid content.
  36. *
  37. * Using `"absolute"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout.
  38. * `"fixed"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `"fixed"`.
  39. *
  40. */
  41. this.overlayPositioning = "absolute";
  42. /**
  43. * Determines where the component will be positioned relative to the `referenceElement`.
  44. *
  45. * @see [LogicalPlacement](https://github.com/Esri/calcite-components/blob/master/src/utils/floating-ui.ts#L25)
  46. */
  47. this.placement = "auto";
  48. this.actionElements = [];
  49. this.guid = `calcite-action-menu-${guid.guid()}`;
  50. this.menuId = `${this.guid}-menu`;
  51. this.menuButtonId = `${this.guid}-menu-button`;
  52. this.activeMenuItemIndex = -1;
  53. // --------------------------------------------------------------------------
  54. //
  55. // Component Methods
  56. //
  57. // --------------------------------------------------------------------------
  58. this.connectMenuButtonEl = () => {
  59. const { menuButtonId, menuId, open, label } = this;
  60. const menuButtonEl = this.slottedMenuButtonEl || this.defaultMenuButtonEl;
  61. if (this.menuButtonEl === menuButtonEl) {
  62. return;
  63. }
  64. this.disconnectMenuButtonEl();
  65. this.menuButtonEl = menuButtonEl;
  66. this.setTooltipReferenceElement();
  67. if (!menuButtonEl) {
  68. return;
  69. }
  70. menuButtonEl.active = open;
  71. menuButtonEl.setAttribute("aria-controls", menuId);
  72. menuButtonEl.setAttribute("aria-expanded", dom.toAriaBoolean(open));
  73. menuButtonEl.setAttribute("aria-haspopup", "true");
  74. if (!menuButtonEl.id) {
  75. menuButtonEl.id = menuButtonId;
  76. }
  77. if (!menuButtonEl.label) {
  78. menuButtonEl.label = label;
  79. }
  80. if (!menuButtonEl.text) {
  81. menuButtonEl.text = label;
  82. }
  83. menuButtonEl.addEventListener("pointerdown", this.menuButtonClick);
  84. menuButtonEl.addEventListener("keydown", this.menuButtonKeyDown);
  85. };
  86. this.disconnectMenuButtonEl = () => {
  87. const { menuButtonEl } = this;
  88. if (!menuButtonEl) {
  89. return;
  90. }
  91. menuButtonEl.removeEventListener("pointerdown", this.menuButtonClick);
  92. menuButtonEl.removeEventListener("keydown", this.menuButtonKeyDown);
  93. };
  94. this.setMenuButtonEl = (event) => {
  95. const actions = event.target
  96. .assignedElements({
  97. flatten: true
  98. })
  99. .filter((el) => el === null || el === void 0 ? void 0 : el.matches("calcite-action"));
  100. this.slottedMenuButtonEl = actions[0];
  101. this.connectMenuButtonEl();
  102. };
  103. this.setDefaultMenuButtonEl = (el) => {
  104. this.defaultMenuButtonEl = el;
  105. this.connectMenuButtonEl();
  106. };
  107. // --------------------------------------------------------------------------
  108. //
  109. // Private Methods
  110. //
  111. // --------------------------------------------------------------------------
  112. this.handleCalciteActionClick = () => {
  113. this.open = false;
  114. this.setFocus();
  115. };
  116. this.menuButtonClick = (event) => {
  117. if (!dom.isPrimaryPointerButton(event)) {
  118. return;
  119. }
  120. this.toggleOpen();
  121. };
  122. this.updateTooltip = (event) => {
  123. const tooltips = event.target
  124. .assignedElements({
  125. flatten: true
  126. })
  127. .filter((el) => el === null || el === void 0 ? void 0 : el.matches("calcite-tooltip"));
  128. this.tooltipEl = tooltips[0];
  129. this.setTooltipReferenceElement();
  130. };
  131. this.setTooltipReferenceElement = () => {
  132. const { tooltipEl, expanded, menuButtonEl, open } = this;
  133. if (tooltipEl) {
  134. tooltipEl.referenceElement = !expanded && !open ? menuButtonEl : null;
  135. }
  136. };
  137. this.updateAction = (action, index) => {
  138. const { guid, activeMenuItemIndex } = this;
  139. const id = `${guid}-action-${index}`;
  140. action.tabIndex = -1;
  141. action.setAttribute("role", "menuitem");
  142. if (!action.id) {
  143. action.id = id;
  144. }
  145. action.active = index === activeMenuItemIndex;
  146. };
  147. this.updateActions = (actions) => {
  148. actions === null || actions === void 0 ? void 0 : actions.forEach(this.updateAction);
  149. };
  150. this.handleDefaultSlotChange = (event) => {
  151. const actions = event.target
  152. .assignedElements({
  153. flatten: true
  154. })
  155. .filter((el) => el === null || el === void 0 ? void 0 : el.matches("calcite-action"));
  156. this.actionElements = actions;
  157. };
  158. this.menuButtonKeyDown = (event) => {
  159. const { key: key$1 } = event;
  160. const { actionElements, activeMenuItemIndex, open } = this;
  161. if (!actionElements.length) {
  162. return;
  163. }
  164. if (key.isActivationKey(key$1)) {
  165. event.preventDefault();
  166. if (!open) {
  167. this.toggleOpen();
  168. return;
  169. }
  170. const action = actionElements[activeMenuItemIndex];
  171. action ? action.click() : this.toggleOpen(false);
  172. }
  173. if (key$1 === "Tab") {
  174. this.open = false;
  175. return;
  176. }
  177. if (key$1 === "Escape") {
  178. this.toggleOpen(false);
  179. event.preventDefault();
  180. return;
  181. }
  182. this.handleActionNavigation(event, key$1, actionElements);
  183. };
  184. this.handleActionNavigation = (event, key, actions) => {
  185. if (!this.isValidKey(key, SUPPORTED_MENU_NAV_KEYS)) {
  186. return;
  187. }
  188. event.preventDefault();
  189. if (!this.open) {
  190. this.toggleOpen();
  191. if (key === "Home" || key === "ArrowDown") {
  192. this.activeMenuItemIndex = 0;
  193. }
  194. if (key === "End" || key === "ArrowUp") {
  195. this.activeMenuItemIndex = actions.length - 1;
  196. }
  197. return;
  198. }
  199. if (key === "Home") {
  200. this.activeMenuItemIndex = 0;
  201. }
  202. if (key === "End") {
  203. this.activeMenuItemIndex = actions.length - 1;
  204. }
  205. const currentIndex = this.activeMenuItemIndex;
  206. if (key === "ArrowUp") {
  207. this.activeMenuItemIndex = array.getRoundRobinIndex(Math.max(currentIndex - 1, -1), actions.length);
  208. }
  209. if (key === "ArrowDown") {
  210. this.activeMenuItemIndex = array.getRoundRobinIndex(currentIndex + 1, actions.length);
  211. }
  212. };
  213. this.toggleOpenEnd = () => {
  214. this.setFocus();
  215. this.el.removeEventListener("calcitePopoverOpen", this.toggleOpenEnd);
  216. };
  217. this.toggleOpen = (value = !this.open) => {
  218. this.el.addEventListener("calcitePopoverOpen", this.toggleOpenEnd);
  219. this.open = value;
  220. };
  221. }
  222. // --------------------------------------------------------------------------
  223. //
  224. // Lifecycle
  225. //
  226. // --------------------------------------------------------------------------
  227. disconnectedCallback() {
  228. this.disconnectMenuButtonEl();
  229. }
  230. expandedHandler() {
  231. this.open = false;
  232. this.setTooltipReferenceElement();
  233. }
  234. openHandler(open) {
  235. this.activeMenuItemIndex = this.open ? 0 : -1;
  236. if (this.menuButtonEl) {
  237. this.menuButtonEl.active = open;
  238. }
  239. this.calciteActionMenuOpenChange.emit(open);
  240. this.setTooltipReferenceElement();
  241. }
  242. closeCalciteActionMenuOnClick(event) {
  243. if (!dom.isPrimaryPointerButton(event)) {
  244. return;
  245. }
  246. const composedPath = event.composedPath();
  247. if (composedPath.includes(this.el)) {
  248. return;
  249. }
  250. this.open = false;
  251. }
  252. activeMenuItemIndexHandler() {
  253. this.updateActions(this.actionElements);
  254. }
  255. // --------------------------------------------------------------------------
  256. //
  257. // Methods
  258. //
  259. // --------------------------------------------------------------------------
  260. /** Sets focus on the component. */
  261. async setFocus() {
  262. dom.focusElement(this.menuButtonEl);
  263. }
  264. renderMenuButton() {
  265. const { label, scale, expanded } = this;
  266. const menuButtonSlot = (index.h("slot", { name: resources.SLOTS.trigger, onSlotchange: this.setMenuButtonEl }, index.h("calcite-action", { class: resources.CSS.defaultTrigger, icon: resources.ICONS.menu, ref: this.setDefaultMenuButtonEl, scale: scale, text: label, textEnabled: expanded })));
  267. return menuButtonSlot;
  268. }
  269. renderMenuItems() {
  270. const { actionElements, activeMenuItemIndex, open, menuId, menuButtonEl, label, placement, overlayPositioning, flipPlacements } = this;
  271. const activeAction = actionElements[activeMenuItemIndex];
  272. const activeDescendantId = (activeAction === null || activeAction === void 0 ? void 0 : activeAction.id) || null;
  273. return (index.h("calcite-popover", { disablePointer: true, flipPlacements: flipPlacements, label: label, offsetDistance: 0, open: open, overlayPositioning: overlayPositioning, placement: placement, referenceElement: menuButtonEl }, index.h("div", { "aria-activedescendant": activeDescendantId, "aria-labelledby": menuButtonEl === null || menuButtonEl === void 0 ? void 0 : menuButtonEl.id, class: resources.CSS.menu, id: menuId, onClick: this.handleCalciteActionClick, role: "menu", tabIndex: -1 }, index.h("slot", { onSlotchange: this.handleDefaultSlotChange }))));
  274. }
  275. render() {
  276. return (index.h(index.Fragment, null, this.renderMenuButton(), this.renderMenuItems(), index.h("slot", { name: resources.SLOTS.tooltip, onSlotchange: this.updateTooltip })));
  277. }
  278. isValidKey(key, supportedKeys) {
  279. return !!supportedKeys.find((k) => k === key);
  280. }
  281. get el() { return index.getElement(this); }
  282. static get watchers() { return {
  283. "expanded": ["expandedHandler"],
  284. "open": ["openHandler"],
  285. "activeMenuItemIndex": ["activeMenuItemIndexHandler"]
  286. }; }
  287. };
  288. ActionMenu.style = actionMenuCss;
  289. exports.calcite_action_menu = ActionMenu;