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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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.82
  5. */
  6. 'use strict';
  7. Object.defineProperty(exports, '__esModule', { value: true });
  8. const index = require('./index-5c65e149.js');
  9. const resources = require('./resources-1a214670.js');
  10. const dom = require('./dom-9ac0341c.js');
  11. const array = require('./array-f1fe66d8.js');
  12. const guid = require('./guid-8b6d6cb4.js');
  13. const observers = require('./observers-d9fdf006.js');
  14. const conditionalSlot = require('./conditionalSlot-ba5cd797.js');
  15. const actionMenuCss = "@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 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;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:var(--calcite-animation-timing);animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{-webkit-animation-name:in;animation-name:in}.calcite-animate__in-down{-webkit-animation-name:in-down;animation-name:in-down}.calcite-animate__in-up{-webkit-animation-name:in-up;animation-name:in-up}.calcite-animate__in-scale{-webkit-animation-name:in-scale;animation-name:in-scale}:root{--calcite-popper-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host{-webkit-box-sizing:border-box;box-sizing:border-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;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:-ms-flexbox;display:flex;outline-offset:0;outline-color:transparent;-webkit-transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out;transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out}::slotted(calcite-action[active]){outline:2px solid var(--calcite-ui-brand);outline-offset:0px}.default-trigger{position:relative;height:100%;-ms-flex:0 1 auto;flex:0 1 auto;-ms-flex-item-align:stretch;align-self:stretch}slot[name=trigger]::slotted(calcite-action),calcite-action::slotted([slot=trigger]){position:relative;height:100%;-ms-flex:0 1 auto;flex:0 1 auto;-ms-flex-item-align:stretch;align-self:stretch}.menu{-ms-flex-direction:column;flex-direction:column;-ms-flex-wrap:nowrap;flex-wrap:nowrap;outline:2px solid transparent;outline-offset:2px}";
  16. const SUPPORTED_BUTTON_NAV_KEYS = ["ArrowUp", "ArrowDown"];
  17. const SUPPORTED_MENU_NAV_KEYS = ["ArrowUp", "ArrowDown", "End", "Home"];
  18. const ActionMenu = class {
  19. constructor(hostRef) {
  20. index.registerInstance(this, hostRef);
  21. this.calciteActionMenuOpenChange = index.createEvent(this, "calciteActionMenuOpenChange", 7);
  22. // --------------------------------------------------------------------------
  23. //
  24. // Properties
  25. //
  26. // --------------------------------------------------------------------------
  27. /**
  28. * Indicates whether widget is expanded.
  29. */
  30. this.expanded = false;
  31. /**
  32. * Opens the action menu.
  33. */
  34. this.open = false;
  35. /** Describes the type of positioning to use for the overlaid content. If your element is in a fixed container, use the 'fixed' value. */
  36. this.overlayPositioning = "absolute";
  37. /**
  38. * Determines where the component will be positioned relative to the referenceElement.
  39. * @see [PopperPlacement](https://github.com/Esri/calcite-components/blob/master/src/utils/popper.ts#L25)
  40. */
  41. this.placement = "auto";
  42. this.actionElements = [];
  43. this.mutationObserver = observers.createObserver("mutation", () => this.getActions());
  44. this.guid = `calcite-action-menu-${guid.guid()}`;
  45. this.menuId = `${this.guid}-menu`;
  46. this.menuButtonId = `${this.guid}-menu-button`;
  47. this.activeMenuItemIndex = -1;
  48. // --------------------------------------------------------------------------
  49. //
  50. // Component Methods
  51. //
  52. // --------------------------------------------------------------------------
  53. this.connectMenuButtonEl = () => {
  54. const { el, menuButtonId, menuId, open, label } = this;
  55. const menuButtonEl = dom.getSlotted(el, resources.SLOTS.trigger) || this.defaultMenuButtonEl;
  56. if (this.menuButtonEl === menuButtonEl) {
  57. return;
  58. }
  59. this.disconnectMenuButtonEl();
  60. this.menuButtonEl = menuButtonEl;
  61. this.setTooltipReferenceElement();
  62. if (!menuButtonEl) {
  63. return;
  64. }
  65. menuButtonEl.active = open;
  66. menuButtonEl.setAttribute("aria-controls", menuId);
  67. menuButtonEl.setAttribute("aria-expanded", dom.toAriaBoolean(open));
  68. menuButtonEl.setAttribute("aria-haspopup", "true");
  69. if (!menuButtonEl.id) {
  70. menuButtonEl.id = menuButtonId;
  71. }
  72. if (!menuButtonEl.label) {
  73. menuButtonEl.label = label;
  74. }
  75. if (!menuButtonEl.text) {
  76. menuButtonEl.text = label;
  77. }
  78. menuButtonEl.addEventListener("click", this.menuButtonClick);
  79. menuButtonEl.addEventListener("keydown", this.menuButtonKeyDown);
  80. menuButtonEl.addEventListener("keyup", this.menuButtonKeyUp);
  81. };
  82. this.disconnectMenuButtonEl = () => {
  83. const { menuButtonEl } = this;
  84. if (!menuButtonEl) {
  85. return;
  86. }
  87. menuButtonEl.removeEventListener("click", this.menuButtonClick);
  88. menuButtonEl.removeEventListener("keydown", this.menuButtonKeyDown);
  89. menuButtonEl.removeEventListener("keyup", this.menuButtonKeyUp);
  90. };
  91. this.setDefaultMenuButtonEl = (el) => {
  92. this.defaultMenuButtonEl = el;
  93. this.connectMenuButtonEl();
  94. };
  95. // --------------------------------------------------------------------------
  96. //
  97. // Private Methods
  98. //
  99. // --------------------------------------------------------------------------
  100. this.handleCalciteActionClick = () => {
  101. this.open = false;
  102. this.setFocus();
  103. };
  104. this.menuButtonClick = () => {
  105. this.toggleOpen();
  106. };
  107. this.updateTooltip = (event) => {
  108. const tooltips = event.target
  109. .assignedElements({
  110. flatten: true
  111. })
  112. .filter((el) => el === null || el === void 0 ? void 0 : el.matches("calcite-tooltip"));
  113. this.tooltipEl = tooltips[0];
  114. this.setTooltipReferenceElement();
  115. };
  116. this.setTooltipReferenceElement = () => {
  117. const { tooltipEl, expanded, menuButtonEl } = this;
  118. if (tooltipEl) {
  119. tooltipEl.referenceElement = !expanded ? menuButtonEl : null;
  120. }
  121. };
  122. this.updateAction = (action, index) => {
  123. const { guid, activeMenuItemIndex } = this;
  124. const id = `${guid}-action-${index}`;
  125. action.tabIndex = -1;
  126. action.setAttribute("role", "menuitem");
  127. if (!action.id) {
  128. action.id = id;
  129. }
  130. action.active = index === activeMenuItemIndex;
  131. };
  132. this.updateActions = (actions) => {
  133. actions === null || actions === void 0 ? void 0 : actions.forEach(this.updateAction);
  134. };
  135. this.getActions = () => {
  136. const { el } = this;
  137. const actionElements = dom.getSlotted(el, { all: true, matches: "calcite-action" });
  138. this.updateActions(actionElements);
  139. this.actionElements = actionElements;
  140. this.connectMenuButtonEl();
  141. };
  142. this.menuButtonKeyUp = (event) => {
  143. const { key } = event;
  144. const { actionElements } = this;
  145. if (!this.isValidKey(key, SUPPORTED_BUTTON_NAV_KEYS)) {
  146. return;
  147. }
  148. event.preventDefault();
  149. if (!actionElements.length) {
  150. return;
  151. }
  152. this.toggleOpen(true);
  153. this.handleActionNavigation(key, actionElements);
  154. };
  155. this.menuButtonKeyDown = (event) => {
  156. const { key } = event;
  157. if (!this.isValidKey(key, SUPPORTED_BUTTON_NAV_KEYS)) {
  158. return;
  159. }
  160. event.preventDefault();
  161. };
  162. this.menuActionsContainerKeyDown = (event) => {
  163. const { key } = event;
  164. const { actionElements, activeMenuItemIndex } = this;
  165. if (key === "Tab") {
  166. this.open = false;
  167. return;
  168. }
  169. if (key === " " || key === "Enter") {
  170. event.preventDefault();
  171. const action = actionElements[activeMenuItemIndex];
  172. action ? action.click() : this.toggleOpen(false);
  173. return;
  174. }
  175. if (this.isValidKey(key, SUPPORTED_MENU_NAV_KEYS)) {
  176. event.preventDefault();
  177. }
  178. };
  179. this.menuActionsContainerKeyUp = (event) => {
  180. const { key } = event;
  181. const { actionElements } = this;
  182. if (key === "Escape") {
  183. this.toggleOpen(false);
  184. return;
  185. }
  186. if (!this.isValidKey(key, SUPPORTED_MENU_NAV_KEYS)) {
  187. return;
  188. }
  189. event.preventDefault();
  190. if (!actionElements.length) {
  191. return;
  192. }
  193. this.handleActionNavigation(key, actionElements);
  194. };
  195. this.handleActionNavigation = (key, actions) => {
  196. const currentIndex = this.activeMenuItemIndex;
  197. if (key === "Home") {
  198. this.activeMenuItemIndex = 0;
  199. }
  200. if (key === "End") {
  201. this.activeMenuItemIndex = actions.length - 1;
  202. }
  203. if (key === "ArrowUp") {
  204. this.activeMenuItemIndex = array.getRoundRobinIndex(Math.max(currentIndex - 1, -1), actions.length);
  205. }
  206. if (key === "ArrowDown") {
  207. this.activeMenuItemIndex = array.getRoundRobinIndex(currentIndex + 1, actions.length);
  208. }
  209. };
  210. this.toggleOpenEnd = () => {
  211. this.setFocus();
  212. this.el.removeEventListener("calcitePopoverOpen", this.toggleOpenEnd);
  213. };
  214. this.toggleOpen = (value = !this.open) => {
  215. this.el.addEventListener("calcitePopoverOpen", this.toggleOpenEnd);
  216. this.open = value;
  217. };
  218. }
  219. // --------------------------------------------------------------------------
  220. //
  221. // Lifecycle
  222. //
  223. // --------------------------------------------------------------------------
  224. connectedCallback() {
  225. var _a;
  226. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.observe(this.el, { childList: true, subtree: true });
  227. this.getActions();
  228. conditionalSlot.connectConditionalSlotComponent(this);
  229. }
  230. disconnectedCallback() {
  231. var _a;
  232. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
  233. this.disconnectMenuButtonEl();
  234. conditionalSlot.disconnectConditionalSlotComponent(this);
  235. }
  236. expandedHandler() {
  237. this.open = false;
  238. this.setTooltipReferenceElement();
  239. }
  240. openHandler(open) {
  241. this.activeMenuItemIndex = this.open ? 0 : -1;
  242. if (this.menuButtonEl) {
  243. this.menuButtonEl.active = open;
  244. }
  245. this.calciteActionMenuOpenChange.emit(open);
  246. }
  247. closeCalciteActionMenuOnClick(event) {
  248. const composedPath = event.composedPath();
  249. if (composedPath.includes(this.el)) {
  250. return;
  251. }
  252. this.open = false;
  253. }
  254. activeMenuItemIndexHandler() {
  255. this.updateActions(this.actionElements);
  256. }
  257. // --------------------------------------------------------------------------
  258. //
  259. // Methods
  260. //
  261. // --------------------------------------------------------------------------
  262. /** Sets focus on the component. */
  263. async setFocus() {
  264. dom.focusElement(this.open ? this.menuEl : this.menuButtonEl);
  265. }
  266. renderMenuButton() {
  267. const { label, scale } = this;
  268. const menuButtonSlot = (index.h("slot", { name: resources.SLOTS.trigger }, index.h("calcite-action", { class: resources.CSS.defaultTrigger, icon: resources.ICONS.menu, ref: this.setDefaultMenuButtonEl, scale: scale, text: label })));
  269. return menuButtonSlot;
  270. }
  271. renderMenuItems() {
  272. const { actionElements, activeMenuItemIndex, open, menuId, menuButtonEl, label, placement, overlayPositioning, flipPlacements } = this;
  273. const activeAction = actionElements[activeMenuItemIndex];
  274. const activeDescendantId = (activeAction === null || activeAction === void 0 ? void 0 : activeAction.id) || null;
  275. 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, onKeyDown: this.menuActionsContainerKeyDown, onKeyUp: this.menuActionsContainerKeyUp, ref: (el) => (this.menuEl = el), role: "menu", tabIndex: -1 }, index.h("slot", null))));
  276. }
  277. render() {
  278. return (index.h(index.Fragment, null, this.renderMenuButton(), this.renderMenuItems(), index.h("slot", { name: resources.SLOTS.tooltip, onSlotchange: this.updateTooltip })));
  279. }
  280. isValidKey(key, supportedKeys) {
  281. return !!supportedKeys.find((k) => k === key);
  282. }
  283. get el() { return index.getElement(this); }
  284. static get watchers() { return {
  285. "expanded": ["expandedHandler"],
  286. "open": ["openHandler"],
  287. "activeMenuItemIndex": ["activeMenuItemIndexHandler"]
  288. }; }
  289. };
  290. ActionMenu.style = actionMenuCss;
  291. exports.calcite_action_menu = ActionMenu;