input-time-picker.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  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, Host, h, Prop, Listen, Event, Method, Watch, State } from "@stencil/core";
  7. import { guid } from "../../utils/guid";
  8. import { formatTimeString, isValidTime, localizeTimeString } from "../../utils/time";
  9. import { connectLabel, disconnectLabel, getLabelText } from "../../utils/label";
  10. import { connectForm, disconnectForm, HiddenFormInputSlot, submitForm } from "../../utils/form";
  11. import { updateHostInteraction } from "../../utils/interactive";
  12. export class InputTimePicker {
  13. constructor() {
  14. //--------------------------------------------------------------------------
  15. //
  16. // Properties
  17. //
  18. //--------------------------------------------------------------------------
  19. /** The active state of the time input */
  20. this.active = false;
  21. /** The disabled state of the time input */
  22. this.disabled = false;
  23. /**
  24. * BCP 47 language tag for desired language and country format
  25. * @internal
  26. */
  27. this.locale = document.documentElement.lang || navigator.language || "en";
  28. /**
  29. * When true, makes the component required for form-submission.
  30. *
  31. * @internal
  32. */
  33. this.required = false;
  34. /** The scale (size) of the time input */
  35. this.scale = "m";
  36. /**
  37. * Determines where the popover will be positioned relative to the input.
  38. * @see [PopperPlacement](https://github.com/Esri/calcite-components/blob/master/src/utils/popper.ts#L25)
  39. */
  40. this.placement = "auto";
  41. /** number (seconds) that specifies the granularity that the value must adhere to */
  42. this.step = 60;
  43. /** The selected time in UTC (always 24-hour format) */
  44. this.value = null;
  45. /** whether the value of the input was changed as a result of user typing or not */
  46. this.internalValueChange = false;
  47. this.previousValidValue = null;
  48. this.referenceElementId = `input-time-picker-${guid()}`;
  49. //--------------------------------------------------------------------------
  50. //
  51. // Event Listeners
  52. //
  53. //--------------------------------------------------------------------------
  54. this.calciteInputBlurHandler = () => {
  55. this.active = false;
  56. const shouldIncludeSeconds = this.shouldIncludeSeconds();
  57. const localizedInputValue = localizeTimeString(this.calciteInputEl.value, this.locale, shouldIncludeSeconds);
  58. this.setInputValue(localizedInputValue || localizeTimeString(this.value, this.locale, shouldIncludeSeconds));
  59. };
  60. this.calciteInputFocusHandler = () => {
  61. this.active = true;
  62. };
  63. this.calciteInputInputHandler = (event) => {
  64. const target = event.target;
  65. this.setValue({ value: target.value });
  66. };
  67. this.timePickerChangeHandler = (event) => {
  68. event.stopPropagation();
  69. const target = event.target;
  70. const value = target.value;
  71. this.setValue({ value, origin: "time-picker" });
  72. };
  73. // --------------------------------------------------------------------------
  74. //
  75. // Private Methods
  76. //
  77. // --------------------------------------------------------------------------
  78. this.keyDownHandler = (event) => {
  79. if (event.key === "Enter" && !event.defaultPrevented) {
  80. submitForm(this);
  81. }
  82. };
  83. this.setCalcitePopoverEl = (el) => {
  84. this.popoverEl = el;
  85. };
  86. this.setCalciteInputEl = (el) => {
  87. this.calciteInputEl = el;
  88. };
  89. this.setCalciteTimePickerEl = (el) => {
  90. this.calciteTimePickerEl = el;
  91. };
  92. this.setInputValue = (newInputValue) => {
  93. if (!this.calciteInputEl) {
  94. return;
  95. }
  96. this.calciteInputEl.value = newInputValue;
  97. };
  98. this.setValue = ({ value, origin = "input" }) => {
  99. const previousValue = this.value;
  100. const newValue = formatTimeString(value);
  101. const newLocalizedValue = localizeTimeString(newValue, this.locale, this.shouldIncludeSeconds());
  102. this.internalValueChange = origin !== "external" && origin !== "loading";
  103. const shouldEmit = origin !== "loading" &&
  104. origin !== "external" &&
  105. ((value !== this.previousValidValue && !value) ||
  106. !!(!this.previousValidValue && newValue) ||
  107. (newValue !== this.previousValidValue && newValue));
  108. if (value) {
  109. if (shouldEmit) {
  110. this.previousValidValue = newValue;
  111. }
  112. if (newValue && newValue !== this.value) {
  113. this.value = newValue;
  114. }
  115. this.localizedValue = newLocalizedValue;
  116. }
  117. else {
  118. this.value = value;
  119. this.localizedValue = null;
  120. }
  121. if (origin === "time-picker" || origin === "external") {
  122. this.setInputValue(newLocalizedValue);
  123. }
  124. if (shouldEmit) {
  125. const changeEvent = this.calciteInputTimePickerChange.emit();
  126. if (changeEvent.defaultPrevented) {
  127. this.internalValueChange = false;
  128. this.value = previousValue;
  129. this.setInputValue(previousValue);
  130. this.previousValidValue = previousValue;
  131. }
  132. else {
  133. this.previousValidValue = newValue;
  134. }
  135. }
  136. };
  137. }
  138. activeHandler() {
  139. if (this.disabled) {
  140. this.active = false;
  141. return;
  142. }
  143. this.reposition();
  144. }
  145. handleDisabledChange(value) {
  146. if (!value) {
  147. this.active = false;
  148. }
  149. }
  150. localeWatcher(newLocale) {
  151. this.setInputValue(localizeTimeString(this.value, newLocale, this.shouldIncludeSeconds()));
  152. }
  153. valueWatcher(newValue) {
  154. if (!this.internalValueChange) {
  155. this.setValue({ value: newValue, origin: "external" });
  156. }
  157. this.internalValueChange = false;
  158. }
  159. clickHandler(event) {
  160. if (event.composedPath().includes(this.calciteTimePickerEl)) {
  161. return;
  162. }
  163. this.setFocus();
  164. }
  165. keyUpHandler(event) {
  166. if (event.key === "Escape" && this.active) {
  167. this.active = false;
  168. }
  169. }
  170. timePickerBlurHandler(event) {
  171. event.preventDefault();
  172. event.stopPropagation();
  173. this.active = false;
  174. }
  175. timePickerFocusHandler(event) {
  176. event.preventDefault();
  177. event.stopPropagation();
  178. this.active = true;
  179. }
  180. // --------------------------------------------------------------------------
  181. //
  182. // Public Methods
  183. //
  184. // --------------------------------------------------------------------------
  185. /** Sets focus on the component. */
  186. async setFocus() {
  187. this.calciteInputEl.setFocus();
  188. }
  189. /** Updates the position of the component. */
  190. async reposition() {
  191. var _a;
  192. (_a = this.popoverEl) === null || _a === void 0 ? void 0 : _a.reposition();
  193. }
  194. onLabelClick() {
  195. this.setFocus();
  196. }
  197. shouldIncludeSeconds() {
  198. return this.step < 60;
  199. }
  200. //--------------------------------------------------------------------------
  201. //
  202. // Lifecycle
  203. //
  204. //--------------------------------------------------------------------------
  205. connectedCallback() {
  206. if (this.value) {
  207. this.setValue({ value: isValidTime(this.value) ? this.value : undefined, origin: "loading" });
  208. }
  209. connectLabel(this);
  210. connectForm(this);
  211. }
  212. componentDidLoad() {
  213. this.setInputValue(this.localizedValue);
  214. }
  215. disconnectedCallback() {
  216. disconnectLabel(this);
  217. disconnectForm(this);
  218. }
  219. componentDidRender() {
  220. updateHostInteraction(this);
  221. }
  222. // --------------------------------------------------------------------------
  223. //
  224. // Render Methods
  225. //
  226. // --------------------------------------------------------------------------
  227. render() {
  228. const popoverId = `${this.referenceElementId}-popover`;
  229. return (h(Host, { onKeyDown: this.keyDownHandler },
  230. h("div", { "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": this.name, "aria-owns": popoverId, id: this.referenceElementId, role: "combobox" },
  231. h("calcite-input", { disabled: this.disabled, icon: "clock", label: getLabelText(this), onCalciteInputBlur: this.calciteInputBlurHandler, onCalciteInputFocus: this.calciteInputFocusHandler, onCalciteInputInput: this.calciteInputInputHandler, ref: this.setCalciteInputEl, scale: this.scale, step: this.step })),
  232. h("calcite-popover", { id: popoverId, label: "Time Picker", open: this.active, placement: this.placement, ref: this.setCalcitePopoverEl, referenceElement: this.referenceElementId },
  233. h("calcite-time-picker", { intlHour: this.intlHour, intlHourDown: this.intlHourDown, intlHourUp: this.intlHourUp, intlMeridiem: this.intlMeridiem, intlMeridiemDown: this.intlMeridiemDown, intlMeridiemUp: this.intlMeridiemUp, intlMinute: this.intlMinute, intlMinuteDown: this.intlMinuteDown, intlMinuteUp: this.intlMinuteUp, intlSecond: this.intlSecond, intlSecondDown: this.intlSecondDown, intlSecondUp: this.intlSecondUp, lang: this.locale, onCalciteTimePickerChange: this.timePickerChangeHandler, ref: this.setCalciteTimePickerEl, scale: this.scale, step: this.step, value: this.value })),
  234. h(HiddenFormInputSlot, { component: this })));
  235. }
  236. static get is() { return "calcite-input-time-picker"; }
  237. static get encapsulation() { return "shadow"; }
  238. static get originalStyleUrls() { return {
  239. "$": ["input-time-picker.scss"]
  240. }; }
  241. static get styleUrls() { return {
  242. "$": ["input-time-picker.css"]
  243. }; }
  244. static get properties() { return {
  245. "active": {
  246. "type": "boolean",
  247. "mutable": true,
  248. "complexType": {
  249. "original": "boolean",
  250. "resolved": "boolean",
  251. "references": {}
  252. },
  253. "required": false,
  254. "optional": false,
  255. "docs": {
  256. "tags": [],
  257. "text": "The active state of the time input"
  258. },
  259. "attribute": "active",
  260. "reflect": true,
  261. "defaultValue": "false"
  262. },
  263. "disabled": {
  264. "type": "boolean",
  265. "mutable": false,
  266. "complexType": {
  267. "original": "boolean",
  268. "resolved": "boolean",
  269. "references": {}
  270. },
  271. "required": false,
  272. "optional": false,
  273. "docs": {
  274. "tags": [],
  275. "text": "The disabled state of the time input"
  276. },
  277. "attribute": "disabled",
  278. "reflect": true,
  279. "defaultValue": "false"
  280. },
  281. "intlHour": {
  282. "type": "string",
  283. "mutable": false,
  284. "complexType": {
  285. "original": "string",
  286. "resolved": "string",
  287. "references": {}
  288. },
  289. "required": false,
  290. "optional": true,
  291. "docs": {
  292. "tags": [],
  293. "text": "aria-label for the hour input"
  294. },
  295. "attribute": "intl-hour",
  296. "reflect": false
  297. },
  298. "intlHourDown": {
  299. "type": "string",
  300. "mutable": false,
  301. "complexType": {
  302. "original": "string",
  303. "resolved": "string",
  304. "references": {}
  305. },
  306. "required": false,
  307. "optional": true,
  308. "docs": {
  309. "tags": [],
  310. "text": "aria-label for the hour down button"
  311. },
  312. "attribute": "intl-hour-down",
  313. "reflect": false
  314. },
  315. "intlHourUp": {
  316. "type": "string",
  317. "mutable": false,
  318. "complexType": {
  319. "original": "string",
  320. "resolved": "string",
  321. "references": {}
  322. },
  323. "required": false,
  324. "optional": true,
  325. "docs": {
  326. "tags": [],
  327. "text": "aria-label for the hour up button"
  328. },
  329. "attribute": "intl-hour-up",
  330. "reflect": false
  331. },
  332. "intlMeridiem": {
  333. "type": "string",
  334. "mutable": false,
  335. "complexType": {
  336. "original": "string",
  337. "resolved": "string",
  338. "references": {}
  339. },
  340. "required": false,
  341. "optional": true,
  342. "docs": {
  343. "tags": [],
  344. "text": "aria-label for the meridiem (am/pm) input"
  345. },
  346. "attribute": "intl-meridiem",
  347. "reflect": false
  348. },
  349. "intlMeridiemDown": {
  350. "type": "string",
  351. "mutable": false,
  352. "complexType": {
  353. "original": "string",
  354. "resolved": "string",
  355. "references": {}
  356. },
  357. "required": false,
  358. "optional": true,
  359. "docs": {
  360. "tags": [],
  361. "text": "aria-label for the meridiem (am/pm) down button"
  362. },
  363. "attribute": "intl-meridiem-down",
  364. "reflect": false
  365. },
  366. "intlMeridiemUp": {
  367. "type": "string",
  368. "mutable": false,
  369. "complexType": {
  370. "original": "string",
  371. "resolved": "string",
  372. "references": {}
  373. },
  374. "required": false,
  375. "optional": true,
  376. "docs": {
  377. "tags": [],
  378. "text": "aria-label for the meridiem (am/pm) up button"
  379. },
  380. "attribute": "intl-meridiem-up",
  381. "reflect": false
  382. },
  383. "intlMinute": {
  384. "type": "string",
  385. "mutable": false,
  386. "complexType": {
  387. "original": "string",
  388. "resolved": "string",
  389. "references": {}
  390. },
  391. "required": false,
  392. "optional": true,
  393. "docs": {
  394. "tags": [],
  395. "text": "aria-label for the minute input"
  396. },
  397. "attribute": "intl-minute",
  398. "reflect": false
  399. },
  400. "intlMinuteDown": {
  401. "type": "string",
  402. "mutable": false,
  403. "complexType": {
  404. "original": "string",
  405. "resolved": "string",
  406. "references": {}
  407. },
  408. "required": false,
  409. "optional": true,
  410. "docs": {
  411. "tags": [],
  412. "text": "aria-label for the minute down button"
  413. },
  414. "attribute": "intl-minute-down",
  415. "reflect": false
  416. },
  417. "intlMinuteUp": {
  418. "type": "string",
  419. "mutable": false,
  420. "complexType": {
  421. "original": "string",
  422. "resolved": "string",
  423. "references": {}
  424. },
  425. "required": false,
  426. "optional": true,
  427. "docs": {
  428. "tags": [],
  429. "text": "aria-label for the minute up button"
  430. },
  431. "attribute": "intl-minute-up",
  432. "reflect": false
  433. },
  434. "intlSecond": {
  435. "type": "string",
  436. "mutable": false,
  437. "complexType": {
  438. "original": "string",
  439. "resolved": "string",
  440. "references": {}
  441. },
  442. "required": false,
  443. "optional": true,
  444. "docs": {
  445. "tags": [],
  446. "text": "aria-label for the second input"
  447. },
  448. "attribute": "intl-second",
  449. "reflect": false
  450. },
  451. "intlSecondDown": {
  452. "type": "string",
  453. "mutable": false,
  454. "complexType": {
  455. "original": "string",
  456. "resolved": "string",
  457. "references": {}
  458. },
  459. "required": false,
  460. "optional": true,
  461. "docs": {
  462. "tags": [],
  463. "text": "aria-label for the second down button"
  464. },
  465. "attribute": "intl-second-down",
  466. "reflect": false
  467. },
  468. "intlSecondUp": {
  469. "type": "string",
  470. "mutable": false,
  471. "complexType": {
  472. "original": "string",
  473. "resolved": "string",
  474. "references": {}
  475. },
  476. "required": false,
  477. "optional": true,
  478. "docs": {
  479. "tags": [],
  480. "text": "aria-label for the second up button"
  481. },
  482. "attribute": "intl-second-up",
  483. "reflect": false
  484. },
  485. "locale": {
  486. "type": "string",
  487. "mutable": true,
  488. "complexType": {
  489. "original": "string",
  490. "resolved": "string",
  491. "references": {}
  492. },
  493. "required": false,
  494. "optional": false,
  495. "docs": {
  496. "tags": [{
  497. "name": "internal",
  498. "text": undefined
  499. }],
  500. "text": "BCP 47 language tag for desired language and country format"
  501. },
  502. "attribute": "lang",
  503. "reflect": false,
  504. "defaultValue": "document.documentElement.lang || navigator.language || \"en\""
  505. },
  506. "name": {
  507. "type": "string",
  508. "mutable": false,
  509. "complexType": {
  510. "original": "string",
  511. "resolved": "string",
  512. "references": {}
  513. },
  514. "required": false,
  515. "optional": false,
  516. "docs": {
  517. "tags": [],
  518. "text": "The name of the time input"
  519. },
  520. "attribute": "name",
  521. "reflect": false
  522. },
  523. "required": {
  524. "type": "boolean",
  525. "mutable": false,
  526. "complexType": {
  527. "original": "boolean",
  528. "resolved": "boolean",
  529. "references": {}
  530. },
  531. "required": false,
  532. "optional": false,
  533. "docs": {
  534. "tags": [{
  535. "name": "internal",
  536. "text": undefined
  537. }],
  538. "text": "When true, makes the component required for form-submission."
  539. },
  540. "attribute": "required",
  541. "reflect": true,
  542. "defaultValue": "false"
  543. },
  544. "scale": {
  545. "type": "string",
  546. "mutable": false,
  547. "complexType": {
  548. "original": "Scale",
  549. "resolved": "\"l\" | \"m\" | \"s\"",
  550. "references": {
  551. "Scale": {
  552. "location": "import",
  553. "path": "../interfaces"
  554. }
  555. }
  556. },
  557. "required": false,
  558. "optional": false,
  559. "docs": {
  560. "tags": [],
  561. "text": "The scale (size) of the time input"
  562. },
  563. "attribute": "scale",
  564. "reflect": true,
  565. "defaultValue": "\"m\""
  566. },
  567. "placement": {
  568. "type": "string",
  569. "mutable": false,
  570. "complexType": {
  571. "original": "PopperPlacement",
  572. "resolved": "Placement | PlacementRtl | VariationRtl",
  573. "references": {
  574. "PopperPlacement": {
  575. "location": "import",
  576. "path": "../../utils/popper"
  577. }
  578. }
  579. },
  580. "required": false,
  581. "optional": false,
  582. "docs": {
  583. "tags": [{
  584. "name": "see",
  585. "text": "[PopperPlacement](https://github.com/Esri/calcite-components/blob/master/src/utils/popper.ts#L25)"
  586. }],
  587. "text": "Determines where the popover will be positioned relative to the input."
  588. },
  589. "attribute": "placement",
  590. "reflect": true,
  591. "defaultValue": "\"auto\""
  592. },
  593. "step": {
  594. "type": "number",
  595. "mutable": false,
  596. "complexType": {
  597. "original": "number",
  598. "resolved": "number",
  599. "references": {}
  600. },
  601. "required": false,
  602. "optional": false,
  603. "docs": {
  604. "tags": [],
  605. "text": "number (seconds) that specifies the granularity that the value must adhere to"
  606. },
  607. "attribute": "step",
  608. "reflect": false,
  609. "defaultValue": "60"
  610. },
  611. "value": {
  612. "type": "string",
  613. "mutable": true,
  614. "complexType": {
  615. "original": "string",
  616. "resolved": "string",
  617. "references": {}
  618. },
  619. "required": false,
  620. "optional": false,
  621. "docs": {
  622. "tags": [],
  623. "text": "The selected time in UTC (always 24-hour format)"
  624. },
  625. "attribute": "value",
  626. "reflect": false,
  627. "defaultValue": "null"
  628. }
  629. }; }
  630. static get states() { return {
  631. "localizedValue": {}
  632. }; }
  633. static get events() { return [{
  634. "method": "calciteInputTimePickerChange",
  635. "name": "calciteInputTimePickerChange",
  636. "bubbles": true,
  637. "cancelable": true,
  638. "composed": true,
  639. "docs": {
  640. "tags": [],
  641. "text": "Fires when the time value is changed as a result of user input."
  642. },
  643. "complexType": {
  644. "original": "string",
  645. "resolved": "string",
  646. "references": {}
  647. }
  648. }]; }
  649. static get methods() { return {
  650. "setFocus": {
  651. "complexType": {
  652. "signature": "() => Promise<void>",
  653. "parameters": [],
  654. "references": {
  655. "Promise": {
  656. "location": "global"
  657. }
  658. },
  659. "return": "Promise<void>"
  660. },
  661. "docs": {
  662. "text": "Sets focus on the component.",
  663. "tags": []
  664. }
  665. },
  666. "reposition": {
  667. "complexType": {
  668. "signature": "() => Promise<void>",
  669. "parameters": [],
  670. "references": {
  671. "Promise": {
  672. "location": "global"
  673. }
  674. },
  675. "return": "Promise<void>"
  676. },
  677. "docs": {
  678. "text": "Updates the position of the component.",
  679. "tags": []
  680. }
  681. }
  682. }; }
  683. static get elementRef() { return "el"; }
  684. static get watchers() { return [{
  685. "propName": "active",
  686. "methodName": "activeHandler"
  687. }, {
  688. "propName": "disabled",
  689. "methodName": "handleDisabledChange"
  690. }, {
  691. "propName": "locale",
  692. "methodName": "localeWatcher"
  693. }, {
  694. "propName": "value",
  695. "methodName": "valueWatcher"
  696. }]; }
  697. static get listeners() { return [{
  698. "name": "click",
  699. "method": "clickHandler",
  700. "target": undefined,
  701. "capture": false,
  702. "passive": false
  703. }, {
  704. "name": "keyup",
  705. "method": "keyUpHandler",
  706. "target": undefined,
  707. "capture": false,
  708. "passive": false
  709. }, {
  710. "name": "calciteTimePickerBlur",
  711. "method": "timePickerBlurHandler",
  712. "target": undefined,
  713. "capture": false,
  714. "passive": false
  715. }, {
  716. "name": "calciteTimePickerFocus",
  717. "method": "timePickerFocusHandler",
  718. "target": undefined,
  719. "capture": false,
  720. "passive": false
  721. }]; }
  722. }