block.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. import { Component, Element, Event, h, Host, Prop } from "@stencil/core";
  7. import { CSS, HEADING_LEVEL, ICONS, SLOTS, TEXT } from "./resources";
  8. import { getSlotted, toAriaBoolean } from "../../utils/dom";
  9. import { Heading } from "../functional/Heading";
  10. import { connectConditionalSlotComponent, disconnectConditionalSlotComponent } from "../../utils/conditionalSlot";
  11. import { updateHostInteraction } from "../../utils/interactive";
  12. import { guid } from "../../utils/guid";
  13. /**
  14. * @slot - A slot for adding content to the block.
  15. * @slot icon - A slot for adding a leading header icon.
  16. * @slot control - A slot for adding a single HTML input element in a header.
  17. * @slot header-menu-actions - a slot for adding an overflow menu with actions inside a dropdown.
  18. */
  19. export class Block {
  20. constructor() {
  21. // --------------------------------------------------------------------------
  22. //
  23. // Properties
  24. //
  25. // --------------------------------------------------------------------------
  26. /**
  27. * When true, this block will be collapsible.
  28. */
  29. this.collapsible = false;
  30. /**
  31. * When true, disabled prevents interaction. This state shows items with lower opacity/grayed.
  32. */
  33. this.disabled = false;
  34. /**
  35. * When true, displays a drag handle in the header.
  36. */
  37. this.dragHandle = false;
  38. /**
  39. * Aria-label for collapsing the toggle and tooltip used for the toggle when expanded.
  40. */
  41. this.intlCollapse = TEXT.collapse;
  42. /**
  43. * Aria-label for expanding the toggle and tooltip used for the toggle when collapsed.
  44. */
  45. this.intlExpand = TEXT.expand;
  46. /** string to override English loading text
  47. * @default "Loading"
  48. */
  49. this.intlLoading = TEXT.loading;
  50. /** Text string used for the actions menu
  51. * @default "Options"
  52. */
  53. this.intlOptions = TEXT.options;
  54. /**
  55. * When true, content is waiting to be loaded. This state shows a busy indicator.
  56. */
  57. this.loading = false;
  58. /**
  59. * When true, the block's content will be displayed.
  60. */
  61. this.open = false;
  62. this.guid = guid();
  63. // --------------------------------------------------------------------------
  64. //
  65. // Private Methods
  66. //
  67. // --------------------------------------------------------------------------
  68. this.onHeaderClick = () => {
  69. this.open = !this.open;
  70. this.calciteBlockToggle.emit();
  71. };
  72. }
  73. //--------------------------------------------------------------------------
  74. //
  75. // Lifecycle
  76. //
  77. //--------------------------------------------------------------------------
  78. componentDidRender() {
  79. updateHostInteraction(this);
  80. }
  81. // --------------------------------------------------------------------------
  82. //
  83. // Lifecycle
  84. //
  85. // --------------------------------------------------------------------------
  86. connectedCallback() {
  87. connectConditionalSlotComponent(this);
  88. }
  89. disconnectedCallback() {
  90. disconnectConditionalSlotComponent(this);
  91. }
  92. // --------------------------------------------------------------------------
  93. //
  94. // Render Methods
  95. //
  96. // --------------------------------------------------------------------------
  97. renderScrim() {
  98. const { loading } = this;
  99. const defaultSlot = h("slot", null);
  100. return [loading ? h("calcite-scrim", { loading: loading }) : null, defaultSlot];
  101. }
  102. renderIcon() {
  103. const { el, status } = this;
  104. const showingLoadingStatus = this.loading && !this.open;
  105. const statusIcon = showingLoadingStatus ? ICONS.refresh : ICONS[status];
  106. const hasIcon = getSlotted(el, SLOTS.icon) || statusIcon;
  107. const iconEl = !statusIcon ? (h("slot", { key: "icon-slot", name: SLOTS.icon })) : (h("calcite-icon", { class: {
  108. [CSS.statusIcon]: true,
  109. [CSS.valid]: status == "valid",
  110. [CSS.invalid]: status == "invalid",
  111. [CSS.loading]: showingLoadingStatus
  112. }, icon: statusIcon, scale: "m" }));
  113. return hasIcon ? h("div", { class: CSS.icon }, iconEl) : null;
  114. }
  115. renderTitle() {
  116. const { heading, headingLevel, summary } = this;
  117. return heading || summary ? (h("div", { class: CSS.title },
  118. h(Heading, { class: CSS.heading, level: headingLevel || HEADING_LEVEL }, heading),
  119. summary ? h("div", { class: CSS.summary }, summary) : null)) : null;
  120. }
  121. render() {
  122. const { collapsible, el, intlCollapse, intlExpand, loading, open, intlLoading } = this;
  123. const toggleLabel = open ? intlCollapse || TEXT.collapse : intlExpand || TEXT.expand;
  124. const headerContent = (h("header", { class: CSS.header },
  125. this.renderIcon(),
  126. this.renderTitle()));
  127. const hasControl = !!getSlotted(el, SLOTS.control);
  128. const hasMenuActions = !!getSlotted(el, SLOTS.headerMenuActions);
  129. const collapseIcon = open ? ICONS.opened : ICONS.closed;
  130. const { guid } = this;
  131. const regionId = `${guid}-region`;
  132. const buttonId = `${guid}-button`;
  133. const headerNode = (h("div", { class: CSS.headerContainer },
  134. this.dragHandle ? h("calcite-handle", null) : null,
  135. collapsible ? (h("button", { "aria-controls": regionId, "aria-expanded": collapsible ? toAriaBoolean(open) : null, "aria-label": toggleLabel, class: CSS.toggle, id: buttonId, onClick: this.onHeaderClick, title: toggleLabel },
  136. headerContent,
  137. !hasControl && !hasMenuActions ? (h("calcite-icon", { "aria-hidden": "true", class: CSS.toggleIcon, icon: collapseIcon, scale: "s" })) : null)) : (headerContent),
  138. loading ? (h("calcite-loader", { inline: true, "is-active": true, label: intlLoading })) : hasControl ? (h("div", { class: CSS.controlContainer },
  139. h("slot", { name: SLOTS.control }))) : null,
  140. hasMenuActions ? (h("calcite-action-menu", { label: this.intlOptions || TEXT.options },
  141. h("slot", { name: SLOTS.headerMenuActions }))) : null));
  142. return (h(Host, null,
  143. h("article", { "aria-busy": toAriaBoolean(loading), class: {
  144. [CSS.article]: true
  145. } },
  146. headerNode,
  147. h("section", { "aria-expanded": toAriaBoolean(open), "aria-labelledby": buttonId, class: CSS.content, hidden: !open, id: regionId }, this.renderScrim()))));
  148. }
  149. static get is() { return "calcite-block"; }
  150. static get encapsulation() { return "shadow"; }
  151. static get originalStyleUrls() { return {
  152. "$": ["block.scss"]
  153. }; }
  154. static get styleUrls() { return {
  155. "$": ["block.css"]
  156. }; }
  157. static get properties() { return {
  158. "collapsible": {
  159. "type": "boolean",
  160. "mutable": false,
  161. "complexType": {
  162. "original": "boolean",
  163. "resolved": "boolean",
  164. "references": {}
  165. },
  166. "required": false,
  167. "optional": false,
  168. "docs": {
  169. "tags": [],
  170. "text": "When true, this block will be collapsible."
  171. },
  172. "attribute": "collapsible",
  173. "reflect": false,
  174. "defaultValue": "false"
  175. },
  176. "disabled": {
  177. "type": "boolean",
  178. "mutable": false,
  179. "complexType": {
  180. "original": "boolean",
  181. "resolved": "boolean",
  182. "references": {}
  183. },
  184. "required": false,
  185. "optional": false,
  186. "docs": {
  187. "tags": [],
  188. "text": "When true, disabled prevents interaction. This state shows items with lower opacity/grayed."
  189. },
  190. "attribute": "disabled",
  191. "reflect": true,
  192. "defaultValue": "false"
  193. },
  194. "dragHandle": {
  195. "type": "boolean",
  196. "mutable": false,
  197. "complexType": {
  198. "original": "boolean",
  199. "resolved": "boolean",
  200. "references": {}
  201. },
  202. "required": false,
  203. "optional": false,
  204. "docs": {
  205. "tags": [],
  206. "text": "When true, displays a drag handle in the header."
  207. },
  208. "attribute": "drag-handle",
  209. "reflect": true,
  210. "defaultValue": "false"
  211. },
  212. "heading": {
  213. "type": "string",
  214. "mutable": false,
  215. "complexType": {
  216. "original": "string",
  217. "resolved": "string",
  218. "references": {}
  219. },
  220. "required": true,
  221. "optional": false,
  222. "docs": {
  223. "tags": [],
  224. "text": "Block heading."
  225. },
  226. "attribute": "heading",
  227. "reflect": false
  228. },
  229. "headingLevel": {
  230. "type": "number",
  231. "mutable": false,
  232. "complexType": {
  233. "original": "HeadingLevel",
  234. "resolved": "1 | 2 | 3 | 4 | 5 | 6",
  235. "references": {
  236. "HeadingLevel": {
  237. "location": "import",
  238. "path": "../functional/Heading"
  239. }
  240. }
  241. },
  242. "required": false,
  243. "optional": false,
  244. "docs": {
  245. "tags": [],
  246. "text": "Number at which section headings should start for this component."
  247. },
  248. "attribute": "heading-level",
  249. "reflect": false
  250. },
  251. "intlCollapse": {
  252. "type": "string",
  253. "mutable": false,
  254. "complexType": {
  255. "original": "string",
  256. "resolved": "string",
  257. "references": {}
  258. },
  259. "required": false,
  260. "optional": true,
  261. "docs": {
  262. "tags": [],
  263. "text": "Aria-label for collapsing the toggle and tooltip used for the toggle when expanded."
  264. },
  265. "attribute": "intl-collapse",
  266. "reflect": false,
  267. "defaultValue": "TEXT.collapse"
  268. },
  269. "intlExpand": {
  270. "type": "string",
  271. "mutable": false,
  272. "complexType": {
  273. "original": "string",
  274. "resolved": "string",
  275. "references": {}
  276. },
  277. "required": false,
  278. "optional": true,
  279. "docs": {
  280. "tags": [],
  281. "text": "Aria-label for expanding the toggle and tooltip used for the toggle when collapsed."
  282. },
  283. "attribute": "intl-expand",
  284. "reflect": false,
  285. "defaultValue": "TEXT.expand"
  286. },
  287. "intlLoading": {
  288. "type": "string",
  289. "mutable": false,
  290. "complexType": {
  291. "original": "string",
  292. "resolved": "string",
  293. "references": {}
  294. },
  295. "required": false,
  296. "optional": true,
  297. "docs": {
  298. "tags": [{
  299. "name": "default",
  300. "text": "\"Loading\""
  301. }],
  302. "text": "string to override English loading text"
  303. },
  304. "attribute": "intl-loading",
  305. "reflect": false,
  306. "defaultValue": "TEXT.loading"
  307. },
  308. "intlOptions": {
  309. "type": "string",
  310. "mutable": false,
  311. "complexType": {
  312. "original": "string",
  313. "resolved": "string",
  314. "references": {}
  315. },
  316. "required": false,
  317. "optional": true,
  318. "docs": {
  319. "tags": [{
  320. "name": "default",
  321. "text": "\"Options\""
  322. }],
  323. "text": "Text string used for the actions menu"
  324. },
  325. "attribute": "intl-options",
  326. "reflect": false,
  327. "defaultValue": "TEXT.options"
  328. },
  329. "loading": {
  330. "type": "boolean",
  331. "mutable": false,
  332. "complexType": {
  333. "original": "boolean",
  334. "resolved": "boolean",
  335. "references": {}
  336. },
  337. "required": false,
  338. "optional": false,
  339. "docs": {
  340. "tags": [],
  341. "text": "When true, content is waiting to be loaded. This state shows a busy indicator."
  342. },
  343. "attribute": "loading",
  344. "reflect": true,
  345. "defaultValue": "false"
  346. },
  347. "open": {
  348. "type": "boolean",
  349. "mutable": true,
  350. "complexType": {
  351. "original": "boolean",
  352. "resolved": "boolean",
  353. "references": {}
  354. },
  355. "required": false,
  356. "optional": false,
  357. "docs": {
  358. "tags": [],
  359. "text": "When true, the block's content will be displayed."
  360. },
  361. "attribute": "open",
  362. "reflect": true,
  363. "defaultValue": "false"
  364. },
  365. "status": {
  366. "type": "string",
  367. "mutable": false,
  368. "complexType": {
  369. "original": "Status",
  370. "resolved": "\"idle\" | \"invalid\" | \"valid\"",
  371. "references": {
  372. "Status": {
  373. "location": "import",
  374. "path": "../interfaces"
  375. }
  376. }
  377. },
  378. "required": false,
  379. "optional": true,
  380. "docs": {
  381. "tags": [],
  382. "text": "Block status. Updates or adds icon to show related icon and color."
  383. },
  384. "attribute": "status",
  385. "reflect": true
  386. },
  387. "summary": {
  388. "type": "string",
  389. "mutable": false,
  390. "complexType": {
  391. "original": "string",
  392. "resolved": "string",
  393. "references": {}
  394. },
  395. "required": false,
  396. "optional": false,
  397. "docs": {
  398. "tags": [],
  399. "text": "Block summary."
  400. },
  401. "attribute": "summary",
  402. "reflect": false
  403. }
  404. }; }
  405. static get events() { return [{
  406. "method": "calciteBlockToggle",
  407. "name": "calciteBlockToggle",
  408. "bubbles": true,
  409. "cancelable": true,
  410. "composed": true,
  411. "docs": {
  412. "tags": [],
  413. "text": "Emitted when the header has been clicked."
  414. },
  415. "complexType": {
  416. "original": "any",
  417. "resolved": "any",
  418. "references": {}
  419. }
  420. }]; }
  421. static get elementRef() { return "el"; }
  422. }