rating.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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 { Fragment, h } from "@stencil/core";
  7. import { guid } from "../../utils/guid";
  8. import { connectLabel, disconnectLabel } from "../../utils/label";
  9. import { connectForm, disconnectForm, HiddenFormInputSlot } from "../../utils/form";
  10. import { TEXT } from "./resources";
  11. import { updateHostInteraction } from "../../utils/interactive";
  12. import { isActivationKey } from "../../utils/key";
  13. export class Rating {
  14. constructor() {
  15. // --------------------------------------------------------------------------
  16. //
  17. // Properties
  18. //
  19. // --------------------------------------------------------------------------
  20. /** Specifies the size of the component. */
  21. this.scale = "m";
  22. /** The component's value. */
  23. this.value = 0;
  24. /** When `true`, the component's value can be read, but cannot be modified. */
  25. this.readOnly = false;
  26. /** When `true`, interaction is prevented and the component is displayed with lower opacity. */
  27. this.disabled = false;
  28. /** When `true`, and if available, displays the `average` and/or `count` data summary in a `calcite-chip`. */
  29. this.showChip = false;
  30. /**
  31. * Accessible name for the component.
  32. *
  33. * @default "Rating"
  34. */
  35. this.intlRating = TEXT.rating;
  36. /**
  37. * Accessible name for each star. The `${num}` in the string will be replaced by the number.
  38. *
  39. * @default "Stars: ${num}"
  40. */
  41. this.intlStars = TEXT.stars;
  42. /**
  43. * When `true`, the component must have a value in order for the form to submit.
  44. *
  45. * @internal
  46. */
  47. this.required = false;
  48. this.onKeyboardPressed = (event) => {
  49. if (!this.required && isActivationKey(event.key)) {
  50. event.preventDefault();
  51. this.updateValue(0);
  52. }
  53. };
  54. this.onFocusChange = (selectedRatingValue) => {
  55. this.hasFocus = true;
  56. if (!this.required && this.focusValue === selectedRatingValue) {
  57. this.updateValue(0);
  58. }
  59. else {
  60. this.focusValue = selectedRatingValue;
  61. }
  62. };
  63. this.guid = `calcite-ratings-${guid()}`;
  64. }
  65. //--------------------------------------------------------------------------
  66. //
  67. // Lifecycle
  68. //
  69. //--------------------------------------------------------------------------
  70. connectedCallback() {
  71. connectLabel(this);
  72. connectForm(this);
  73. }
  74. disconnectedCallback() {
  75. disconnectLabel(this);
  76. disconnectForm(this);
  77. }
  78. componentDidRender() {
  79. updateHostInteraction(this);
  80. }
  81. //--------------------------------------------------------------------------
  82. //
  83. // Event Listeners
  84. //
  85. //--------------------------------------------------------------------------
  86. blurHandler() {
  87. this.hasFocus = false;
  88. }
  89. // --------------------------------------------------------------------------
  90. //
  91. // Render Methods
  92. //
  93. // --------------------------------------------------------------------------
  94. renderStars() {
  95. return [1, 2, 3, 4, 5].map((i) => {
  96. const selected = this.value >= i;
  97. const average = this.average && !this.value && i <= this.average;
  98. const hovered = i <= this.hoverValue;
  99. const fraction = this.average && this.average + 1 - i;
  100. const partial = !this.value && !hovered && fraction > 0 && fraction < 1;
  101. const focused = this.hasFocus && this.focusValue === i;
  102. return (h("span", { class: { wrapper: true } }, h("label", { class: { star: true, focused, selected, average, hovered, partial }, htmlFor: `${this.guid}-${i}`, onPointerOver: () => {
  103. this.hoverValue = i;
  104. } }, h("calcite-icon", { "aria-hidden": "true", class: "icon", icon: selected || average || this.readOnly ? "star-f" : "star", scale: this.scale }), partial && (h("div", { class: "fraction", style: { width: `${fraction * 100}%` } }, h("calcite-icon", { icon: "star-f", scale: this.scale }))), h("span", { class: "visually-hidden" }, this.intlStars.replace("${num}", `${i}`))), h("input", { checked: i === this.value, class: "visually-hidden", disabled: this.disabled || this.readOnly, id: `${this.guid}-${i}`, name: this.guid, onChange: () => this.updateValue(i), onClick: (event) =>
  105. // click is fired from the component's label, so we treat this as an internal event
  106. event.stopPropagation(), onFocus: () => this.onFocusChange(i), onKeyDown: this.onKeyboardPressed, ref: (el) => (i === 1 || i === this.value) && (this.inputFocusRef = el), type: "radio", value: i })));
  107. });
  108. }
  109. render() {
  110. const { disabled, intlRating, showChip, scale, count, average } = this;
  111. return (h(Fragment, null, h("fieldset", { class: "fieldset", disabled: disabled, onBlur: () => (this.hoverValue = null), onPointerLeave: () => (this.hoverValue = null), onTouchEnd: () => (this.hoverValue = null) }, h("legend", { class: "visually-hidden" }, intlRating), this.renderStars()), (count || average) && showChip ? (h("calcite-chip", { scale: scale, value: count === null || count === void 0 ? void 0 : count.toString() }, !!average && h("span", { class: "number--average" }, average.toString()), !!count && h("span", { class: "number--count" }, "(", count === null || count === void 0 ? void 0 :
  112. count.toString(), ")"))) : null, h(HiddenFormInputSlot, { component: this })));
  113. }
  114. //--------------------------------------------------------------------------
  115. //
  116. // Private Methods
  117. //
  118. //--------------------------------------------------------------------------
  119. onLabelClick() {
  120. this.setFocus();
  121. }
  122. updateValue(value) {
  123. this.value = value;
  124. this.calciteRatingChange.emit({ value });
  125. }
  126. //--------------------------------------------------------------------------
  127. //
  128. // Public Methods
  129. //
  130. //--------------------------------------------------------------------------
  131. /** Sets focus on the component. */
  132. async setFocus() {
  133. var _a;
  134. (_a = this.inputFocusRef) === null || _a === void 0 ? void 0 : _a.focus();
  135. }
  136. static get is() { return "calcite-rating"; }
  137. static get encapsulation() { return "shadow"; }
  138. static get originalStyleUrls() {
  139. return {
  140. "$": ["rating.scss"]
  141. };
  142. }
  143. static get styleUrls() {
  144. return {
  145. "$": ["rating.css"]
  146. };
  147. }
  148. static get properties() {
  149. return {
  150. "scale": {
  151. "type": "string",
  152. "mutable": false,
  153. "complexType": {
  154. "original": "Scale",
  155. "resolved": "\"l\" | \"m\" | \"s\"",
  156. "references": {
  157. "Scale": {
  158. "location": "import",
  159. "path": "../interfaces"
  160. }
  161. }
  162. },
  163. "required": false,
  164. "optional": false,
  165. "docs": {
  166. "tags": [],
  167. "text": "Specifies the size of the component."
  168. },
  169. "attribute": "scale",
  170. "reflect": true,
  171. "defaultValue": "\"m\""
  172. },
  173. "value": {
  174. "type": "number",
  175. "mutable": true,
  176. "complexType": {
  177. "original": "number",
  178. "resolved": "number",
  179. "references": {}
  180. },
  181. "required": false,
  182. "optional": false,
  183. "docs": {
  184. "tags": [],
  185. "text": "The component's value."
  186. },
  187. "attribute": "value",
  188. "reflect": true,
  189. "defaultValue": "0"
  190. },
  191. "readOnly": {
  192. "type": "boolean",
  193. "mutable": false,
  194. "complexType": {
  195. "original": "boolean",
  196. "resolved": "boolean",
  197. "references": {}
  198. },
  199. "required": false,
  200. "optional": false,
  201. "docs": {
  202. "tags": [],
  203. "text": "When `true`, the component's value can be read, but cannot be modified."
  204. },
  205. "attribute": "read-only",
  206. "reflect": true,
  207. "defaultValue": "false"
  208. },
  209. "disabled": {
  210. "type": "boolean",
  211. "mutable": false,
  212. "complexType": {
  213. "original": "boolean",
  214. "resolved": "boolean",
  215. "references": {}
  216. },
  217. "required": false,
  218. "optional": false,
  219. "docs": {
  220. "tags": [],
  221. "text": "When `true`, interaction is prevented and the component is displayed with lower opacity."
  222. },
  223. "attribute": "disabled",
  224. "reflect": true,
  225. "defaultValue": "false"
  226. },
  227. "showChip": {
  228. "type": "boolean",
  229. "mutable": false,
  230. "complexType": {
  231. "original": "boolean",
  232. "resolved": "boolean",
  233. "references": {}
  234. },
  235. "required": false,
  236. "optional": false,
  237. "docs": {
  238. "tags": [],
  239. "text": "When `true`, and if available, displays the `average` and/or `count` data summary in a `calcite-chip`."
  240. },
  241. "attribute": "show-chip",
  242. "reflect": true,
  243. "defaultValue": "false"
  244. },
  245. "count": {
  246. "type": "number",
  247. "mutable": false,
  248. "complexType": {
  249. "original": "number",
  250. "resolved": "number",
  251. "references": {}
  252. },
  253. "required": false,
  254. "optional": true,
  255. "docs": {
  256. "tags": [],
  257. "text": "Specifies the number of previous ratings to display."
  258. },
  259. "attribute": "count",
  260. "reflect": true
  261. },
  262. "average": {
  263. "type": "number",
  264. "mutable": false,
  265. "complexType": {
  266. "original": "number",
  267. "resolved": "number",
  268. "references": {}
  269. },
  270. "required": false,
  271. "optional": true,
  272. "docs": {
  273. "tags": [],
  274. "text": "Specifies a cumulative average from previous ratings to display."
  275. },
  276. "attribute": "average",
  277. "reflect": true
  278. },
  279. "name": {
  280. "type": "string",
  281. "mutable": false,
  282. "complexType": {
  283. "original": "string",
  284. "resolved": "string",
  285. "references": {}
  286. },
  287. "required": false,
  288. "optional": false,
  289. "docs": {
  290. "tags": [],
  291. "text": "Specifies the name of the component on form submission."
  292. },
  293. "attribute": "name",
  294. "reflect": true
  295. },
  296. "intlRating": {
  297. "type": "string",
  298. "mutable": false,
  299. "complexType": {
  300. "original": "string",
  301. "resolved": "string",
  302. "references": {}
  303. },
  304. "required": false,
  305. "optional": true,
  306. "docs": {
  307. "tags": [{
  308. "name": "default",
  309. "text": "\"Rating\""
  310. }],
  311. "text": "Accessible name for the component."
  312. },
  313. "attribute": "intl-rating",
  314. "reflect": false,
  315. "defaultValue": "TEXT.rating"
  316. },
  317. "intlStars": {
  318. "type": "string",
  319. "mutable": false,
  320. "complexType": {
  321. "original": "string",
  322. "resolved": "string",
  323. "references": {}
  324. },
  325. "required": false,
  326. "optional": true,
  327. "docs": {
  328. "tags": [{
  329. "name": "default",
  330. "text": "\"Stars: ${num}\""
  331. }],
  332. "text": "Accessible name for each star. The `${num}` in the string will be replaced by the number."
  333. },
  334. "attribute": "intl-stars",
  335. "reflect": false,
  336. "defaultValue": "TEXT.stars"
  337. },
  338. "required": {
  339. "type": "boolean",
  340. "mutable": false,
  341. "complexType": {
  342. "original": "boolean",
  343. "resolved": "boolean",
  344. "references": {}
  345. },
  346. "required": false,
  347. "optional": false,
  348. "docs": {
  349. "tags": [{
  350. "name": "internal",
  351. "text": undefined
  352. }],
  353. "text": "When `true`, the component must have a value in order for the form to submit."
  354. },
  355. "attribute": "required",
  356. "reflect": true,
  357. "defaultValue": "false"
  358. }
  359. };
  360. }
  361. static get states() {
  362. return {
  363. "hoverValue": {},
  364. "focusValue": {},
  365. "hasFocus": {}
  366. };
  367. }
  368. static get events() {
  369. return [{
  370. "method": "calciteRatingChange",
  371. "name": "calciteRatingChange",
  372. "bubbles": true,
  373. "cancelable": false,
  374. "composed": true,
  375. "docs": {
  376. "tags": [],
  377. "text": "Fires when the component's value changes."
  378. },
  379. "complexType": {
  380. "original": "{ value: number }",
  381. "resolved": "{ value: number; }",
  382. "references": {}
  383. }
  384. }];
  385. }
  386. static get methods() {
  387. return {
  388. "setFocus": {
  389. "complexType": {
  390. "signature": "() => Promise<void>",
  391. "parameters": [],
  392. "references": {
  393. "Promise": {
  394. "location": "global"
  395. }
  396. },
  397. "return": "Promise<void>"
  398. },
  399. "docs": {
  400. "text": "Sets focus on the component.",
  401. "tags": []
  402. }
  403. }
  404. };
  405. }
  406. static get elementRef() { return "el"; }
  407. static get listeners() {
  408. return [{
  409. "name": "blur",
  410. "method": "blurHandler",
  411. "target": undefined,
  412. "capture": false,
  413. "passive": false
  414. }];
  415. }
  416. }