alert.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  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 { h, Host } from "@stencil/core";
  7. import { getSlotted, setRequestedIcon, toAriaBoolean } from "../../utils/dom";
  8. import { DURATIONS, SLOTS, TEXT } from "./resources";
  9. import { StatusIcons } from "./interfaces";
  10. import { connectOpenCloseComponent, disconnectOpenCloseComponent } from "../../utils/openCloseComponent";
  11. import { connectLocalized, disconnectLocalized, numberStringFormatter } from "../../utils/locale";
  12. /**
  13. * 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
  14. * 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.
  15. */
  16. /**
  17. * @slot title - A slot for optionally adding a title to the component.
  18. * @slot message - A slot for adding main text to the component.
  19. * @slot link - A slot for optionally adding an action to take from the alert (undo, try again, link to page, etc.)
  20. */
  21. export class Alert {
  22. constructor() {
  23. //--------------------------------------------------------------------------
  24. //
  25. // Properties
  26. //
  27. //---------------------------------------------------------------------------
  28. /**
  29. * When `true`, displays and positions the component.
  30. *
  31. * @deprecated use `open` instead.
  32. */
  33. this.active = false;
  34. /** When `true`, displays and positions the component. */
  35. this.open = false;
  36. /** When `true`, the component closes automatically (recommended for passive, non-blocking alerts). */
  37. this.autoDismiss = false;
  38. /** Specifies the duration before the component automatically closes (only use with `autoDismiss`). */
  39. this.autoDismissDuration = this.autoDismiss ? "medium" : null;
  40. /** Specifies the color for the component (will apply to top border and icon). */
  41. this.color = "blue";
  42. /**
  43. * Specifies the text label for the close button.
  44. *
  45. * @default "Close"
  46. */
  47. this.intlClose = TEXT.intlClose;
  48. /** Specifies the placement of the component */
  49. this.placement = "bottom";
  50. /** Specifies the size of the component. */
  51. this.scale = "m";
  52. //--------------------------------------------------------------------------
  53. //
  54. // Private State/Props
  55. //
  56. //--------------------------------------------------------------------------
  57. this.effectiveLocale = "";
  58. /** the list of queued alerts */
  59. this.queue = [];
  60. /** the count of queued alerts */
  61. this.queueLength = 0;
  62. /** is the alert queued */
  63. this.queued = false;
  64. this.autoDismissTimeoutId = null;
  65. this.trackTimer = Date.now();
  66. this.openTransitionProp = "opacity";
  67. //--------------------------------------------------------------------------
  68. //
  69. // Private Methods
  70. //
  71. //--------------------------------------------------------------------------
  72. this.setTransitionEl = (el) => {
  73. this.transitionEl = el;
  74. connectOpenCloseComponent(this);
  75. };
  76. /** close and emit calciteInternalAlertSync event with the updated queue payload */
  77. this.closeAlert = () => {
  78. this.autoDismissTimeoutId = null;
  79. this.queued = false;
  80. this.open = false;
  81. this.queue = this.queue.filter((el) => el !== this.el);
  82. this.determineActiveAlert();
  83. this.calciteInternalAlertSync.emit({ queue: this.queue });
  84. };
  85. }
  86. activeHandler(value) {
  87. this.open = value;
  88. }
  89. openHandler(value) {
  90. if (this.open && !this.queued) {
  91. this.calciteInternalAlertRegister.emit();
  92. this.active = value;
  93. }
  94. if (!this.open) {
  95. this.queue = this.queue.filter((el) => el !== this.el);
  96. this.calciteInternalAlertSync.emit({ queue: this.queue });
  97. this.active = false;
  98. }
  99. }
  100. updateRequestedIcon() {
  101. this.requestedIcon = setRequestedIcon(StatusIcons, this.icon, this.color);
  102. }
  103. updateDuration() {
  104. if (this.autoDismiss && this.autoDismissTimeoutId) {
  105. window.clearTimeout(this.autoDismissTimeoutId);
  106. this.autoDismissTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoDismissDuration] - (Date.now() - this.trackTimer));
  107. }
  108. }
  109. //--------------------------------------------------------------------------
  110. //
  111. // Lifecycle
  112. //
  113. //--------------------------------------------------------------------------
  114. connectedCallback() {
  115. connectLocalized(this);
  116. const open = this.open || this.active;
  117. if (open && !this.queued) {
  118. this.activeHandler(open);
  119. this.openHandler(open);
  120. this.calciteInternalAlertRegister.emit();
  121. }
  122. connectOpenCloseComponent(this);
  123. }
  124. componentWillLoad() {
  125. this.requestedIcon = setRequestedIcon(StatusIcons, this.icon, this.color);
  126. }
  127. disconnectedCallback() {
  128. window.clearTimeout(this.autoDismissTimeoutId);
  129. disconnectOpenCloseComponent(this);
  130. disconnectLocalized(this);
  131. }
  132. render() {
  133. const closeButton = (h("button", { "aria-label": this.intlClose, class: "alert-close", onClick: this.closeAlert, ref: (el) => (this.closeButton = el), type: "button" }, h("calcite-icon", { icon: "x", scale: this.scale === "l" ? "m" : "s" })));
  134. numberStringFormatter.numberFormatOptions = {
  135. locale: this.effectiveLocale,
  136. numberingSystem: this.numberingSystem,
  137. signDisplay: "always"
  138. };
  139. const queueNumber = this.queueLength > 2 ? this.queueLength - 1 : 1;
  140. const queueText = numberStringFormatter.numberFormatter.format(queueNumber);
  141. const queueCount = (h("div", { class: `${this.queueLength > 1 ? "active " : ""}alert-queue-count` }, h("calcite-chip", { scale: this.scale, value: queueText }, queueText)));
  142. const { active, autoDismiss, label, placement, queued, requestedIcon } = this;
  143. const role = autoDismiss ? "alert" : "alertdialog";
  144. const hidden = !active;
  145. return (h(Host, { "aria-hidden": toAriaBoolean(hidden), "aria-label": label, "calcite-hydrated-hidden": hidden, role: role }, h("div", { class: {
  146. container: true,
  147. queued,
  148. [placement]: true
  149. }, ref: this.setTransitionEl }, requestedIcon ? (h("div", { class: "alert-icon" }, h("calcite-icon", { icon: requestedIcon, scale: this.scale === "l" ? "m" : "s" }))) : null, h("div", { class: "alert-content" }, h("slot", { name: SLOTS.title }), h("slot", { name: SLOTS.message }), h("slot", { name: SLOTS.link })), queueCount, !autoDismiss ? closeButton : null, active && !queued && autoDismiss ? h("div", { class: "alert-dismiss-progress" }) : null)));
  150. }
  151. // when an alert is opened or closed, update queue and determine active alert
  152. alertSync(event) {
  153. if (this.queue !== event.detail.queue) {
  154. this.queue = event.detail.queue;
  155. }
  156. this.queueLength = this.queue.length;
  157. this.determineActiveAlert();
  158. event.stopPropagation();
  159. }
  160. // when an alert is first registered, trigger a queue sync
  161. alertRegister() {
  162. if (this.open && !this.queue.includes(this.el)) {
  163. this.queued = true;
  164. this.queue.push(this.el);
  165. }
  166. this.calciteInternalAlertSync.emit({ queue: this.queue });
  167. this.determineActiveAlert();
  168. }
  169. //--------------------------------------------------------------------------
  170. //
  171. // Public Methods
  172. //
  173. //--------------------------------------------------------------------------
  174. /** Sets focus on the component. */
  175. async setFocus() {
  176. const alertLinkEl = getSlotted(this.el, { selector: "calcite-link" });
  177. if (!this.closeButton && !alertLinkEl) {
  178. return;
  179. }
  180. else if (alertLinkEl) {
  181. alertLinkEl.setFocus();
  182. }
  183. else if (this.closeButton) {
  184. this.closeButton.focus();
  185. }
  186. }
  187. /** determine which alert is active */
  188. determineActiveAlert() {
  189. var _a;
  190. if (((_a = this.queue) === null || _a === void 0 ? void 0 : _a[0]) === this.el) {
  191. this.openAlert();
  192. if (this.autoDismiss && !this.autoDismissTimeoutId) {
  193. this.trackTimer = Date.now();
  194. this.autoDismissTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoDismissDuration]);
  195. }
  196. }
  197. else {
  198. return;
  199. }
  200. }
  201. onBeforeOpen() {
  202. this.calciteAlertBeforeOpen.emit();
  203. }
  204. onOpen() {
  205. this.calciteAlertOpen.emit();
  206. }
  207. onBeforeClose() {
  208. this.calciteAlertBeforeClose.emit();
  209. }
  210. onClose() {
  211. this.calciteAlertClose.emit();
  212. }
  213. /** remove queued class after animation completes */
  214. openAlert() {
  215. window.clearTimeout(this.queueTimeout);
  216. this.queueTimeout = window.setTimeout(() => (this.queued = false), 300);
  217. }
  218. static get is() { return "calcite-alert"; }
  219. static get encapsulation() { return "shadow"; }
  220. static get originalStyleUrls() {
  221. return {
  222. "$": ["alert.scss"]
  223. };
  224. }
  225. static get styleUrls() {
  226. return {
  227. "$": ["alert.css"]
  228. };
  229. }
  230. static get properties() {
  231. return {
  232. "active": {
  233. "type": "boolean",
  234. "mutable": true,
  235. "complexType": {
  236. "original": "boolean",
  237. "resolved": "boolean",
  238. "references": {}
  239. },
  240. "required": false,
  241. "optional": false,
  242. "docs": {
  243. "tags": [{
  244. "name": "deprecated",
  245. "text": "use `open` instead."
  246. }],
  247. "text": "When `true`, displays and positions the component."
  248. },
  249. "attribute": "active",
  250. "reflect": true,
  251. "defaultValue": "false"
  252. },
  253. "open": {
  254. "type": "boolean",
  255. "mutable": true,
  256. "complexType": {
  257. "original": "boolean",
  258. "resolved": "boolean",
  259. "references": {}
  260. },
  261. "required": false,
  262. "optional": false,
  263. "docs": {
  264. "tags": [],
  265. "text": "When `true`, displays and positions the component."
  266. },
  267. "attribute": "open",
  268. "reflect": true,
  269. "defaultValue": "false"
  270. },
  271. "autoDismiss": {
  272. "type": "boolean",
  273. "mutable": false,
  274. "complexType": {
  275. "original": "boolean",
  276. "resolved": "boolean",
  277. "references": {}
  278. },
  279. "required": false,
  280. "optional": false,
  281. "docs": {
  282. "tags": [],
  283. "text": "When `true`, the component closes automatically (recommended for passive, non-blocking alerts)."
  284. },
  285. "attribute": "auto-dismiss",
  286. "reflect": true,
  287. "defaultValue": "false"
  288. },
  289. "autoDismissDuration": {
  290. "type": "string",
  291. "mutable": false,
  292. "complexType": {
  293. "original": "AlertDuration",
  294. "resolved": "\"fast\" | \"medium\" | \"slow\"",
  295. "references": {
  296. "AlertDuration": {
  297. "location": "import",
  298. "path": "./interfaces"
  299. }
  300. }
  301. },
  302. "required": false,
  303. "optional": false,
  304. "docs": {
  305. "tags": [],
  306. "text": "Specifies the duration before the component automatically closes (only use with `autoDismiss`)."
  307. },
  308. "attribute": "auto-dismiss-duration",
  309. "reflect": true,
  310. "defaultValue": "this.autoDismiss ? \"medium\" : null"
  311. },
  312. "color": {
  313. "type": "string",
  314. "mutable": false,
  315. "complexType": {
  316. "original": "StatusColor",
  317. "resolved": "\"blue\" | \"green\" | \"red\" | \"yellow\"",
  318. "references": {
  319. "StatusColor": {
  320. "location": "import",
  321. "path": "./interfaces"
  322. }
  323. }
  324. },
  325. "required": false,
  326. "optional": false,
  327. "docs": {
  328. "tags": [],
  329. "text": "Specifies the color for the component (will apply to top border and icon)."
  330. },
  331. "attribute": "color",
  332. "reflect": true,
  333. "defaultValue": "\"blue\""
  334. },
  335. "icon": {
  336. "type": "any",
  337. "mutable": false,
  338. "complexType": {
  339. "original": "string | boolean",
  340. "resolved": "boolean | string",
  341. "references": {}
  342. },
  343. "required": false,
  344. "optional": false,
  345. "docs": {
  346. "tags": [],
  347. "text": "When `true`, shows a default recommended icon. Alternatively,\npass a Calcite UI Icon name to display a specific icon."
  348. },
  349. "attribute": "icon",
  350. "reflect": true
  351. },
  352. "intlClose": {
  353. "type": "string",
  354. "mutable": false,
  355. "complexType": {
  356. "original": "string",
  357. "resolved": "string",
  358. "references": {}
  359. },
  360. "required": false,
  361. "optional": false,
  362. "docs": {
  363. "tags": [{
  364. "name": "default",
  365. "text": "\"Close\""
  366. }],
  367. "text": "Specifies the text label for the close button."
  368. },
  369. "attribute": "intl-close",
  370. "reflect": false,
  371. "defaultValue": "TEXT.intlClose"
  372. },
  373. "label": {
  374. "type": "string",
  375. "mutable": false,
  376. "complexType": {
  377. "original": "string",
  378. "resolved": "string",
  379. "references": {}
  380. },
  381. "required": true,
  382. "optional": false,
  383. "docs": {
  384. "tags": [],
  385. "text": "Specifies an accessible name for the component."
  386. },
  387. "attribute": "label",
  388. "reflect": false
  389. },
  390. "numberingSystem": {
  391. "type": "string",
  392. "mutable": false,
  393. "complexType": {
  394. "original": "NumberingSystem",
  395. "resolved": "\"arab\" | \"arabext\" | \"bali\" | \"beng\" | \"deva\" | \"fullwide\" | \"gujr\" | \"guru\" | \"hanidec\" | \"khmr\" | \"knda\" | \"laoo\" | \"latn\" | \"limb\" | \"mlym\" | \"mong\" | \"mymr\" | \"orya\" | \"tamldec\" | \"telu\" | \"thai\" | \"tibt\"",
  396. "references": {
  397. "NumberingSystem": {
  398. "location": "import",
  399. "path": "../../utils/locale"
  400. }
  401. }
  402. },
  403. "required": false,
  404. "optional": true,
  405. "docs": {
  406. "tags": [],
  407. "text": "Specifies the Unicode numeral system used by the component for localization."
  408. },
  409. "attribute": "numbering-system",
  410. "reflect": true
  411. },
  412. "placement": {
  413. "type": "string",
  414. "mutable": false,
  415. "complexType": {
  416. "original": "AlertPlacement",
  417. "resolved": "\"bottom\" | \"bottom-end\" | \"bottom-start\" | \"top\" | \"top-end\" | \"top-start\"",
  418. "references": {
  419. "AlertPlacement": {
  420. "location": "import",
  421. "path": "./interfaces"
  422. }
  423. }
  424. },
  425. "required": false,
  426. "optional": false,
  427. "docs": {
  428. "tags": [],
  429. "text": "Specifies the placement of the component"
  430. },
  431. "attribute": "placement",
  432. "reflect": true,
  433. "defaultValue": "\"bottom\""
  434. },
  435. "scale": {
  436. "type": "string",
  437. "mutable": false,
  438. "complexType": {
  439. "original": "Scale",
  440. "resolved": "\"l\" | \"m\" | \"s\"",
  441. "references": {
  442. "Scale": {
  443. "location": "import",
  444. "path": "../interfaces"
  445. }
  446. }
  447. },
  448. "required": false,
  449. "optional": false,
  450. "docs": {
  451. "tags": [],
  452. "text": "Specifies the size of the component."
  453. },
  454. "attribute": "scale",
  455. "reflect": true,
  456. "defaultValue": "\"m\""
  457. }
  458. };
  459. }
  460. static get states() {
  461. return {
  462. "effectiveLocale": {},
  463. "queue": {},
  464. "queueLength": {},
  465. "queued": {},
  466. "requestedIcon": {}
  467. };
  468. }
  469. static get events() {
  470. return [{
  471. "method": "calciteAlertBeforeClose",
  472. "name": "calciteAlertBeforeClose",
  473. "bubbles": true,
  474. "cancelable": false,
  475. "composed": true,
  476. "docs": {
  477. "tags": [],
  478. "text": "Fires when the component is requested to be closed and before the closing transition begins."
  479. },
  480. "complexType": {
  481. "original": "void",
  482. "resolved": "void",
  483. "references": {}
  484. }
  485. }, {
  486. "method": "calciteAlertClose",
  487. "name": "calciteAlertClose",
  488. "bubbles": true,
  489. "cancelable": false,
  490. "composed": true,
  491. "docs": {
  492. "tags": [],
  493. "text": "Fires when the component is closed and animation is complete."
  494. },
  495. "complexType": {
  496. "original": "void",
  497. "resolved": "void",
  498. "references": {}
  499. }
  500. }, {
  501. "method": "calciteAlertBeforeOpen",
  502. "name": "calciteAlertBeforeOpen",
  503. "bubbles": true,
  504. "cancelable": false,
  505. "composed": true,
  506. "docs": {
  507. "tags": [],
  508. "text": "Fires when the component is added to the DOM but not rendered, and before the opening transition begins."
  509. },
  510. "complexType": {
  511. "original": "void",
  512. "resolved": "void",
  513. "references": {}
  514. }
  515. }, {
  516. "method": "calciteAlertOpen",
  517. "name": "calciteAlertOpen",
  518. "bubbles": true,
  519. "cancelable": false,
  520. "composed": true,
  521. "docs": {
  522. "tags": [],
  523. "text": "Fires when the component is open and animation is complete."
  524. },
  525. "complexType": {
  526. "original": "void",
  527. "resolved": "void",
  528. "references": {}
  529. }
  530. }, {
  531. "method": "calciteInternalAlertSync",
  532. "name": "calciteInternalAlertSync",
  533. "bubbles": true,
  534. "cancelable": false,
  535. "composed": true,
  536. "docs": {
  537. "tags": [{
  538. "name": "internal",
  539. "text": undefined
  540. }],
  541. "text": "Fires to sync queue when opened or closed."
  542. },
  543. "complexType": {
  544. "original": "Sync",
  545. "resolved": "Sync",
  546. "references": {
  547. "Sync": {
  548. "location": "import",
  549. "path": "./interfaces"
  550. }
  551. }
  552. }
  553. }, {
  554. "method": "calciteInternalAlertRegister",
  555. "name": "calciteInternalAlertRegister",
  556. "bubbles": true,
  557. "cancelable": false,
  558. "composed": true,
  559. "docs": {
  560. "tags": [{
  561. "name": "internal",
  562. "text": undefined
  563. }],
  564. "text": "Fires when the component is added to DOM - used to receive initial queue."
  565. },
  566. "complexType": {
  567. "original": "void",
  568. "resolved": "void",
  569. "references": {}
  570. }
  571. }];
  572. }
  573. static get methods() {
  574. return {
  575. "setFocus": {
  576. "complexType": {
  577. "signature": "() => Promise<void>",
  578. "parameters": [],
  579. "references": {
  580. "Promise": {
  581. "location": "global"
  582. },
  583. "HTMLCalciteLinkElement": {
  584. "location": "global"
  585. }
  586. },
  587. "return": "Promise<void>"
  588. },
  589. "docs": {
  590. "text": "Sets focus on the component.",
  591. "tags": []
  592. }
  593. }
  594. };
  595. }
  596. static get elementRef() { return "el"; }
  597. static get watchers() {
  598. return [{
  599. "propName": "active",
  600. "methodName": "activeHandler"
  601. }, {
  602. "propName": "open",
  603. "methodName": "openHandler"
  604. }, {
  605. "propName": "icon",
  606. "methodName": "updateRequestedIcon"
  607. }, {
  608. "propName": "color",
  609. "methodName": "updateRequestedIcon"
  610. }, {
  611. "propName": "autoDismissDuration",
  612. "methodName": "updateDuration"
  613. }];
  614. }
  615. static get listeners() {
  616. return [{
  617. "name": "calciteInternalAlertSync",
  618. "method": "alertSync",
  619. "target": "window",
  620. "capture": false,
  621. "passive": false
  622. }, {
  623. "name": "calciteInternalAlertRegister",
  624. "method": "alertRegister",
  625. "target": "window",
  626. "capture": false,
  627. "passive": false
  628. }];
  629. }
  630. }