input-duration 3.1.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -17,28 +17,72 @@
17
17
 
18
18
  /**
19
19
  * Class for the Input-Duration webcomponent
20
+ *
21
+ * Boolean attributes `no-hours`, `no-minutes`, `no-seconds`, and
22
+ * `no-microseconds` hide their respective fields. String attributes
23
+ * `hours-divider`, `minutes-divider`, `seconds-divider`, and
24
+ * `microseconds-divider` customize the separators; the last adds a suffix
25
+ * after milliseconds. Divider values are rendered as text.
26
+ *
27
+ * @example
28
+ * <input-duration no-microseconds hours-divider="h " microseconds-divider="ms"></input-duration>
29
+ *
20
30
  * @class InputDuration
21
31
  * @extends HTMLElement
22
32
  */
23
33
 
24
34
  class InputDuration extends HTMLElement {
35
+ /**
36
+ * HTML attributes that update the component when changed.
37
+ *
38
+ * @static
39
+ * @returns {string[]} Observed value, visibility, and divider attributes.
40
+ */
25
41
  static get observedAttributes() {
26
- return ['value'];
42
+ return [
43
+ 'value',
44
+ 'no-hours',
45
+ 'no-minutes',
46
+ 'no-seconds',
47
+ 'no-microseconds',
48
+ 'hours-divider',
49
+ 'minutes-divider',
50
+ 'seconds-divider',
51
+ 'microseconds-divider'
52
+ ];
27
53
  }
28
54
 
29
55
  constructor() {
30
56
 
31
57
  super();
32
58
 
59
+ /**
60
+ * Shadow root containing this component's controls.
61
+ * @type {ShadowRoot}
62
+ */
33
63
  this.myElement = this.attachShadow({ mode: 'open' });
34
64
 
35
65
  this.boot();
36
66
 
37
67
  } // end constructor
38
68
 
69
+ /**
70
+ * Updates the rendered control when an observed attribute changes.
71
+ *
72
+ * @param {string} name - Changed attribute name.
73
+ * @param {?string} oldValue - Previous attribute value.
74
+ * @param {?string} newValue - New attribute value, or null when removed.
75
+ * @returns {void}
76
+ */
39
77
  attributeChangedCallback(name, oldValue, newValue) {
40
78
 
41
- if (name !== 'value' || oldValue === newValue) {
79
+ if (oldValue === newValue) {
80
+ return;
81
+ }
82
+
83
+ if (name !== 'value') {
84
+ this.updateVisibleFields();
85
+ this.updateDividers();
42
86
  return;
43
87
  }
44
88
 
@@ -55,7 +99,7 @@ class InputDuration extends HTMLElement {
55
99
  /**
56
100
  * get the input-duration value, pulls values from each input
57
101
  * so in js use var x = document.querySelector("#bobPrepop").value;
58
- * @return {duration}
102
+ * @returns {?string} Duration in `HH:mm:ss.SSS` format, or null before initialization.
59
103
  */
60
104
 
61
105
  get value() {
@@ -79,10 +123,10 @@ class InputDuration extends HTMLElement {
79
123
 
80
124
 
81
125
  /**
82
- * set the input-duration value
83
- * so in js use document.querySelector("#bobPrepop").value = "05:05:05.005"
84
- * @return {duration}
85
- * @param {duration} can be html attribute or set in js
126
+ * Sets the duration using the fixed `HH:mm:ss.SSS` format.
127
+ *
128
+ * @param {string} val - Duration string, such as `05:05:05.005`.
129
+ * @returns {void}
86
130
  */
87
131
 
88
132
  set value(val) {
@@ -106,11 +150,23 @@ class InputDuration extends HTMLElement {
106
150
  }
107
151
  }
108
152
 
153
+ /**
154
+ * Checks whether a value follows the supported duration format and ranges.
155
+ *
156
+ * @param {*} val - Candidate value.
157
+ * @returns {boolean} Whether the value is valid `HH:mm:ss.SSS`.
158
+ */
109
159
  isValidValue(val) {
110
160
  return typeof val === 'string' &&
111
161
  /^[0-9]{2}:[0-5][0-9]:[0-5][0-9]\.[0-9]{3}$/.test(val);
112
162
  }
113
163
 
164
+ /**
165
+ * Copies a validated duration string into the four shadow inputs.
166
+ *
167
+ * @param {string} val - Valid duration in `HH:mm:ss.SSS` format.
168
+ * @returns {void}
169
+ */
114
170
  setInputValues(val) {
115
171
  const timeArray = val.split(/[.:]+/);
116
172
  const inputNames = ['startHours', 'startMinutes', 'startSeconds', 'startMilliSecs'];
@@ -122,6 +178,11 @@ class InputDuration extends HTMLElement {
122
178
  });
123
179
  }
124
180
 
181
+ /**
182
+ * Resets all duration fields to zero.
183
+ *
184
+ * @returns {void}
185
+ */
125
186
  resetInputs() {
126
187
  const defaults = ['00', '00', '00', '000'];
127
188
  const inputs = this.myElement.querySelectorAll('.timeCase input');
@@ -132,6 +193,12 @@ class InputDuration extends HTMLElement {
132
193
  });
133
194
  }
134
195
 
196
+ /**
197
+ * Reports a value that does not match the supported duration format.
198
+ *
199
+ * @param {*} val - Rejected value.
200
+ * @returns {void}
201
+ */
135
202
  warnInvalidValue(val) {
136
203
  console.warn(`The specified value ${val} does not conform to the required format. The format is "HH:mm:ss.SSS" where HH is 00-99, mm is 00-59, ss is 00-59, and SSS is 000-999.`);
137
204
  }
@@ -249,6 +316,7 @@ class InputDuration extends HTMLElement {
249
316
  <input type="number" class="sS D2 ts_digit" part="field field-seconds" name="startSeconds" data-tp="3" min="-1" max="60" value="00">
250
317
  <span class="bds-s" part="divider divider-seconds-milliseconds">.</span>
251
318
  <input type="number" class="sMS D3 ts_digit" part="field field-milliseconds" name="startMilliSecs" data-tp="4" min="-10" max="1010" step="10" value="000">
319
+ <span class="bds-ms" part="divider divider-milliseconds-suffix" style="display: none"></span>
252
320
  <div id="svgContainer" part="icon">
253
321
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
254
322
  focusable="false" data-prefix="fal" data-icon="stopwatch" class="svg-inline--fa fa-stopwatch fa-w-14"
@@ -631,23 +699,217 @@ class InputDuration extends HTMLElement {
631
699
 
632
700
 
633
701
  /**
634
- * removes the hours, so the display is mm:ss.uuu
635
- * @method
702
+ * Hides the hours field and its adjacent divider.
703
+ * Sets the `no-hours` attribute.
704
+ *
705
+ * @returns {void}
636
706
  */
707
+ hideHours() {
708
+ this.setAttribute('no-hours', '');
709
+ }
710
+
711
+ /**
712
+ * Shows the hours field and its adjacent divider.
713
+ * Removes the `no-hours` attribute.
714
+ *
715
+ * @returns {void}
716
+ */
717
+ showHours() {
718
+ this.removeAttribute('no-hours');
719
+ }
637
720
 
721
+ /**
722
+ * Backward-compatible alias for {@link InputDuration#hideHours}.
723
+ *
724
+ * @returns {void}
725
+ */
638
726
  noHours() {
727
+ this.hideHours();
728
+ }
639
729
 
640
- var closure = this;
730
+ /**
731
+ * Hides the minutes field and its adjacent divider.
732
+ * Sets the `no-minutes` attribute.
733
+ *
734
+ * @returns {void}
735
+ */
736
+ hideMinutes() {
737
+ this.setAttribute('no-minutes', '');
738
+ }
739
+
740
+ /**
741
+ * Shows the minutes field and its adjacent divider.
742
+ * Removes the `no-minutes` attribute.
743
+ *
744
+ * @returns {void}
745
+ */
746
+ showMinutes() {
747
+ this.removeAttribute('no-minutes');
748
+ }
749
+
750
+ /**
751
+ * Backward-compatible alias for {@link InputDuration#hideMinutes}.
752
+ *
753
+ * @returns {void}
754
+ */
755
+ noMinutes() {
756
+ this.hideMinutes();
757
+ }
758
+
759
+ /**
760
+ * Hides the seconds field and its adjacent divider.
761
+ * Sets the `no-seconds` attribute.
762
+ *
763
+ * @returns {void}
764
+ */
765
+ hideSeconds() {
766
+ this.setAttribute('no-seconds', '');
767
+ }
768
+
769
+ /**
770
+ * Shows the seconds field and its adjacent divider.
771
+ * Removes the `no-seconds` attribute.
772
+ *
773
+ * @returns {void}
774
+ */
775
+ showSeconds() {
776
+ this.removeAttribute('no-seconds');
777
+ }
641
778
 
642
- // hide chronly hours inputs on page
779
+ /**
780
+ * Backward-compatible alias for {@link InputDuration#hideSeconds}.
781
+ *
782
+ * @returns {void}
783
+ */
784
+ noSeconds() {
785
+ this.hideSeconds();
786
+ }
643
787
 
644
- closure.shadowRoot.querySelectorAll(".timeCase input.sH").forEach(el => el.style.display = 'none');
788
+ /**
789
+ * Hides the milliseconds field and its adjacent divider or suffix.
790
+ * Sets the `no-microseconds` attribute.
791
+ *
792
+ * @returns {void}
793
+ */
794
+ hideMicroSeconds() {
795
+ this.setAttribute('no-microseconds', '');
796
+ }
645
797
 
798
+ /**
799
+ * Shows the milliseconds field and its adjacent divider or suffix.
800
+ * Removes the `no-microseconds` attribute.
801
+ *
802
+ * @returns {void}
803
+ */
804
+ showMicroSeconds() {
805
+ this.removeAttribute('no-microseconds');
806
+ }
646
807
 
647
- // hide the hours colon on page
808
+ /**
809
+ * Backward-compatible alias for {@link InputDuration#hideMicroSeconds}.
810
+ *
811
+ * @returns {void}
812
+ */
813
+ noMicroSeconds() {
814
+ this.hideMicroSeconds();
815
+ }
648
816
 
649
- closure.shadowRoot.querySelectorAll(".timeCase span.bds-h").forEach(el => el.style.display = 'none');
817
+ /**
818
+ * Replaces the hours/minutes separator.
819
+ * Also available through the `hours-divider` attribute.
820
+ *
821
+ * @param {string} text - Divider text to display.
822
+ * @returns {void}
823
+ */
824
+ setHoursDivider(text) {
825
+ this.setAttribute('hours-divider', text);
826
+ }
650
827
 
828
+ /**
829
+ * Replaces the minutes/seconds separator.
830
+ * Also available through the `minutes-divider` attribute.
831
+ *
832
+ * @param {string} text - Divider text to display.
833
+ * @returns {void}
834
+ */
835
+ setMinutesDivider(text) {
836
+ this.setAttribute('minutes-divider', text);
837
+ }
838
+
839
+ /**
840
+ * Replaces the seconds/milliseconds separator.
841
+ * Also available through the `seconds-divider` attribute.
842
+ *
843
+ * @param {string} text - Divider text to display.
844
+ * @returns {void}
845
+ */
846
+ setSecondsDivider(text) {
847
+ this.setAttribute('seconds-divider', text);
848
+ }
849
+
850
+ /**
851
+ * Sets a suffix to display after milliseconds.
852
+ * Also available through the `microseconds-divider` attribute.
853
+ *
854
+ * @param {string} text - Suffix text to display.
855
+ * @returns {void}
856
+ */
857
+ setMicroSecondsDivider(text) {
858
+ this.setAttribute('microseconds-divider', text);
859
+ }
860
+
861
+ /**
862
+ * Synchronizes field and divider visibility with the `no-*` attributes.
863
+ *
864
+ * @returns {void}
865
+ */
866
+ updateVisibleFields() {
867
+ const hiddenFields = {
868
+ 'no-hours': '.sH',
869
+ 'no-minutes': '.sM',
870
+ 'no-seconds': '.sS',
871
+ 'no-microseconds': '.sMS'
872
+ };
873
+ const hiddenDividers = {
874
+ '.bds-h': ['no-hours', 'no-minutes'],
875
+ '.bds-m': ['no-seconds'],
876
+ '.bds-s': ['no-microseconds'],
877
+ '.bds-ms': ['no-microseconds']
878
+ };
879
+
880
+ for (const [attribute, selector] of Object.entries(hiddenFields)) {
881
+ const field = this.myElement.querySelector(selector);
882
+ field.style.display = this.hasAttribute(attribute) ? 'none' : '';
883
+ }
884
+
885
+ for (const [selector, attributes] of Object.entries(hiddenDividers)) {
886
+ const divider = this.myElement.querySelector(selector);
887
+ const shouldHide = attributes.some(attribute => this.hasAttribute(attribute));
888
+ divider.style.display = shouldHide ? 'none' : '';
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Synchronizes divider text with the corresponding divider attributes.
894
+ *
895
+ * @returns {void}
896
+ */
897
+ updateDividers() {
898
+ const dividers = {
899
+ 'hours-divider': ['.bds-h', ':'],
900
+ 'minutes-divider': ['.bds-m', ':'],
901
+ 'seconds-divider': ['.bds-s', '.'],
902
+ 'microseconds-divider': ['.bds-ms', '']
903
+ };
904
+
905
+ for (const [attribute, [selector, defaultText]] of Object.entries(dividers)) {
906
+ const divider = this.myElement.querySelector(selector);
907
+ const text = this.getAttribute(attribute) ?? defaultText;
908
+ divider.textContent = text;
909
+ if (selector === '.bds-ms') {
910
+ divider.style.display = text && !this.hasAttribute('no-microseconds') ? '' : 'none';
911
+ }
912
+ }
651
913
  }
652
914
  }
653
915
  // end class
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "input-duration",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "author": "Stephan Luis <stephan@learnsense.org>",
5
5
  "description": "A JavaScript web component for entering durations up to 99 hours in a fixed HH:mm:ss.SSS format, with millisecond precision for countdown timers and elapsed time.",
6
6
  "browser": "./index.js",
@@ -17,7 +17,7 @@
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/StephanLuis/input-duration.git"
19
19
  },
20
- "license": "ISC",
20
+ "license": "MIT",
21
21
  "devDependencies": {
22
22
  "jsdom": "^26.1.0"
23
23
  },