input-duration 3.0.3 → 3.2.0

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
  }
@@ -237,18 +304,20 @@ class InputDuration extends HTMLElement {
237
304
  var spanIn = document.createElement("div");
238
305
 
239
306
  spanIn.classList.add('timeCase');
307
+ spanIn.setAttribute('part', 'control');
240
308
 
241
309
  var template = document.createElement('template');
242
310
 
243
311
  template.innerHTML = `
244
- <input type="number" class="sH D2 ts_digit" name="startHours" data-tp="1" min="-1" max="100" value="00" >
245
- <span class="bds-h">:</span>
246
- <input type="number" class="sM D2 ts_digit" name="startMinutes" data-tp="2" min="-1" max="60" value="00">
247
- <span class="bds-m">:</span>
248
- <input type="number" class="sS D2 ts_digit" name="startSeconds" data-tp="3" min="-1" max="60" value="00">
249
- <span class="bds-s">.</span>
250
- <input type="number" class="sMS D3 ts_digit" name="startMilliSecs" data-tp="4" min="-10" max="1010" step="10" value="000">
251
- <div id="svgContainer">
312
+ <input type="number" class="sH D2 ts_digit" part="field field-hours" name="startHours" data-tp="1" min="-1" max="100" value="00" >
313
+ <span class="bds-h" part="divider divider-hours-minutes">:</span>
314
+ <input type="number" class="sM D2 ts_digit" part="field field-minutes" name="startMinutes" data-tp="2" min="-1" max="60" value="00">
315
+ <span class="bds-m" part="divider divider-minutes-seconds">:</span>
316
+ <input type="number" class="sS D2 ts_digit" part="field field-seconds" name="startSeconds" data-tp="3" min="-1" max="60" value="00">
317
+ <span class="bds-s" part="divider divider-seconds-milliseconds">.</span>
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>
320
+ <div id="svgContainer" part="icon">
252
321
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
253
322
  focusable="false" data-prefix="fal" data-icon="stopwatch" class="svg-inline--fa fa-stopwatch fa-w-14"
254
323
  role="img" viewBox="0 0 448 512">
@@ -261,27 +330,41 @@ class InputDuration extends HTMLElement {
261
330
  color: inherit;
262
331
  font: inherit;
263
332
  vertical-align: middle;
333
+ --input-duration-gap: 0.08em;
334
+ --input-duration-padding: 0.4em 0.55em;
335
+ --input-duration-border-color: #9aa6b2;
336
+ --input-duration-border-radius: 0.4em;
337
+ --input-duration-background: transparent;
338
+ --input-duration-color: inherit;
339
+ --input-duration-focus-color: #2196f3;
340
+ --input-duration-focus-outline: 3px solid #2196f3;
341
+ --input-duration-focus-outline-offset: 2px;
342
+ --input-duration-divider-color: currentColor;
343
+ --input-duration-divider-opacity: 0.7;
344
+ --input-duration-disabled-color: GrayText;
345
+ --input-duration-icon-color: inherit;
264
346
  }
265
347
 
266
- .timeCase {
348
+ .timeCase,
349
+ .timeCase[part~="control"] {
267
350
  display: inline-flex;
268
351
  align-items: center;
269
- gap: 0.08em;
352
+ gap: var(--input-duration-gap);
270
353
  max-width: 100%;
271
- padding: 0.4em 0.55em;
272
- border: 1px solid #9aa6b2;
273
- border-radius: 0.4em;
274
- background: transparent;
275
- color: inherit;
354
+ padding: var(--input-duration-padding);
355
+ border: 1px solid var(--input-duration-border-color);
356
+ border-radius: var(--input-duration-border-radius);
357
+ background: var(--input-duration-background);
358
+ color: var(--input-duration-color);
276
359
  font-variant-numeric: tabular-nums;
277
360
  line-height: 1.4;
278
361
  white-space: nowrap;
279
362
  }
280
363
 
281
364
  .timeCase:focus-within {
282
- border-color: #2196f3;
283
- outline: 3px solid #2196f3;
284
- outline-offset: 2px;
365
+ border-color: var(--input-duration-focus-color);
366
+ outline: var(--input-duration-focus-outline);
367
+ outline-offset: var(--input-duration-focus-outline-offset);
285
368
  }
286
369
 
287
370
  .timeCase input {
@@ -313,8 +396,8 @@ class InputDuration extends HTMLElement {
313
396
  }
314
397
 
315
398
  .timeCase span {
316
- color: currentColor;
317
- opacity: 0.7;
399
+ color: var(--input-duration-divider-color);
400
+ opacity: var(--input-duration-divider-opacity);
318
401
  }
319
402
 
320
403
  #svgContainer {
@@ -325,7 +408,7 @@ class InputDuration extends HTMLElement {
325
408
  align-items: center;
326
409
  justify-content: center;
327
410
  margin-left: 0.2em;
328
- color: inherit;
411
+ color: var(--input-duration-icon-color);
329
412
  }
330
413
 
331
414
  #svgContainer svg {
@@ -336,7 +419,7 @@ class InputDuration extends HTMLElement {
336
419
 
337
420
  .timeCase input:disabled {
338
421
  background: transparent;
339
- color: GrayText;
422
+ color: var(--input-duration-disabled-color);
340
423
  cursor: not-allowed;
341
424
  opacity: 0.65;
342
425
  }
@@ -616,23 +699,217 @@ class InputDuration extends HTMLElement {
616
699
 
617
700
 
618
701
  /**
619
- * removes the hours, so the display is mm:ss.uuu
620
- * @method
702
+ * Hides the hours field and its adjacent divider.
703
+ * Sets the `no-hours` attribute.
704
+ *
705
+ * @returns {void}
621
706
  */
707
+ hideHours() {
708
+ this.setAttribute('no-hours', '');
709
+ }
622
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
+ }
720
+
721
+ /**
722
+ * Backward-compatible alias for {@link InputDuration#hideHours}.
723
+ *
724
+ * @returns {void}
725
+ */
623
726
  noHours() {
727
+ this.hideHours();
728
+ }
624
729
 
625
- 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
+ }
626
739
 
627
- // hide chronly hours inputs on page
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
+ }
628
749
 
629
- closure.shadowRoot.querySelectorAll(".timeCase input.sH").forEach(el => el.style.display = 'none');
750
+ /**
751
+ * Backward-compatible alias for {@link InputDuration#hideMinutes}.
752
+ *
753
+ * @returns {void}
754
+ */
755
+ noMinutes() {
756
+ this.hideMinutes();
757
+ }
630
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
+ }
631
768
 
632
- // hide the hours colon on page
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
+ }
633
778
 
634
- closure.shadowRoot.querySelectorAll(".timeCase span.bds-h").forEach(el => el.style.display = 'none');
779
+ /**
780
+ * Backward-compatible alias for {@link InputDuration#hideSeconds}.
781
+ *
782
+ * @returns {void}
783
+ */
784
+ noSeconds() {
785
+ this.hideSeconds();
786
+ }
787
+
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
+ }
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
+ }
807
+
808
+ /**
809
+ * Backward-compatible alias for {@link InputDuration#hideMicroSeconds}.
810
+ *
811
+ * @returns {void}
812
+ */
813
+ noMicroSeconds() {
814
+ this.hideMicroSeconds();
815
+ }
816
+
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
+ }
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
+ }
635
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
+ }
636
913
  }
637
914
  }
638
915
  // end class
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "input-duration",
3
- "version": "3.0.3",
3
+ "version": "3.2.0",
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",
Binary file
Binary file
@@ -0,0 +1 @@
1
+ x
Binary file
Binary file
Binary file
Binary file