alert.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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, Listen, Method, Prop, State, Watch } from "@stencil/core";
  7. import { getSlotted, setRequestedIcon, toAriaBoolean } from "../../utils/dom";
  8. import { DURATIONS, SLOTS, TEXT } from "./resources";
  9. import { StatusIcons } from "./interfaces";
  10. /** Alerts are meant to provide a way to communicate urgent or important information to users, frequently as a result of an action they took in your app. Alerts are positioned
  11. * at the bottom of the page. Multiple opened alerts will be added to a queue, allowing users to dismiss them in the order they are provided.
  12. */
  13. /**
  14. * @slot title - Title of the alert (optional)
  15. * @slot message - Main text of the alert
  16. * @slot link - Optional action to take from the alert (undo, try again, link to page, etc.)
  17. */
  18. export class Alert {
  19. constructor() {
  20. //--------------------------------------------------------------------------
  21. //
  22. // Properties
  23. //
  24. //---------------------------------------------------------------------------
  25. /** Is the alert currently active or not */
  26. this.active = false;
  27. /** Close the alert automatically (recommended for passive, non-blocking alerts) */
  28. this.autoDismiss = false;
  29. /** Duration of autoDismiss (only used with `autoDismiss`) */
  30. this.autoDismissDuration = this.autoDismiss ? "medium" : null;
  31. /** Color for the alert (will apply to top border and icon) */
  32. this.color = "blue";
  33. /** string to override English close text
  34. * @default "Close"
  35. */
  36. this.intlClose = TEXT.intlClose;
  37. /** specify the placement of the alert */
  38. this.placement = "bottom";
  39. /** specify the scale of the alert, defaults to m */
  40. this.scale = "m";
  41. //--------------------------------------------------------------------------
  42. //
  43. // Private State/Props
  44. //
  45. //--------------------------------------------------------------------------
  46. /** the list of queued alerts */
  47. this.queue = [];
  48. /** the count of queued alerts */
  49. this.queueLength = 0;
  50. /** is the alert queued */
  51. this.queued = false;
  52. this.autoDismissTimeoutId = null;
  53. this.trackTimer = Date.now();
  54. this.activeTransitionProp = "opacity";
  55. /** close and emit the closed alert and the queue */
  56. this.closeAlert = () => {
  57. this.autoDismissTimeoutId = null;
  58. this.queued = false;
  59. this.active = false;
  60. this.queue = this.queue.filter((e) => e !== this.el);
  61. this.determineActiveAlert();
  62. this.calciteAlertSync.emit({ queue: this.queue });
  63. };
  64. this.transitionEnd = (event) => {
  65. if (event.propertyName === this.activeTransitionProp) {
  66. this.active
  67. ? this.calciteAlertOpen.emit({
  68. el: this.el,
  69. queue: this.queue
  70. })
  71. : this.calciteAlertClose.emit({
  72. el: this.el,
  73. queue: this.queue
  74. });
  75. }
  76. };
  77. }
  78. watchActive() {
  79. if (this.active && !this.queued) {
  80. this.calciteAlertRegister.emit();
  81. }
  82. if (!this.active) {
  83. this.queue = this.queue.filter((e) => e !== this.el);
  84. this.calciteAlertSync.emit({ queue: this.queue });
  85. }
  86. }
  87. updateRequestedIcon() {
  88. this.requestedIcon = setRequestedIcon(StatusIcons, this.icon, this.color);
  89. }
  90. updateDuration() {
  91. if (this.autoDismiss && this.autoDismissTimeoutId) {
  92. window.clearTimeout(this.autoDismissTimeoutId);
  93. this.autoDismissTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoDismissDuration] - (Date.now() - this.trackTimer));
  94. }
  95. }
  96. //--------------------------------------------------------------------------
  97. //
  98. // Lifecycle
  99. //
  100. //--------------------------------------------------------------------------
  101. connectedCallback() {
  102. if (this.active && !this.queued) {
  103. this.calciteAlertRegister.emit();
  104. }
  105. }
  106. componentWillLoad() {
  107. this.requestedIcon = setRequestedIcon(StatusIcons, this.icon, this.color);
  108. }
  109. disconnectedCallback() {
  110. window.clearTimeout(this.autoDismissTimeoutId);
  111. }
  112. render() {
  113. const closeButton = (h("button", { "aria-label": this.intlClose, class: "alert-close", onClick: this.closeAlert, ref: (el) => (this.closeButton = el), type: "button" },
  114. h("calcite-icon", { icon: "x", scale: this.scale === "l" ? "m" : "s" })));
  115. const queueText = `+${this.queueLength > 2 ? this.queueLength - 1 : 1}`;
  116. const queueCount = (h("div", { class: `${this.queueLength > 1 ? "active " : ""}alert-queue-count` },
  117. h("calcite-chip", { scale: this.scale, value: queueText }, queueText)));
  118. const { active, autoDismiss, label, placement, queued, requestedIcon } = this;
  119. const role = autoDismiss ? "alert" : "alertdialog";
  120. const hidden = !active;
  121. return (h(Host, { "aria-hidden": toAriaBoolean(hidden), "aria-label": label, "calcite-hydrated-hidden": hidden, role: role },
  122. h("div", { class: {
  123. container: true,
  124. queued,
  125. [placement]: true
  126. }, onTransitionEnd: this.transitionEnd },
  127. requestedIcon ? (h("div", { class: "alert-icon" },
  128. h("calcite-icon", { icon: requestedIcon, scale: this.scale === "l" ? "m" : "s" }))) : null,
  129. h("div", { class: "alert-content" },
  130. h("slot", { name: SLOTS.title }),
  131. h("slot", { name: SLOTS.message }),
  132. h("slot", { name: SLOTS.link })),
  133. queueCount,
  134. !autoDismiss ? closeButton : null,
  135. active && !queued && autoDismiss ? h("div", { class: "alert-dismiss-progress" }) : null)));
  136. }
  137. // when an alert is opened or closed, update queue and determine active alert
  138. alertSync(event) {
  139. if (this.queue !== event.detail.queue) {
  140. this.queue = event.detail.queue;
  141. }
  142. this.queueLength = this.queue.length;
  143. this.determineActiveAlert();
  144. }
  145. // when an alert is first registered, trigger a queue sync to get queue
  146. alertRegister() {
  147. if (this.active && !this.queue.includes(this.el)) {
  148. this.queued = true;
  149. this.queue.push(this.el);
  150. }
  151. this.calciteAlertSync.emit({ queue: this.queue });
  152. this.determineActiveAlert();
  153. }
  154. //--------------------------------------------------------------------------
  155. //
  156. // Public Methods
  157. //
  158. //--------------------------------------------------------------------------
  159. /** Sets focus on the component. */
  160. async setFocus() {
  161. const alertLinkEl = getSlotted(this.el, { selector: "calcite-link" });
  162. if (!this.closeButton && !alertLinkEl) {
  163. return;
  164. }
  165. else if (alertLinkEl) {
  166. alertLinkEl.setFocus();
  167. }
  168. else if (this.closeButton) {
  169. this.closeButton.focus();
  170. }
  171. }
  172. //--------------------------------------------------------------------------
  173. //
  174. // Private Methods
  175. //
  176. //--------------------------------------------------------------------------
  177. /** determine which alert is active */
  178. determineActiveAlert() {
  179. var _a;
  180. if (((_a = this.queue) === null || _a === void 0 ? void 0 : _a[0]) === this.el) {
  181. this.openAlert();
  182. if (this.autoDismiss && !this.autoDismissTimeoutId) {
  183. this.trackTimer = Date.now();
  184. this.autoDismissTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoDismissDuration]);
  185. }
  186. }
  187. else {
  188. return;
  189. }
  190. }
  191. /** emit the opened alert and the queue */
  192. openAlert() {
  193. window.clearTimeout(this.queueTimeout);
  194. this.queueTimeout = window.setTimeout(() => (this.queued = false), 300);
  195. }
  196. static get is() { return "calcite-alert"; }
  197. static get encapsulation() { return "shadow"; }
  198. static get originalStyleUrls() { return {
  199. "$": ["alert.scss"]
  200. }; }
  201. static get styleUrls() { return {
  202. "$": ["alert.css"]
  203. }; }
  204. static get properties() { return {
  205. "active": {
  206. "type": "boolean",
  207. "mutable": true,
  208. "complexType": {
  209. "original": "boolean",
  210. "resolved": "boolean",
  211. "references": {}
  212. },
  213. "required": false,
  214. "optional": false,
  215. "docs": {
  216. "tags": [],
  217. "text": "Is the alert currently active or not"
  218. },
  219. "attribute": "active",
  220. "reflect": true,
  221. "defaultValue": "false"
  222. },
  223. "autoDismiss": {
  224. "type": "boolean",
  225. "mutable": false,
  226. "complexType": {
  227. "original": "boolean",
  228. "resolved": "boolean",
  229. "references": {}
  230. },
  231. "required": false,
  232. "optional": false,
  233. "docs": {
  234. "tags": [],
  235. "text": "Close the alert automatically (recommended for passive, non-blocking alerts)"
  236. },
  237. "attribute": "auto-dismiss",
  238. "reflect": false,
  239. "defaultValue": "false"
  240. },
  241. "autoDismissDuration": {
  242. "type": "string",
  243. "mutable": false,
  244. "complexType": {
  245. "original": "AlertDuration",
  246. "resolved": "\"fast\" | \"medium\" | \"slow\"",
  247. "references": {
  248. "AlertDuration": {
  249. "location": "import",
  250. "path": "./interfaces"
  251. }
  252. }
  253. },
  254. "required": false,
  255. "optional": false,
  256. "docs": {
  257. "tags": [],
  258. "text": "Duration of autoDismiss (only used with `autoDismiss`)"
  259. },
  260. "attribute": "auto-dismiss-duration",
  261. "reflect": true,
  262. "defaultValue": "this.autoDismiss ? \"medium\" : null"
  263. },
  264. "color": {
  265. "type": "string",
  266. "mutable": false,
  267. "complexType": {
  268. "original": "StatusColor",
  269. "resolved": "\"blue\" | \"green\" | \"red\" | \"yellow\"",
  270. "references": {
  271. "StatusColor": {
  272. "location": "import",
  273. "path": "./interfaces"
  274. }
  275. }
  276. },
  277. "required": false,
  278. "optional": false,
  279. "docs": {
  280. "tags": [],
  281. "text": "Color for the alert (will apply to top border and icon)"
  282. },
  283. "attribute": "color",
  284. "reflect": true,
  285. "defaultValue": "\"blue\""
  286. },
  287. "icon": {
  288. "type": "any",
  289. "mutable": false,
  290. "complexType": {
  291. "original": "string | boolean",
  292. "resolved": "boolean | string",
  293. "references": {}
  294. },
  295. "required": false,
  296. "optional": false,
  297. "docs": {
  298. "tags": [],
  299. "text": "when used as a boolean set to true, show a default recommended icon. You can\nalso pass a calcite-ui-icon name to this prop to display a requested icon"
  300. },
  301. "attribute": "icon",
  302. "reflect": true
  303. },
  304. "intlClose": {
  305. "type": "string",
  306. "mutable": false,
  307. "complexType": {
  308. "original": "string",
  309. "resolved": "string",
  310. "references": {}
  311. },
  312. "required": false,
  313. "optional": false,
  314. "docs": {
  315. "tags": [{
  316. "name": "default",
  317. "text": "\"Close\""
  318. }],
  319. "text": "string to override English close text"
  320. },
  321. "attribute": "intl-close",
  322. "reflect": false,
  323. "defaultValue": "TEXT.intlClose"
  324. },
  325. "label": {
  326. "type": "string",
  327. "mutable": false,
  328. "complexType": {
  329. "original": "string",
  330. "resolved": "string",
  331. "references": {}
  332. },
  333. "required": true,
  334. "optional": false,
  335. "docs": {
  336. "tags": [],
  337. "text": "Accessible name for the component"
  338. },
  339. "attribute": "label",
  340. "reflect": false
  341. },
  342. "placement": {
  343. "type": "string",
  344. "mutable": false,
  345. "complexType": {
  346. "original": "AlertPlacement",
  347. "resolved": "\"bottom\" | \"bottom-end\" | \"bottom-start\" | \"top\" | \"top-end\" | \"top-start\"",
  348. "references": {
  349. "AlertPlacement": {
  350. "location": "import",
  351. "path": "./interfaces"
  352. }
  353. }
  354. },
  355. "required": false,
  356. "optional": false,
  357. "docs": {
  358. "tags": [],
  359. "text": "specify the placement of the alert"
  360. },
  361. "attribute": "placement",
  362. "reflect": false,
  363. "defaultValue": "\"bottom\""
  364. },
  365. "scale": {
  366. "type": "string",
  367. "mutable": false,
  368. "complexType": {
  369. "original": "Scale",
  370. "resolved": "\"l\" | \"m\" | \"s\"",
  371. "references": {
  372. "Scale": {
  373. "location": "import",
  374. "path": "../interfaces"
  375. }
  376. }
  377. },
  378. "required": false,
  379. "optional": false,
  380. "docs": {
  381. "tags": [],
  382. "text": "specify the scale of the alert, defaults to m"
  383. },
  384. "attribute": "scale",
  385. "reflect": true,
  386. "defaultValue": "\"m\""
  387. }
  388. }; }
  389. static get states() { return {
  390. "queue": {},
  391. "queueLength": {},
  392. "queued": {},
  393. "requestedIcon": {}
  394. }; }
  395. static get events() { return [{
  396. "method": "calciteAlertClose",
  397. "name": "calciteAlertClose",
  398. "bubbles": true,
  399. "cancelable": true,
  400. "composed": true,
  401. "docs": {
  402. "tags": [],
  403. "text": "Fired when an alert is closed"
  404. },
  405. "complexType": {
  406. "original": "any",
  407. "resolved": "any",
  408. "references": {}
  409. }
  410. }, {
  411. "method": "calciteAlertOpen",
  412. "name": "calciteAlertOpen",
  413. "bubbles": true,
  414. "cancelable": true,
  415. "composed": true,
  416. "docs": {
  417. "tags": [],
  418. "text": "Fired when an alert is opened"
  419. },
  420. "complexType": {
  421. "original": "any",
  422. "resolved": "any",
  423. "references": {}
  424. }
  425. }, {
  426. "method": "calciteAlertSync",
  427. "name": "calciteAlertSync",
  428. "bubbles": true,
  429. "cancelable": true,
  430. "composed": true,
  431. "docs": {
  432. "tags": [{
  433. "name": "internal",
  434. "text": undefined
  435. }],
  436. "text": "Fired to sync queue when opened or closed"
  437. },
  438. "complexType": {
  439. "original": "any",
  440. "resolved": "any",
  441. "references": {}
  442. }
  443. }, {
  444. "method": "calciteAlertRegister",
  445. "name": "calciteAlertRegister",
  446. "bubbles": true,
  447. "cancelable": true,
  448. "composed": true,
  449. "docs": {
  450. "tags": [{
  451. "name": "internal",
  452. "text": undefined
  453. }],
  454. "text": "Fired when an alert is added to dom - used to receive initial queue"
  455. },
  456. "complexType": {
  457. "original": "any",
  458. "resolved": "any",
  459. "references": {}
  460. }
  461. }]; }
  462. static get methods() { return {
  463. "setFocus": {
  464. "complexType": {
  465. "signature": "() => Promise<void>",
  466. "parameters": [],
  467. "references": {
  468. "Promise": {
  469. "location": "global"
  470. },
  471. "HTMLCalciteLinkElement": {
  472. "location": "global"
  473. }
  474. },
  475. "return": "Promise<void>"
  476. },
  477. "docs": {
  478. "text": "Sets focus on the component.",
  479. "tags": []
  480. }
  481. }
  482. }; }
  483. static get elementRef() { return "el"; }
  484. static get watchers() { return [{
  485. "propName": "active",
  486. "methodName": "watchActive"
  487. }, {
  488. "propName": "icon",
  489. "methodName": "updateRequestedIcon"
  490. }, {
  491. "propName": "color",
  492. "methodName": "updateRequestedIcon"
  493. }, {
  494. "propName": "autoDismissDuration",
  495. "methodName": "updateDuration"
  496. }]; }
  497. static get listeners() { return [{
  498. "name": "calciteAlertSync",
  499. "method": "alertSync",
  500. "target": "window",
  501. "capture": false,
  502. "passive": false
  503. }, {
  504. "name": "calciteAlertRegister",
  505. "method": "alertRegister",
  506. "target": "window",
  507. "capture": false,
  508. "passive": false
  509. }]; }
  510. }