action-bar.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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. import { Host, h } from "@stencil/core";
  7. import { ExpandToggle, toggleChildActionText } from "../functional/ExpandToggle";
  8. import { CSS, SLOTS, TEXT } from "./resources";
  9. import { getSlotted, focusElement } from "../../utils/dom";
  10. import { geActionDimensions, getOverflowCount, overflowActions, queryActions, overflowActionsDebounceInMs } from "./utils";
  11. import { createObserver } from "../../utils/observers";
  12. import { debounce } from "lodash-es";
  13. import { connectConditionalSlotComponent, disconnectConditionalSlotComponent } from "../../utils/conditionalSlot";
  14. /**
  15. * @slot - A slot for adding `calcite-action`s that will appear at the top of the action bar.
  16. * @slot bottom-actions - A slot for adding `calcite-action`s that will appear at the bottom of the action bar, above the collapse/expand button.
  17. * @slot expand-tooltip - Used to set the tooltip for the expand toggle.
  18. */
  19. export class ActionBar {
  20. constructor() {
  21. // --------------------------------------------------------------------------
  22. //
  23. // Properties
  24. //
  25. // --------------------------------------------------------------------------
  26. /**
  27. * When `true`, the expand-toggling behavior is disabled.
  28. */
  29. this.expandDisabled = false;
  30. /**
  31. * When `true`, the component is expanded.
  32. */
  33. this.expanded = false;
  34. /**
  35. * The layout direction of the actions.
  36. */
  37. this.layout = "vertical";
  38. /**
  39. * Disables automatically overflowing `calcite-action`s that won't fit into menus.
  40. */
  41. this.overflowActionsDisabled = false;
  42. this.mutationObserver = createObserver("mutation", () => {
  43. const { el, expanded } = this;
  44. toggleChildActionText({ parent: el, expanded });
  45. this.conditionallyOverflowActions();
  46. });
  47. this.resizeObserver = createObserver("resize", (entries) => this.resizeHandlerEntries(entries));
  48. // --------------------------------------------------------------------------
  49. //
  50. // Private Methods
  51. //
  52. // --------------------------------------------------------------------------
  53. this.actionMenuOpenChangeHandler = (event) => {
  54. if (event.detail) {
  55. const composedPath = event.composedPath();
  56. Array.from(this.el.querySelectorAll("calcite-action-group")).forEach((group) => {
  57. if (!composedPath.includes(group)) {
  58. group.menuOpen = false;
  59. }
  60. });
  61. }
  62. };
  63. this.resizeHandlerEntries = (entries) => {
  64. entries.forEach(this.resizeHandler);
  65. };
  66. this.resizeHandler = (entry) => {
  67. const { width, height } = entry.contentRect;
  68. this.resize({ width, height });
  69. };
  70. this.resize = debounce(({ width, height }) => {
  71. const { el, expanded, expandDisabled, layout } = this;
  72. if ((layout === "vertical" && !height) || (layout === "horizontal" && !width)) {
  73. return;
  74. }
  75. const actions = queryActions(el);
  76. const actionCount = expandDisabled ? actions.length : actions.length + 1;
  77. const actionGroups = Array.from(el.querySelectorAll("calcite-action-group"));
  78. const groupCount = getSlotted(el, SLOTS.bottomActions) || !expandDisabled
  79. ? actionGroups.length + 1
  80. : actionGroups.length;
  81. const { actionHeight, actionWidth } = geActionDimensions(actions);
  82. const overflowCount = getOverflowCount({
  83. layout,
  84. actionCount,
  85. actionHeight,
  86. actionWidth,
  87. height,
  88. width,
  89. groupCount
  90. });
  91. overflowActions({
  92. actionGroups,
  93. expanded,
  94. overflowCount
  95. });
  96. }, overflowActionsDebounceInMs);
  97. this.conditionallyOverflowActions = () => {
  98. if (!this.overflowActionsDisabled) {
  99. this.overflowActions();
  100. }
  101. };
  102. this.toggleExpand = () => {
  103. this.expanded = !this.expanded;
  104. this.calciteActionBarToggle.emit();
  105. };
  106. this.setExpandToggleRef = (el) => {
  107. this.expandToggleEl = el;
  108. };
  109. }
  110. expandHandler() {
  111. this.conditionallyOverflowActions();
  112. }
  113. expandedHandler(expanded) {
  114. toggleChildActionText({ parent: this.el, expanded });
  115. this.conditionallyOverflowActions();
  116. }
  117. overflowDisabledHandler(overflowActionsDisabled) {
  118. overflowActionsDisabled
  119. ? this.resizeObserver.disconnect()
  120. : this.resizeObserver.observe(this.el);
  121. }
  122. // --------------------------------------------------------------------------
  123. //
  124. // Lifecycle
  125. //
  126. // --------------------------------------------------------------------------
  127. componentDidLoad() {
  128. this.conditionallyOverflowActions();
  129. }
  130. connectedCallback() {
  131. var _a, _b;
  132. const { el, expanded } = this;
  133. toggleChildActionText({ parent: el, expanded });
  134. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.observe(el, { childList: true, subtree: true });
  135. if (!this.overflowActionsDisabled) {
  136. (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.observe(el);
  137. }
  138. this.conditionallyOverflowActions();
  139. connectConditionalSlotComponent(this);
  140. }
  141. disconnectedCallback() {
  142. var _a, _b;
  143. (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
  144. (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
  145. disconnectConditionalSlotComponent(this);
  146. }
  147. // --------------------------------------------------------------------------
  148. //
  149. // Methods
  150. //
  151. // --------------------------------------------------------------------------
  152. /**
  153. * Overflows actions that won't fit into menus.
  154. *
  155. * @internal
  156. */
  157. async overflowActions() {
  158. this.resize({ width: this.el.clientWidth, height: this.el.clientHeight });
  159. }
  160. /**
  161. * Sets focus on the component.
  162. *
  163. * @param focusId
  164. */
  165. async setFocus(focusId) {
  166. var _a;
  167. if (focusId === "expand-toggle") {
  168. await focusElement(this.expandToggleEl);
  169. return;
  170. }
  171. (_a = this.el) === null || _a === void 0 ? void 0 : _a.focus();
  172. }
  173. // --------------------------------------------------------------------------
  174. //
  175. // Render Methods
  176. //
  177. // --------------------------------------------------------------------------
  178. renderBottomActionGroup() {
  179. const { expanded, expandDisabled, intlExpand, intlCollapse, el, position, toggleExpand, scale, layout } = this;
  180. const tooltip = getSlotted(el, SLOTS.expandTooltip);
  181. const expandLabel = intlExpand || TEXT.expand;
  182. const collapseLabel = intlCollapse || TEXT.collapse;
  183. const expandToggleNode = !expandDisabled ? (h(ExpandToggle, { el: el, expanded: expanded, intlCollapse: collapseLabel, intlExpand: expandLabel, position: position, ref: this.setExpandToggleRef, scale: scale, toggle: toggleExpand, tooltip: tooltip })) : null;
  184. return getSlotted(el, SLOTS.bottomActions) || expandToggleNode ? (h("calcite-action-group", { class: CSS.actionGroupBottom, layout: layout, scale: scale }, h("slot", { name: SLOTS.bottomActions }), h("slot", { name: SLOTS.expandTooltip }), expandToggleNode)) : null;
  185. }
  186. render() {
  187. return (h(Host, { onCalciteActionMenuOpenChange: this.actionMenuOpenChangeHandler }, h("slot", null), this.renderBottomActionGroup()));
  188. }
  189. static get is() { return "calcite-action-bar"; }
  190. static get encapsulation() { return "shadow"; }
  191. static get originalStyleUrls() {
  192. return {
  193. "$": ["action-bar.scss"]
  194. };
  195. }
  196. static get styleUrls() {
  197. return {
  198. "$": ["action-bar.css"]
  199. };
  200. }
  201. static get properties() {
  202. return {
  203. "expandDisabled": {
  204. "type": "boolean",
  205. "mutable": false,
  206. "complexType": {
  207. "original": "boolean",
  208. "resolved": "boolean",
  209. "references": {}
  210. },
  211. "required": false,
  212. "optional": false,
  213. "docs": {
  214. "tags": [],
  215. "text": "When `true`, the expand-toggling behavior is disabled."
  216. },
  217. "attribute": "expand-disabled",
  218. "reflect": true,
  219. "defaultValue": "false"
  220. },
  221. "expanded": {
  222. "type": "boolean",
  223. "mutable": true,
  224. "complexType": {
  225. "original": "boolean",
  226. "resolved": "boolean",
  227. "references": {}
  228. },
  229. "required": false,
  230. "optional": false,
  231. "docs": {
  232. "tags": [],
  233. "text": "When `true`, the component is expanded."
  234. },
  235. "attribute": "expanded",
  236. "reflect": true,
  237. "defaultValue": "false"
  238. },
  239. "intlExpand": {
  240. "type": "string",
  241. "mutable": false,
  242. "complexType": {
  243. "original": "string",
  244. "resolved": "string",
  245. "references": {}
  246. },
  247. "required": false,
  248. "optional": true,
  249. "docs": {
  250. "tags": [],
  251. "text": "Specifies the label of the expand icon when the component is collapsed."
  252. },
  253. "attribute": "intl-expand",
  254. "reflect": false
  255. },
  256. "intlCollapse": {
  257. "type": "string",
  258. "mutable": false,
  259. "complexType": {
  260. "original": "string",
  261. "resolved": "string",
  262. "references": {}
  263. },
  264. "required": false,
  265. "optional": true,
  266. "docs": {
  267. "tags": [],
  268. "text": "Specifies the label of the collapse icon when the component is expanded."
  269. },
  270. "attribute": "intl-collapse",
  271. "reflect": false
  272. },
  273. "layout": {
  274. "type": "string",
  275. "mutable": false,
  276. "complexType": {
  277. "original": "Extract<\"horizontal\" | \"vertical\", Layout>",
  278. "resolved": "\"horizontal\" | \"vertical\"",
  279. "references": {
  280. "Extract": {
  281. "location": "global"
  282. },
  283. "Layout": {
  284. "location": "import",
  285. "path": "../interfaces"
  286. }
  287. }
  288. },
  289. "required": false,
  290. "optional": false,
  291. "docs": {
  292. "tags": [],
  293. "text": "The layout direction of the actions."
  294. },
  295. "attribute": "layout",
  296. "reflect": true,
  297. "defaultValue": "\"vertical\""
  298. },
  299. "overflowActionsDisabled": {
  300. "type": "boolean",
  301. "mutable": false,
  302. "complexType": {
  303. "original": "boolean",
  304. "resolved": "boolean",
  305. "references": {}
  306. },
  307. "required": false,
  308. "optional": false,
  309. "docs": {
  310. "tags": [],
  311. "text": "Disables automatically overflowing `calcite-action`s that won't fit into menus."
  312. },
  313. "attribute": "overflow-actions-disabled",
  314. "reflect": true,
  315. "defaultValue": "false"
  316. },
  317. "position": {
  318. "type": "string",
  319. "mutable": false,
  320. "complexType": {
  321. "original": "Position",
  322. "resolved": "\"end\" | \"start\"",
  323. "references": {
  324. "Position": {
  325. "location": "import",
  326. "path": "../interfaces"
  327. }
  328. }
  329. },
  330. "required": false,
  331. "optional": false,
  332. "docs": {
  333. "tags": [],
  334. "text": "Arranges the component depending on the element's `dir` property."
  335. },
  336. "attribute": "position",
  337. "reflect": true
  338. },
  339. "scale": {
  340. "type": "string",
  341. "mutable": false,
  342. "complexType": {
  343. "original": "Scale",
  344. "resolved": "\"l\" | \"m\" | \"s\"",
  345. "references": {
  346. "Scale": {
  347. "location": "import",
  348. "path": "../interfaces"
  349. }
  350. }
  351. },
  352. "required": false,
  353. "optional": false,
  354. "docs": {
  355. "tags": [],
  356. "text": "Specifies the size of the expand `calcite-action`."
  357. },
  358. "attribute": "scale",
  359. "reflect": true
  360. }
  361. };
  362. }
  363. static get events() {
  364. return [{
  365. "method": "calciteActionBarToggle",
  366. "name": "calciteActionBarToggle",
  367. "bubbles": true,
  368. "cancelable": false,
  369. "composed": true,
  370. "docs": {
  371. "tags": [],
  372. "text": "Emits when the `expanded` property is toggled."
  373. },
  374. "complexType": {
  375. "original": "void",
  376. "resolved": "void",
  377. "references": {}
  378. }
  379. }];
  380. }
  381. static get methods() {
  382. return {
  383. "overflowActions": {
  384. "complexType": {
  385. "signature": "() => Promise<void>",
  386. "parameters": [],
  387. "references": {
  388. "Promise": {
  389. "location": "global"
  390. }
  391. },
  392. "return": "Promise<void>"
  393. },
  394. "docs": {
  395. "text": "Overflows actions that won't fit into menus.",
  396. "tags": [{
  397. "name": "internal",
  398. "text": undefined
  399. }]
  400. }
  401. },
  402. "setFocus": {
  403. "complexType": {
  404. "signature": "(focusId?: \"expand-toggle\") => Promise<void>",
  405. "parameters": [{
  406. "tags": [{
  407. "name": "param",
  408. "text": "focusId"
  409. }],
  410. "text": ""
  411. }],
  412. "references": {
  413. "Promise": {
  414. "location": "global"
  415. }
  416. },
  417. "return": "Promise<void>"
  418. },
  419. "docs": {
  420. "text": "Sets focus on the component.",
  421. "tags": [{
  422. "name": "param",
  423. "text": "focusId"
  424. }]
  425. }
  426. }
  427. };
  428. }
  429. static get elementRef() { return "el"; }
  430. static get watchers() {
  431. return [{
  432. "propName": "expandDisabled",
  433. "methodName": "expandHandler"
  434. }, {
  435. "propName": "expanded",
  436. "methodName": "expandedHandler"
  437. }, {
  438. "propName": "overflowActionsDisabled",
  439. "methodName": "overflowDisabledHandler"
  440. }];
  441. }
  442. }