neo.mjs 5.10.12 → 5.11.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.
@@ -75,6 +75,14 @@ class DateSelector extends Component {
75
75
  * @member {String} locale_=Neo.config.locale
76
76
  */
77
77
  locale_: Neo.config.locale,
78
+ /**
79
+ * @member {String|null} maxValue_=null
80
+ */
81
+ maxValue_: null,
82
+ /**
83
+ * @member {String|null} minValue_=null
84
+ */
85
+ minValue_: null,
78
86
  /**
79
87
  * Used for wheel events. min value = 1.
80
88
  * A higher value means lesser sensitivity for wheel events
@@ -241,6 +249,26 @@ class DateSelector extends Component {
241
249
  }
242
250
  }
243
251
 
252
+ /**
253
+ * Triggered after the maxValue config got changed
254
+ * @param {Text} value
255
+ * @param {Text} oldValue
256
+ * @protected
257
+ */
258
+ afterSetMaxValue(value, oldValue) {
259
+ oldValue !== undefined && this.recreateDayViewContent()
260
+ }
261
+
262
+ /**
263
+ * Triggered after the minValue config got changed
264
+ * @param {Text} value
265
+ * @param {Text} oldValue
266
+ * @protected
267
+ */
268
+ afterSetMinValue(value, oldValue) {
269
+ oldValue !== undefined && this.recreateDayViewContent()
270
+ }
271
+
244
272
  /**
245
273
  * Triggered after the showCellBorders config got changed
246
274
  * @param {Boolean} value
@@ -262,7 +290,7 @@ class DateSelector extends Component {
262
290
  * @protected
263
291
  */
264
292
  afterSetShowDisabledDays(value, oldValue) {
265
- oldValue !== undefined && this.recreateDayViewContent();
293
+ oldValue !== undefined && this.recreateDayViewContent()
266
294
  }
267
295
 
268
296
  /**
@@ -566,6 +594,8 @@ class DateSelector extends Component {
566
594
  currentMonth = currentDate.getMonth(),
567
595
  currentYear = currentDate.getFullYear(),
568
596
  date = me.currentDate, // cloned
597
+ maxDate = me.maxValue && new Date(`${me.maxValue}T00:00:00.000Z`),
598
+ minDate = me.minValue && new Date(`${me.minValue}T00:00:00.000Z`),
569
599
  valueDate = new Date(`${me.value}T00:00:00.000Z`),
570
600
  valueMonth = valueDate.getMonth(),
571
601
  valueYear = valueDate.getFullYear(),
@@ -612,23 +642,27 @@ class DateSelector extends Component {
612
642
  config.cls.push('neo-weekend');
613
643
  }
614
644
 
645
+ if (maxDate && date > maxDate || minDate && date < minDate) {
646
+ NeoArray.add(config.cls, 'neo-disabled')
647
+ }
648
+
615
649
  if (today.year === currentYear && today.month === currentMonth && today.day === day) {
616
- config.cn[0].cls.push('neo-today');
650
+ config.cn[0].cls.push('neo-today')
617
651
  }
618
652
 
619
653
  if (valueYear === currentYear && valueMonth === currentMonth && day === currentDay) {
620
654
  config.cls.push('neo-selected');
621
- me.selectionModel.items = [cellId]; // silent update
655
+ me.selectionModel.items = [cellId] // silent update
622
656
  }
623
657
 
624
658
  row.cn.push(config);
625
659
 
626
660
  date.setDate(date.getDate() + 1);
627
661
 
628
- day++;
662
+ day++
629
663
  }
630
664
 
631
- centerEl.cn.push(row);
665
+ centerEl.cn.push(row)
632
666
  }
633
667
 
634
668
  !silent && me.update()
@@ -638,7 +672,7 @@ class DateSelector extends Component {
638
672
  *
639
673
  */
640
674
  focusCurrentItem() {
641
- this.focus(this.selectionModel.items[0]);
675
+ this.focus(this.selectionModel.items[0])
642
676
  }
643
677
 
644
678
  /**
@@ -752,7 +786,7 @@ class DateSelector extends Component {
752
786
  */
753
787
  onConstructed() {
754
788
  super.onConstructed();
755
- this.selectionModel?.register(this);
789
+ this.selectionModel?.register(this)
756
790
  }
757
791
 
758
792
  /**
@@ -777,7 +811,7 @@ class DateSelector extends Component {
777
811
  monthIncrement !== 0 && me.updateHeaderMonth(monthIncrement, yearIncrement, true);
778
812
  yearIncrement !== 0 && me.updateHeaderYear(yearIncrement, true);
779
813
 
780
- me.triggerVdomUpdate(silent);
814
+ me.triggerVdomUpdate(silent)
781
815
  }
782
816
 
783
817
  /**
@@ -793,10 +827,10 @@ class DateSelector extends Component {
793
827
  me.createDayViewContent(true);
794
828
 
795
829
  if (syncIds) {
796
- me.syncVdomIds();
830
+ me.syncVdomIds()
797
831
  }
798
832
 
799
- me.triggerVdomUpdate(silent);
833
+ me.triggerVdomUpdate(silent)
800
834
  }
801
835
 
802
836
  /**
@@ -0,0 +1,328 @@
1
+ import Component from './Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
3
+
4
+ /**
5
+ * @class Neo.component.Timer
6
+ * @extends Neo.component.Base
7
+ */
8
+ class Timer extends Component {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Neo.component.Timer'
12
+ * @protected
13
+ */
14
+ className: 'Neo.component.Timer',
15
+ /**
16
+ * @member {String} ntype='timer'
17
+ * @protected
18
+ */
19
+ ntype: 'timer',
20
+ /**
21
+ * CSS selectors to apply to the root level node of this component
22
+ * @member {String[]} baseCls=['timer']
23
+ */
24
+ baseCls: ['neo-timer'],
25
+ /**
26
+ * End color of the circle. If not set, it uses the css default
27
+ * @member {Number|String} colorEnd_=null
28
+ */
29
+ colorEnd_: null,
30
+ /**
31
+ * Start color of the circle. If not set, it uses the css default
32
+ * @member {Number|String} colorStart_=null
33
+ */
34
+ colorStart_: null,
35
+ /**
36
+ * Start time. This might be '5m', '30s' or milliseconds as number
37
+ * @member {Number|String} duration_='5m'
38
+ */
39
+ duration_: '10m',
40
+ /**
41
+ * Defines height and min-width. This can be a number in px or a string.
42
+ * @member {Number|String} dimensions_='6rem'
43
+ */
44
+ dimensions_: '8em',
45
+ /**
46
+ * Helper to keep running smooth at minimum cost
47
+ * @member {Object} timer={}
48
+ * @member {Number|null} timer.currentSecond =null // run only once per second
49
+ * @member {Number|null} timer.intervalId =null // setInterval id
50
+ * @member {Boolean} timer.running =false// keeps track if timer/entry is up
51
+ * @member {Number|null} timer.startTime =null // calc the current progress
52
+ */
53
+ timer: {
54
+ currentSecond: null,
55
+ intervalId : null,
56
+ running : false,
57
+ startTime : null
58
+ },
59
+ /**
60
+ * The vdom markup for this component.
61
+ * @member {Object} vdom={}
62
+ */
63
+ vdom: {
64
+ cn: [
65
+ {cls: 'countdown', cn : [
66
+ {tag: 'svg', cls: 'clock', viewBox: "-50 -50 100 100", strokeWidth: "10", cn: [
67
+ {tag: 'circle', r: 45},
68
+ {tag: 'circle', r: 45,pathLength: 1}
69
+ ]},
70
+ {cls: ['flip-card'], cn : [
71
+ {cls: 'flip-card-inner enter-mask', cn : [
72
+ {cls: 'flip-card-front', cn : [
73
+ {tag: 'input', cls: 'enter-time'},
74
+ {tag: 'button',cls: 'fa fa-play'}
75
+ ]},
76
+ {cls: 'flip-card-back', cn : [
77
+ {cls : 'runner', html: '00:00'}
78
+ ]}
79
+ ]}
80
+ ]}
81
+ ]}
82
+ ]
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @param {Object} config
88
+ */
89
+ construct(config) {
90
+ super.construct(config);
91
+
92
+ let me = this;
93
+
94
+ me.addDomListeners([
95
+ {click: me.onTimerClick, delegate: 'flip-card-back'},
96
+ {click: me.onTimerClick, delegate: 'fa fa-play'},
97
+ {input: me.onTimerInput, delegate: 'enter-time'},
98
+ {focusout: me.onTimerInput, delegate: 'enter-time'},
99
+ {keydown: me.onFieldKeyDown, delegate: 'enter-time'}
100
+ ]);
101
+ }
102
+
103
+ /**
104
+ * Triggered after the dimensions config got changed
105
+ * @param {Number|String} value
106
+ * @param {Number|String} oldValue
107
+ * @protected
108
+ */
109
+ afterSetDimensions(value, oldValue) {
110
+ if (typeof value === 'number') {
111
+ value = value + 'px';
112
+ }
113
+ this.updateProperties({dimensions: value});
114
+ }
115
+
116
+ /**
117
+ * Triggered after the colorStart config got changed
118
+ * @param {String|null} value
119
+ * @param {String|null} oldValue
120
+ * @protected
121
+ */
122
+ afterSetColorStart(value, oldValue) {
123
+ if (!value) return;
124
+ this.updateProperties({colorStart: value});
125
+ }
126
+
127
+ /**
128
+ * Triggered after the colorEnd config got changed
129
+ * @param {String|null} value
130
+ * @param {String|null} oldValue
131
+ * @protected
132
+ */
133
+ afterSetColorEnd(value, oldValue) {
134
+ if (!value) return;
135
+ this.updateProperties({colorEnd: value});
136
+ }
137
+
138
+ /**
139
+ * Triggered before the duration config got changed
140
+ * @param {Number|String} value
141
+ * @param {Number|String} oldValue
142
+ * @protected
143
+ */
144
+ beforeSetDuration(value, oldValue) {
145
+ const me = this;
146
+
147
+ me.updateInputField(value)
148
+
149
+ if (typeof value === 'string') {
150
+ const durationType = value.at(-1);
151
+
152
+ if (durationType === 'm') {
153
+ value = value.split('m')[0] * 60 * 1000;
154
+ } else if (durationType === 's') {
155
+ value = value.split('s')[0] * 1000;
156
+ }
157
+ }
158
+
159
+ me.updateProperties({full: value});
160
+
161
+ return value;
162
+ }
163
+
164
+ /**
165
+ * Check if Enter was pressed
166
+ * @param {Object} data
167
+ */
168
+ onFieldKeyDown(data) {
169
+ const me = this;
170
+
171
+ if (data.key === 'Enter') {
172
+ me.duration = me.timer.entry;
173
+ me.onTimerClick();
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Click on Play or Timer
179
+ */
180
+ onTimerClick() {
181
+ const me = this;
182
+
183
+ // If the timer is running, stop and clear it
184
+ if (me.timer.intervalId) {
185
+ me.toggleTimer(false);
186
+ me.resetTimer();
187
+ } else {
188
+ // prepare
189
+ me.timer.startTime = new Date().getTime();
190
+
191
+ me.timer.intervalId = setInterval(function () {
192
+ const startTime = me.timer.startTime,
193
+ curTime = new Date().getTime(),
194
+ totalTime = me.duration,
195
+ endTime = startTime + totalTime;
196
+
197
+ if (curTime > endTime) {
198
+ me.toggleTimer(false);
199
+ me.resetTimer();
200
+ } else {
201
+ const milliseconds = endTime - curTime,
202
+ secondsLeft = Math.floor(milliseconds / 1000);
203
+ let secondsNow = secondsLeft % 60,
204
+ minutesNow = Math.floor(secondsLeft / 60)
205
+
206
+ // Ensure this does not run 10 times a second
207
+ if (secondsNow !== me.timer.currentSecond) {
208
+ me.timer.currentSecond = secondsNow;
209
+
210
+ secondsNow = secondsNow.toString().padStart(2, '0');
211
+ minutesNow = minutesNow.toString().padStart(2, '0');
212
+
213
+ me.updateTimer(`${minutesNow}:${secondsNow}`);
214
+ me.updateProperties({current: milliseconds});
215
+ me.toggleTimer(true);
216
+ }
217
+ }
218
+ }, 100);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * On change event of the textfield
224
+ * @param {Object} data
225
+ */
226
+ onTimerInput(data) {
227
+ const me = this;
228
+
229
+ if (data.value) {
230
+ me.timer.entry = data.value
231
+ } else {
232
+ me.duration = me.timer.entry;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Reset the properties, timer and remove Interval
238
+ */
239
+ resetTimer() {
240
+ const me = this;
241
+
242
+ me.updateProperties({current: ''});
243
+ me.updateTimer('00:00');
244
+
245
+ clearInterval(me.timer.intervalId);
246
+ delete me.timer.intervalId;
247
+ }
248
+
249
+ /**
250
+ * Flip over the timer face
251
+ * @param {Boolean} doShow
252
+ */
253
+ toggleTimer(doShow) {
254
+ if(this.running === doShow) return;
255
+
256
+ let me = this,
257
+ turnFn = doShow ? 'add' : 'remove',
258
+ vdom = me.vdom,
259
+ flipCard = vdom.cn[0].cn[1];
260
+
261
+ me.running = doShow;
262
+
263
+ flipCard.cls = flipCard.cls || [];
264
+
265
+ NeoArray[turnFn](flipCard.cls, 'turn');
266
+ me.vdom = vdom;
267
+ }
268
+
269
+ /**
270
+ * Write to the input field
271
+ * @param {String} value
272
+ */
273
+ updateInputField(value) {
274
+ let me = this,
275
+ vdom = me.vdom,
276
+ inputField = vdom.cn[0].cn[1].cn[0].cn[0].cn[0];
277
+
278
+ inputField.value = value;
279
+ }
280
+
281
+ /**
282
+ * Update the timer, typically once per second
283
+ * @param {String} value
284
+ */
285
+ updateTimer(value) {
286
+ let me = this,
287
+ vdom = me.vdom,
288
+ timer = vdom.cn[0].cn[1].cn[0].cn[1].cn[0];
289
+
290
+ timer.innerHTML = value;
291
+ me.vdom = vdom;
292
+ }
293
+
294
+ /**
295
+ * Update the css properties
296
+ * - current amount of seconds left
297
+ * - full amount of time
298
+ * - size of the timer
299
+ * @param {Object} properties
300
+ */
301
+ updateProperties(properties) {
302
+ // Neo.setCssVariable({key: '--neo-timer-full', value: '\'200\''});
303
+ let me = this,
304
+ style = me.style;
305
+
306
+ if (properties.current !== undefined) {
307
+ style['--neo-timer-current'] = `${properties.current}!important`;
308
+ }
309
+ if (properties.full !== undefined) {
310
+ style['--neo-timer-full'] = `${properties.full}!important`;
311
+ }
312
+ if (properties.colorEnd !== undefined) {
313
+ style['--timer-color-end'] = `${properties.colorEnd}!important`;
314
+ }
315
+ if (properties.colorStart !== undefined) {
316
+ style['--timer-color-start'] = `${properties.colorStart}!important`;
317
+ }
318
+ if (properties.dimensions !== undefined) {
319
+ style['--timer-dimension'] = `${properties.dimensions}!important`;
320
+ }
321
+
322
+ me.style = style;
323
+ }
324
+ }
325
+
326
+ Neo.applyClassConfig(Timer);
327
+
328
+ export default Timer;
@@ -42,6 +42,14 @@ class DateField extends Picker {
42
42
  * @member {String} inputType='date'
43
43
  */
44
44
  inputType: 'date',
45
+ /**
46
+ * @member {String|null} maxValue_=null
47
+ */
48
+ maxValue_: null,
49
+ /**
50
+ * @member {String|null} minValue_=null
51
+ */
52
+ minValue_: null,
45
53
  /**
46
54
  * @member {Number} pickerHeight=225
47
55
  */
@@ -79,6 +87,8 @@ class DateField extends Picker {
79
87
 
80
88
  me.dateSelector = Neo.create(DateSelector, {
81
89
  dayNameFormat: 'short',
90
+ maxValue : me.maxValue,
91
+ minValue : me.minValue,
82
92
  value : me.value || DateUtil.convertToyyyymmdd(new Date()),
83
93
  ...me.dateSelectorConfig
84
94
  });
@@ -93,6 +103,38 @@ class DateField extends Picker {
93
103
  });
94
104
  }
95
105
 
106
+ /**
107
+ * Triggered after the maxValue config got changed
108
+ * @param {Text} value
109
+ * @param {Text} oldValue
110
+ * @protected
111
+ */
112
+ afterSetMaxValue(value, oldValue) {
113
+ let me = this;
114
+
115
+ me.changeInputElKey('max', value);
116
+
117
+ if (me.dateSelector) {
118
+ me.dateSelector.maxValue = value
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Triggered after the minValue config got changed
124
+ * @param {Text} value
125
+ * @param {Text} oldValue
126
+ * @protected
127
+ */
128
+ afterSetMinValue(value, oldValue) {
129
+ let me = this;
130
+
131
+ me.changeInputElKey('max', value);
132
+
133
+ if (me.dateSelector) {
134
+ me.dateSelector.minValue = value
135
+ }
136
+ }
137
+
96
138
  /**
97
139
  * Triggered after the value config got changed
98
140
  * @param {String} value
@@ -115,9 +115,9 @@ class Text extends Base {
115
115
  * If false, the inputPattern will only get validated via JavaScript, but not getting applied on DOM level.
116
116
  * The regex support for input based patterns is not fully there yet, so feel free to disable this feature
117
117
  * if needed (E.g. form.field.Phone).
118
- * @member {Boolean} inputPatternDOM=true
118
+ * @member {Boolean} inputPatternDOM_=true
119
119
  */
120
- inputPatternDOM: true,
120
+ inputPatternDOM_: true,
121
121
  /**
122
122
  * @member {String} inputType_='text'
123
123
  */
@@ -56,6 +56,11 @@ class Flexbox extends Base {
56
56
  * @member {String|null} direction_=null
57
57
  */
58
58
  direction_: null,
59
+ /**
60
+ * flex css allows gap. This adds it to the component style
61
+ * @member {String} gap_=null
62
+ */
63
+ gap_: null,
59
64
  /**
60
65
  * Valid values: 'center', 'end', 'start', null
61
66
  * @member {String|null} pack_=null
@@ -93,6 +98,22 @@ class Flexbox extends Base {
93
98
  oldValue && this.updateInputValue(value, oldValue, 'direction');
94
99
  }
95
100
 
101
+ /**
102
+ * Updates the Container style to add a gap to display:flex
103
+ * @param {String|null} value
104
+ * @param {String|null} oldValue
105
+ * @protected
106
+ */
107
+ afterSetGap(value, oldValue) {
108
+ if (!value && !oldValue) return;
109
+
110
+ let item = Neo.getComponent(this.containerId),
111
+ style = item.wrapperStyle;
112
+
113
+ style.gap = value;
114
+ item.wrapperStyle = style;
115
+ }
116
+
96
117
  /**
97
118
  * Updates the Container CSS cls after "pack" gets changed
98
119
  * @param {String|null} value
@@ -0,0 +1,140 @@
1
+ import Base from './Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
3
+
4
+ /**
5
+ * @class Neo.layout.Form
6
+ * @extends Neo.layout.Base
7
+ */
8
+ class Form extends Base {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Neo.layout.Form'
12
+ * @protected
13
+ */
14
+ className: 'Neo.layout.Form',
15
+ /**
16
+ * @member {String} ntype='layout-form'
17
+ * @protected
18
+ */
19
+ ntype: 'layout-form',
20
+ /**
21
+ * flex css allows gap. This adds it to the component style
22
+ * @member {String} gap_=null
23
+ */
24
+ gap_: null,
25
+ /**
26
+ * CSS className prefix
27
+ * @member {String} prefix='neo-form-'
28
+ */
29
+ prefix: 'neo-layout-form-'
30
+ }
31
+
32
+ /**
33
+ * Updates the Container style to add a gap to display:flex
34
+ * @param {String|null} value
35
+ * @param {String|null} oldValue
36
+ * @protected
37
+ */
38
+ afterSetGap(value, oldValue) {
39
+ if (!value && !oldValue) return;
40
+
41
+ let item = Neo.getComponent(this.containerId),
42
+ style = item.wrapperStyle;
43
+
44
+ style.gap = value;
45
+ item.wrapperStyle = style;
46
+ }
47
+
48
+ /**
49
+ * Applies the flex value to an item of the container this layout is bound to
50
+ * @param {Neo.component.Base} item
51
+ * @param {Number} index
52
+ */
53
+ applyChildAttributes(child, index) {
54
+ if (!child.ignoreLayout) {
55
+ if (child.ntype === 'fieldset') {
56
+ child.wrapperCls = NeoArray.union(child.wrapperCls, 'neo-layout-form-subfieldset');
57
+ } else if (child.ntype === 'legend') {
58
+ child.wrapperCls = NeoArray.union(child.wrapperCls, 'neo-layout-form-legend');
59
+ } else {
60
+ child.wrapperCls = NeoArray.union(child.wrapperCls, 'neo-layout-form-item');
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Applies CSS classes to the container this layout is bound to
67
+ */
68
+ applyRenderAttributes() {
69
+ let me = this,
70
+ container = Neo.getComponent(me.containerId),
71
+ wrapperCls = container?.wrapperCls || [];
72
+
73
+ if (!container) {
74
+ Neo.logError('layout.Form: applyRenderAttributes -> container not yet created', me.containerId);
75
+ }
76
+
77
+ NeoArray.add(wrapperCls, 'neo-layout-form');
78
+
79
+ container.wrapperCls = wrapperCls;
80
+ }
81
+
82
+ /**
83
+ * Removes all CSS rules from an container item this layout is bound to.
84
+ * Gets called when switching to a different layout.
85
+ * @param {Neo.component.Base} item
86
+ * @protected
87
+ */
88
+ removeChildAttributes(item) {
89
+ let style = item.wrapperStyle || {};
90
+
91
+ style.flex = item.flex || null;
92
+ item.wrapperStyle = style;
93
+ }
94
+
95
+ /**
96
+ * Removes all CSS rules from the container this layout is bound to.
97
+ * Gets called when switching to a different layout.
98
+ */
99
+ removeRenderAttributes() {
100
+ let me = this,
101
+ container = Neo.getComponent(me.containerId),
102
+ wrapperCls = container?.wrapperCls || [];
103
+
104
+ if (!container) {
105
+ Neo.logError('layout.Form: removeRenderAttributes -> container not yet created', me.containerId);
106
+ }
107
+
108
+ NeoArray.remove(wrapperCls, 'neo-layout-form');
109
+
110
+ container.wrapperCls = wrapperCls;
111
+ }
112
+
113
+ /**
114
+ * Updates the Container CSS wrapperCls
115
+ * @param {String|null} value
116
+ * @param {String|null} oldValue
117
+ * @param {String} propertyName
118
+ * @protected
119
+ */
120
+ updateInputValue(value, oldValue, propertyName) {
121
+ let me = this,
122
+ container = Neo.getComponent(me.containerId),
123
+ prefix = me.prefix,
124
+ wrapperCls = container?.wrapperCls;
125
+
126
+ if (container?.rendered) {
127
+ NeoArray.remove(wrapperCls, prefix + propertyName + '-' + oldValue);
128
+
129
+ if (value !== null) {
130
+ NeoArray.add(wrapperCls, prefix + propertyName + '-' + value);
131
+ }
132
+
133
+ container.wrapperCls = wrapperCls;
134
+ }
135
+ }
136
+ }
137
+
138
+ Neo.applyClassConfig(Form);
139
+
140
+ export default Form;