mithril-materialized 3.5.10 → 3.7.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/dist/analog-clock.d.ts +57 -0
- package/dist/components.css +0 -1
- package/dist/core.css +4 -0
- package/dist/digital-clock.d.ts +48 -0
- package/dist/dropdown.d.ts +2 -0
- package/dist/forms.css +4 -0
- package/dist/index.css +176 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.js +1469 -456
- package/dist/index.js +1481 -455
- package/dist/index.min.css +1 -1
- package/dist/index.umd.js +1481 -455
- package/dist/pickers.css +170 -0
- package/dist/search-select.d.ts +12 -1
- package/dist/select.d.ts +5 -0
- package/dist/time-range-picker.d.ts +51 -0
- package/dist/time-utils.d.ts +17 -0
- package/dist/timepicker.d.ts +11 -0
- package/dist/utils.d.ts +12 -0
- package/package.json +1 -1
- package/sass/components/_collapsible.scss +0 -1
- package/sass/components/_timepicker.scss +196 -1
- package/sass/components/forms/_select.scss +20 -16
package/dist/index.umd.js
CHANGED
|
@@ -66,6 +66,28 @@
|
|
|
66
66
|
};
|
|
67
67
|
/** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
|
|
68
68
|
const isNumeric = (n) => !isNaN(parseFloat(n)) && isFinite(n);
|
|
69
|
+
/**
|
|
70
|
+
* Sort options array based on sorting configuration
|
|
71
|
+
* @param options - Array of options to sort
|
|
72
|
+
* @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
|
|
73
|
+
* @returns Sorted array (or original if 'none' or undefined)
|
|
74
|
+
*/
|
|
75
|
+
const sortOptions = (options, sortConfig) => {
|
|
76
|
+
if (!sortConfig || sortConfig === 'none') {
|
|
77
|
+
return options;
|
|
78
|
+
}
|
|
79
|
+
const sorted = [...options]; // Create a copy to avoid mutating original
|
|
80
|
+
if (typeof sortConfig === 'function') {
|
|
81
|
+
return sorted.sort(sortConfig);
|
|
82
|
+
}
|
|
83
|
+
// Sort by label, fallback to id if no label
|
|
84
|
+
return sorted.sort((a, b) => {
|
|
85
|
+
const aLabel = (a.label || a.id.toString()).toLowerCase();
|
|
86
|
+
const bLabel = (b.label || b.id.toString()).toLowerCase();
|
|
87
|
+
const comparison = aLabel.localeCompare(bLabel);
|
|
88
|
+
return sortConfig === 'asc' ? comparison : -comparison;
|
|
89
|
+
});
|
|
90
|
+
};
|
|
69
91
|
/**
|
|
70
92
|
* Pad left, default width 2 with a '0'
|
|
71
93
|
*
|
|
@@ -1588,7 +1610,7 @@
|
|
|
1588
1610
|
};
|
|
1589
1611
|
};
|
|
1590
1612
|
|
|
1591
|
-
const defaultI18n$
|
|
1613
|
+
const defaultI18n$4 = {
|
|
1592
1614
|
cancel: 'Cancel',
|
|
1593
1615
|
clear: 'Clear',
|
|
1594
1616
|
done: 'Ok',
|
|
@@ -1696,9 +1718,9 @@
|
|
|
1696
1718
|
else if (attrs.displayFormat) {
|
|
1697
1719
|
finalFormat = attrs.displayFormat;
|
|
1698
1720
|
}
|
|
1699
|
-
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$
|
|
1721
|
+
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$4, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1700
1722
|
// Merge i18n properly
|
|
1701
|
-
merged.i18n = Object.assign(Object.assign({}, defaultI18n$
|
|
1723
|
+
merged.i18n = Object.assign(Object.assign({}, defaultI18n$4), attrs.i18n);
|
|
1702
1724
|
return merged;
|
|
1703
1725
|
};
|
|
1704
1726
|
const toString = (date, format) => {
|
|
@@ -4499,7 +4521,7 @@
|
|
|
4499
4521
|
opacity: 1,
|
|
4500
4522
|
};
|
|
4501
4523
|
};
|
|
4502
|
-
const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
|
|
4524
|
+
const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
|
|
4503
4525
|
if (!state.isInsideModal)
|
|
4504
4526
|
return;
|
|
4505
4527
|
// Clean up existing portal
|
|
@@ -4543,7 +4565,7 @@
|
|
|
4543
4565
|
// Create dropdown with proper positioning
|
|
4544
4566
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
4545
4567
|
tabindex: 0,
|
|
4546
|
-
style: getPortalStyles(state.inputRef),
|
|
4568
|
+
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
|
|
4547
4569
|
oncreate: ({ dom }) => {
|
|
4548
4570
|
state.dropdownRef = dom;
|
|
4549
4571
|
},
|
|
@@ -4607,7 +4629,7 @@
|
|
|
4607
4629
|
state.focusedIndex = -1;
|
|
4608
4630
|
handleSelection(item.id);
|
|
4609
4631
|
}
|
|
4610
|
-
});
|
|
4632
|
+
}, attrs.maxHeight);
|
|
4611
4633
|
}
|
|
4612
4634
|
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
4613
4635
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -4655,7 +4677,7 @@
|
|
|
4655
4677
|
onremove: () => {
|
|
4656
4678
|
state.dropdownRef = null;
|
|
4657
4679
|
},
|
|
4658
|
-
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4680
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, items, true)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
4659
4681
|
}, items.map((item) => {
|
|
4660
4682
|
if (item.divider) {
|
|
4661
4683
|
return m('li.divider');
|
|
@@ -5276,45 +5298,417 @@
|
|
|
5276
5298
|
};
|
|
5277
5299
|
};
|
|
5278
5300
|
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5301
|
+
/**
|
|
5302
|
+
* Shared utility functions for TimePicker and TimeRangePicker components
|
|
5303
|
+
*/
|
|
5304
|
+
const addLeadingZero = (num) => {
|
|
5305
|
+
return (num < 10 ? '0' : '') + num;
|
|
5283
5306
|
};
|
|
5284
|
-
const
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5307
|
+
const parseTime = (timeStr, twelveHour) => {
|
|
5308
|
+
if (!timeStr) {
|
|
5309
|
+
return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
|
|
5310
|
+
}
|
|
5311
|
+
const parts = timeStr.trim().split(/[:\s]+/);
|
|
5312
|
+
if (parts.length < 2) {
|
|
5313
|
+
return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
|
|
5314
|
+
}
|
|
5315
|
+
let hours = parseInt(parts[0]) || 0;
|
|
5316
|
+
const minutes = parseInt(parts[1]) || 0;
|
|
5317
|
+
let amOrPm = 'AM';
|
|
5318
|
+
const upperStr = timeStr.toUpperCase();
|
|
5319
|
+
if (upperStr.includes('PM')) {
|
|
5320
|
+
amOrPm = 'PM';
|
|
5321
|
+
}
|
|
5322
|
+
else if (upperStr.includes('AM')) {
|
|
5323
|
+
amOrPm = 'AM';
|
|
5324
|
+
}
|
|
5325
|
+
if (twelveHour) {
|
|
5326
|
+
if (hours >= 12) {
|
|
5327
|
+
amOrPm = 'PM';
|
|
5328
|
+
if (hours > 12)
|
|
5329
|
+
hours -= 12;
|
|
5330
|
+
}
|
|
5331
|
+
else if (hours === 0) {
|
|
5332
|
+
hours = 12;
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
return { hours, minutes, amOrPm };
|
|
5336
|
+
};
|
|
5337
|
+
const formatTime = (time, twelveHour) => {
|
|
5338
|
+
if (!time)
|
|
5339
|
+
return '';
|
|
5340
|
+
const { hours, minutes, amOrPm } = time;
|
|
5341
|
+
const hoursStr = addLeadingZero(hours);
|
|
5342
|
+
const minutesStr = addLeadingZero(minutes);
|
|
5343
|
+
if (twelveHour) {
|
|
5344
|
+
return `${hoursStr}:${minutesStr} ${amOrPm}`;
|
|
5345
|
+
}
|
|
5346
|
+
let hours24 = hours;
|
|
5347
|
+
if (amOrPm === 'PM' && hours !== 12) {
|
|
5348
|
+
hours24 = hours + 12;
|
|
5349
|
+
}
|
|
5350
|
+
else if (amOrPm === 'AM' && hours === 12) {
|
|
5351
|
+
hours24 = 0;
|
|
5352
|
+
}
|
|
5353
|
+
return `${addLeadingZero(hours24)}:${minutesStr}`;
|
|
5354
|
+
};
|
|
5355
|
+
const timeToMinutes = (time, twelveHour) => {
|
|
5356
|
+
let h = time.hours;
|
|
5357
|
+
if (twelveHour) {
|
|
5358
|
+
if (time.amOrPm === 'PM' && h !== 12)
|
|
5359
|
+
h += 12;
|
|
5360
|
+
if (time.amOrPm === 'AM' && h === 12)
|
|
5361
|
+
h = 0;
|
|
5362
|
+
}
|
|
5363
|
+
return h * 60 + time.minutes;
|
|
5364
|
+
};
|
|
5365
|
+
const generateHourOptions = (twelveHour, hourStep) => {
|
|
5366
|
+
const start = twelveHour ? 1 : 0;
|
|
5367
|
+
const end = twelveHour ? 12 : 23;
|
|
5368
|
+
const hours = [];
|
|
5369
|
+
for (let i = start; i <= end; i += hourStep) {
|
|
5370
|
+
hours.push(i);
|
|
5371
|
+
}
|
|
5372
|
+
// Special case for 12-hour: include 12 first
|
|
5373
|
+
if (twelveHour && hourStep === 1) {
|
|
5374
|
+
return [12, ...hours.filter((h) => h !== 12)];
|
|
5375
|
+
}
|
|
5376
|
+
return hours;
|
|
5377
|
+
};
|
|
5378
|
+
const generateMinuteOptions = (minuteStep) => {
|
|
5379
|
+
const minutes = [];
|
|
5380
|
+
for (let i = 0; i < 60; i += minuteStep) {
|
|
5381
|
+
minutes.push(i);
|
|
5382
|
+
}
|
|
5383
|
+
return minutes;
|
|
5384
|
+
};
|
|
5385
|
+
const isTimeDisabled = (hours, minutes, amOrPm, minTime, maxTime, twelveHour) => {
|
|
5386
|
+
if (!minTime && !maxTime)
|
|
5387
|
+
return false;
|
|
5388
|
+
const currentMins = timeToMinutes({ hours, minutes, amOrPm }, twelveHour || false);
|
|
5389
|
+
if (minTime) {
|
|
5390
|
+
const minParsed = parseTime(minTime, twelveHour || false);
|
|
5391
|
+
const minMins = timeToMinutes(minParsed, twelveHour || false);
|
|
5392
|
+
if (currentMins < minMins)
|
|
5393
|
+
return true;
|
|
5394
|
+
}
|
|
5395
|
+
if (maxTime) {
|
|
5396
|
+
const maxParsed = parseTime(maxTime, twelveHour || false);
|
|
5397
|
+
const maxMins = timeToMinutes(maxParsed, twelveHour || false);
|
|
5398
|
+
if (currentMins > maxMins)
|
|
5399
|
+
return true;
|
|
5400
|
+
}
|
|
5401
|
+
return false;
|
|
5402
|
+
};
|
|
5403
|
+
const scrollToValue = (container, index, itemHeight, animated = true) => {
|
|
5404
|
+
const scrollTop = index * itemHeight - (container.clientHeight / 2 - itemHeight / 2);
|
|
5405
|
+
if (animated) {
|
|
5406
|
+
container.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
|
5407
|
+
}
|
|
5408
|
+
else {
|
|
5409
|
+
container.scrollTop = scrollTop;
|
|
5410
|
+
}
|
|
5305
5411
|
};
|
|
5412
|
+
const snapToNearestItem = (container, itemHeight, onSnap) => {
|
|
5413
|
+
const scrollTop = container.scrollTop;
|
|
5414
|
+
const centerOffset = container.clientHeight / 2;
|
|
5415
|
+
const nearestIndex = Math.round((scrollTop + centerOffset - itemHeight / 2) / itemHeight);
|
|
5416
|
+
scrollToValue(container, nearestIndex, itemHeight, true);
|
|
5417
|
+
onSnap(nearestIndex);
|
|
5418
|
+
};
|
|
5419
|
+
|
|
5306
5420
|
/**
|
|
5307
|
-
*
|
|
5421
|
+
* DigitalClock component - A scrollable digital time picker
|
|
5422
|
+
*
|
|
5423
|
+
* @example
|
|
5424
|
+
* ```typescript
|
|
5425
|
+
* m(DigitalClock, {
|
|
5426
|
+
* hours: 10,
|
|
5427
|
+
* minutes: 30,
|
|
5428
|
+
* amOrPm: 'AM',
|
|
5429
|
+
* twelveHour: true,
|
|
5430
|
+
* minuteStep: 5,
|
|
5431
|
+
* onTimeChange: (hours, minutes, amOrPm) => {
|
|
5432
|
+
* console.log(`Time changed to ${hours}:${minutes} ${amOrPm}`);
|
|
5433
|
+
* }
|
|
5434
|
+
* })
|
|
5435
|
+
* ```
|
|
5308
5436
|
*/
|
|
5309
|
-
const
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5437
|
+
const DigitalClock = () => {
|
|
5438
|
+
const ITEM_HEIGHT = 48;
|
|
5439
|
+
const state = {};
|
|
5440
|
+
return {
|
|
5441
|
+
view: ({ attrs }) => {
|
|
5442
|
+
const { hours, minutes, amOrPm, twelveHour, minuteStep = 5, hourStep = 1, minTime, maxTime, onTimeChange, spanHours, spanMinutes, spanAmPm, } = attrs;
|
|
5443
|
+
const hourOptions = generateHourOptions(twelveHour, hourStep);
|
|
5444
|
+
const minuteOptions = generateMinuteOptions(minuteStep);
|
|
5445
|
+
return m('.timepicker-digital-clock', [
|
|
5446
|
+
// Hours column
|
|
5447
|
+
m('.digital-clock-column', {
|
|
5448
|
+
oncreate: (vnode) => {
|
|
5449
|
+
state.hourScrollContainer = vnode.dom;
|
|
5450
|
+
const currentIndex = hourOptions.indexOf(hours);
|
|
5451
|
+
if (currentIndex >= 0) {
|
|
5452
|
+
scrollToValue(state.hourScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5453
|
+
}
|
|
5454
|
+
},
|
|
5455
|
+
onwheel: (e) => {
|
|
5456
|
+
e.preventDefault();
|
|
5457
|
+
if (!state.hourScrollContainer)
|
|
5458
|
+
return;
|
|
5459
|
+
const delta = Math.sign(e.deltaY);
|
|
5460
|
+
const currentIndex = hourOptions.indexOf(hours);
|
|
5461
|
+
const newIndex = Math.max(0, Math.min(hourOptions.length - 1, currentIndex + delta));
|
|
5462
|
+
const newHour = hourOptions[newIndex];
|
|
5463
|
+
if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5464
|
+
onTimeChange(newHour, minutes, amOrPm);
|
|
5465
|
+
if (spanHours) {
|
|
5466
|
+
spanHours.innerHTML = addLeadingZero(newHour);
|
|
5467
|
+
}
|
|
5468
|
+
scrollToValue(state.hourScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5469
|
+
m.redraw();
|
|
5470
|
+
}
|
|
5471
|
+
},
|
|
5472
|
+
onscroll: () => {
|
|
5473
|
+
if (state.hourScrollTimeout) {
|
|
5474
|
+
clearTimeout(state.hourScrollTimeout);
|
|
5475
|
+
}
|
|
5476
|
+
state.hourScrollTimeout = window.setTimeout(() => {
|
|
5477
|
+
if (!state.hourScrollContainer)
|
|
5478
|
+
return;
|
|
5479
|
+
snapToNearestItem(state.hourScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5480
|
+
const actualIndex = index - 2; // Account for padding
|
|
5481
|
+
if (actualIndex >= 0 && actualIndex < hourOptions.length) {
|
|
5482
|
+
const newHour = hourOptions[actualIndex];
|
|
5483
|
+
if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5484
|
+
onTimeChange(newHour, minutes, amOrPm);
|
|
5485
|
+
if (spanHours) {
|
|
5486
|
+
spanHours.innerHTML = addLeadingZero(newHour);
|
|
5487
|
+
}
|
|
5488
|
+
m.redraw();
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
});
|
|
5492
|
+
}, 150);
|
|
5493
|
+
},
|
|
5494
|
+
}, [
|
|
5495
|
+
// Padding items for centering
|
|
5496
|
+
m('.digital-clock-item.padding'),
|
|
5497
|
+
m('.digital-clock-item.padding'),
|
|
5498
|
+
// Hour items
|
|
5499
|
+
...hourOptions.map((hour) => {
|
|
5500
|
+
const disabled = isTimeDisabled(hour, minutes, amOrPm, minTime, maxTime, twelveHour);
|
|
5501
|
+
return m('.digital-clock-item', {
|
|
5502
|
+
class: `${hour === hours ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5503
|
+
onclick: () => {
|
|
5504
|
+
if (disabled)
|
|
5505
|
+
return;
|
|
5506
|
+
onTimeChange(hour, minutes, amOrPm);
|
|
5507
|
+
if (spanHours) {
|
|
5508
|
+
spanHours.innerHTML = addLeadingZero(hour);
|
|
5509
|
+
}
|
|
5510
|
+
if (state.hourScrollContainer) {
|
|
5511
|
+
const index = hourOptions.indexOf(hour);
|
|
5512
|
+
scrollToValue(state.hourScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5513
|
+
}
|
|
5514
|
+
m.redraw();
|
|
5515
|
+
},
|
|
5516
|
+
}, addLeadingZero(hour));
|
|
5517
|
+
}),
|
|
5518
|
+
// Padding items for centering
|
|
5519
|
+
m('.digital-clock-item.padding'),
|
|
5520
|
+
m('.digital-clock-item.padding'),
|
|
5521
|
+
]),
|
|
5522
|
+
// Separator
|
|
5523
|
+
m('.digital-clock-separator', ':'),
|
|
5524
|
+
// Minutes column
|
|
5525
|
+
m('.digital-clock-column', {
|
|
5526
|
+
oncreate: (vnode) => {
|
|
5527
|
+
state.minuteScrollContainer = vnode.dom;
|
|
5528
|
+
const currentIndex = minuteOptions.indexOf(minutes);
|
|
5529
|
+
if (currentIndex >= 0) {
|
|
5530
|
+
scrollToValue(state.minuteScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5531
|
+
}
|
|
5532
|
+
},
|
|
5533
|
+
onwheel: (e) => {
|
|
5534
|
+
e.preventDefault();
|
|
5535
|
+
if (!state.minuteScrollContainer)
|
|
5536
|
+
return;
|
|
5537
|
+
const delta = Math.sign(e.deltaY);
|
|
5538
|
+
const currentIndex = minuteOptions.indexOf(minutes);
|
|
5539
|
+
const newIndex = Math.max(0, Math.min(minuteOptions.length - 1, currentIndex + delta));
|
|
5540
|
+
const newMinute = minuteOptions[newIndex];
|
|
5541
|
+
if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5542
|
+
onTimeChange(hours, newMinute, amOrPm);
|
|
5543
|
+
if (spanMinutes) {
|
|
5544
|
+
spanMinutes.innerHTML = addLeadingZero(newMinute);
|
|
5545
|
+
}
|
|
5546
|
+
scrollToValue(state.minuteScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5547
|
+
m.redraw();
|
|
5548
|
+
}
|
|
5549
|
+
},
|
|
5550
|
+
onscroll: () => {
|
|
5551
|
+
if (state.minuteScrollTimeout) {
|
|
5552
|
+
clearTimeout(state.minuteScrollTimeout);
|
|
5553
|
+
}
|
|
5554
|
+
state.minuteScrollTimeout = window.setTimeout(() => {
|
|
5555
|
+
if (!state.minuteScrollContainer)
|
|
5556
|
+
return;
|
|
5557
|
+
snapToNearestItem(state.minuteScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5558
|
+
const actualIndex = index - 2; // Account for padding
|
|
5559
|
+
if (actualIndex >= 0 && actualIndex < minuteOptions.length) {
|
|
5560
|
+
const newMinute = minuteOptions[actualIndex];
|
|
5561
|
+
if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5562
|
+
onTimeChange(hours, newMinute, amOrPm);
|
|
5563
|
+
if (spanMinutes) {
|
|
5564
|
+
spanMinutes.innerHTML = addLeadingZero(newMinute);
|
|
5565
|
+
}
|
|
5566
|
+
m.redraw();
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
});
|
|
5570
|
+
}, 150);
|
|
5571
|
+
},
|
|
5572
|
+
}, [
|
|
5573
|
+
// Padding items for centering
|
|
5574
|
+
m('.digital-clock-item.padding'),
|
|
5575
|
+
m('.digital-clock-item.padding'),
|
|
5576
|
+
// Minute items
|
|
5577
|
+
...minuteOptions.map((minute) => {
|
|
5578
|
+
const disabled = isTimeDisabled(hours, minute, amOrPm, minTime, maxTime, twelveHour);
|
|
5579
|
+
return m('.digital-clock-item', {
|
|
5580
|
+
class: `${minute === minutes ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5581
|
+
onclick: () => {
|
|
5582
|
+
if (disabled)
|
|
5583
|
+
return;
|
|
5584
|
+
onTimeChange(hours, minute, amOrPm);
|
|
5585
|
+
if (spanMinutes) {
|
|
5586
|
+
spanMinutes.innerHTML = addLeadingZero(minute);
|
|
5587
|
+
}
|
|
5588
|
+
if (state.minuteScrollContainer) {
|
|
5589
|
+
const index = minuteOptions.indexOf(minute);
|
|
5590
|
+
scrollToValue(state.minuteScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5591
|
+
}
|
|
5592
|
+
m.redraw();
|
|
5593
|
+
},
|
|
5594
|
+
}, addLeadingZero(minute));
|
|
5595
|
+
}),
|
|
5596
|
+
// Padding items for centering
|
|
5597
|
+
m('.digital-clock-item.padding'),
|
|
5598
|
+
m('.digital-clock-item.padding'),
|
|
5599
|
+
]),
|
|
5600
|
+
// AM/PM column (if twelveHour)
|
|
5601
|
+
twelveHour &&
|
|
5602
|
+
m('.digital-clock-column.ampm-column', {
|
|
5603
|
+
oncreate: (vnode) => {
|
|
5604
|
+
state.amPmScrollContainer = vnode.dom;
|
|
5605
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5606
|
+
const currentIndex = amPmOptions.indexOf(amOrPm);
|
|
5607
|
+
if (currentIndex >= 0) {
|
|
5608
|
+
scrollToValue(state.amPmScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5609
|
+
}
|
|
5610
|
+
},
|
|
5611
|
+
onwheel: (e) => {
|
|
5612
|
+
e.preventDefault();
|
|
5613
|
+
const delta = Math.sign(e.deltaY);
|
|
5614
|
+
const newAmPm = delta > 0 ? 'PM' : 'AM';
|
|
5615
|
+
if (newAmPm !== amOrPm && !isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
|
|
5616
|
+
onTimeChange(hours, minutes, newAmPm);
|
|
5617
|
+
if (spanAmPm) {
|
|
5618
|
+
spanAmPm.innerHTML = newAmPm;
|
|
5619
|
+
}
|
|
5620
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5621
|
+
const newIndex = amPmOptions.indexOf(newAmPm);
|
|
5622
|
+
if (state.amPmScrollContainer) {
|
|
5623
|
+
scrollToValue(state.amPmScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5624
|
+
}
|
|
5625
|
+
m.redraw();
|
|
5626
|
+
}
|
|
5627
|
+
},
|
|
5628
|
+
onscroll: () => {
|
|
5629
|
+
if (state.amPmScrollTimeout) {
|
|
5630
|
+
clearTimeout(state.amPmScrollTimeout);
|
|
5631
|
+
}
|
|
5632
|
+
state.amPmScrollTimeout = window.setTimeout(() => {
|
|
5633
|
+
if (!state.amPmScrollContainer)
|
|
5634
|
+
return;
|
|
5635
|
+
snapToNearestItem(state.amPmScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5636
|
+
const actualIndex = index - 2;
|
|
5637
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5638
|
+
if (actualIndex >= 0 && actualIndex < amPmOptions.length) {
|
|
5639
|
+
const newAmPm = amPmOptions[actualIndex];
|
|
5640
|
+
if (!isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
|
|
5641
|
+
onTimeChange(hours, minutes, newAmPm);
|
|
5642
|
+
if (spanAmPm) {
|
|
5643
|
+
spanAmPm.innerHTML = newAmPm;
|
|
5644
|
+
}
|
|
5645
|
+
m.redraw();
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
});
|
|
5649
|
+
}, 150);
|
|
5650
|
+
},
|
|
5651
|
+
}, [
|
|
5652
|
+
// Padding items
|
|
5653
|
+
m('.digital-clock-item.padding'),
|
|
5654
|
+
m('.digital-clock-item.padding'),
|
|
5655
|
+
// AM/PM items
|
|
5656
|
+
['AM', 'PM'].map((ampm) => {
|
|
5657
|
+
const disabled = isTimeDisabled(hours, minutes, ampm, minTime, maxTime, twelveHour);
|
|
5658
|
+
return m('.digital-clock-item', {
|
|
5659
|
+
class: `${ampm === amOrPm ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5660
|
+
onclick: () => {
|
|
5661
|
+
if (disabled)
|
|
5662
|
+
return;
|
|
5663
|
+
onTimeChange(hours, minutes, ampm);
|
|
5664
|
+
if (spanAmPm) {
|
|
5665
|
+
spanAmPm.innerHTML = ampm;
|
|
5666
|
+
}
|
|
5667
|
+
if (state.amPmScrollContainer) {
|
|
5668
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5669
|
+
const index = amPmOptions.indexOf(ampm);
|
|
5670
|
+
scrollToValue(state.amPmScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5671
|
+
}
|
|
5672
|
+
m.redraw();
|
|
5673
|
+
},
|
|
5674
|
+
}, ampm);
|
|
5675
|
+
}),
|
|
5676
|
+
// Padding items
|
|
5677
|
+
m('.digital-clock-item.padding'),
|
|
5678
|
+
m('.digital-clock-item.padding'),
|
|
5679
|
+
]),
|
|
5680
|
+
]);
|
|
5681
|
+
},
|
|
5314
5682
|
};
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5683
|
+
};
|
|
5684
|
+
|
|
5685
|
+
/**
|
|
5686
|
+
* AnalogClock component - A draggable analog clock face for time selection
|
|
5687
|
+
*
|
|
5688
|
+
* @example
|
|
5689
|
+
* ```typescript
|
|
5690
|
+
* m(AnalogClock, {
|
|
5691
|
+
* hours: 10,
|
|
5692
|
+
* minutes: 30,
|
|
5693
|
+
* amOrPm: 'AM',
|
|
5694
|
+
* currentView: 'hours',
|
|
5695
|
+
* twelveHour: true,
|
|
5696
|
+
* onTimeChange: (hours, minutes) => {
|
|
5697
|
+
* console.log(`Time changed to ${hours}:${minutes}`);
|
|
5698
|
+
* },
|
|
5699
|
+
* onViewChange: (view) => {
|
|
5700
|
+
* console.log(`View changed to ${view}`);
|
|
5701
|
+
* }
|
|
5702
|
+
* })
|
|
5703
|
+
* ```
|
|
5704
|
+
*/
|
|
5705
|
+
const AnalogClock = () => {
|
|
5706
|
+
const state = {
|
|
5707
|
+
moved: false,
|
|
5708
|
+
x0: 0,
|
|
5709
|
+
y0: 0,
|
|
5710
|
+
dx: 0,
|
|
5711
|
+
dy: 0,
|
|
5318
5712
|
};
|
|
5319
5713
|
const getPos = (e) => {
|
|
5320
5714
|
const touchEvent = e;
|
|
@@ -5324,321 +5718,422 @@
|
|
|
5324
5718
|
}
|
|
5325
5719
|
return { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
5326
5720
|
};
|
|
5327
|
-
const vibrate = () => {
|
|
5721
|
+
const vibrate = (attrs) => {
|
|
5328
5722
|
if (state.vibrateTimer) {
|
|
5329
5723
|
clearTimeout(state.vibrateTimer);
|
|
5330
5724
|
}
|
|
5331
|
-
if (
|
|
5725
|
+
if (attrs.vibrate && navigator.vibrate) {
|
|
5332
5726
|
navigator.vibrate(10);
|
|
5333
5727
|
state.vibrateTimer = window.setTimeout(() => {
|
|
5334
5728
|
state.vibrateTimer = undefined;
|
|
5335
5729
|
}, 100);
|
|
5336
5730
|
}
|
|
5337
5731
|
};
|
|
5338
|
-
const
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
const
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
setHand(state.dx, state.dy, options.roundBy5);
|
|
5351
|
-
document.addEventListener('mousemove', handleDocumentClickMove);
|
|
5352
|
-
document.addEventListener('touchmove', handleDocumentClickMove);
|
|
5353
|
-
document.addEventListener('mouseup', handleDocumentClickEnd);
|
|
5354
|
-
document.addEventListener('touchend', handleDocumentClickEnd);
|
|
5355
|
-
};
|
|
5356
|
-
const handleDocumentClickMove = (e) => {
|
|
5357
|
-
e.preventDefault();
|
|
5358
|
-
const clickPos = getPos(e);
|
|
5359
|
-
const x = clickPos.x - state.x0;
|
|
5360
|
-
const y = clickPos.y - state.y0;
|
|
5361
|
-
state.moved = true;
|
|
5362
|
-
setHand(x, y, options.roundBy5);
|
|
5363
|
-
m.redraw();
|
|
5364
|
-
};
|
|
5365
|
-
const handleDocumentClickEnd = (e) => {
|
|
5366
|
-
e.preventDefault();
|
|
5367
|
-
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
5368
|
-
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
5369
|
-
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
5370
|
-
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
5371
|
-
const clickPos = getPos(e);
|
|
5372
|
-
const x = clickPos.x - state.x0;
|
|
5373
|
-
const y = clickPos.y - state.y0;
|
|
5374
|
-
if (state.moved && x === state.dx && y === state.dy) {
|
|
5375
|
-
setHand(x, y);
|
|
5732
|
+
const setHand = (x, y, attrs, roundBy5, _dragging) => {
|
|
5733
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5734
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5735
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5736
|
+
let radian = Math.atan2(x, -y);
|
|
5737
|
+
const isHours = attrs.currentView === 'hours';
|
|
5738
|
+
const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
|
|
5739
|
+
const z = Math.sqrt(x * x + y * y);
|
|
5740
|
+
const inner = isHours && z < (outerRadius + innerRadius) / 2;
|
|
5741
|
+
let radius = inner ? innerRadius : outerRadius;
|
|
5742
|
+
if (attrs.twelveHour) {
|
|
5743
|
+
radius = outerRadius;
|
|
5376
5744
|
}
|
|
5377
|
-
if (
|
|
5378
|
-
|
|
5745
|
+
if (radian < 0) {
|
|
5746
|
+
radian = Math.PI * 2 + radian;
|
|
5379
5747
|
}
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5748
|
+
let value = Math.round(radian / unit);
|
|
5749
|
+
radian = value * unit;
|
|
5750
|
+
if (attrs.twelveHour) {
|
|
5751
|
+
if (isHours) {
|
|
5752
|
+
if (value === 0)
|
|
5753
|
+
value = 12;
|
|
5754
|
+
}
|
|
5755
|
+
else {
|
|
5756
|
+
if (roundBy5)
|
|
5757
|
+
value *= 5;
|
|
5758
|
+
if (value === 60)
|
|
5759
|
+
value = 0;
|
|
5383
5760
|
}
|
|
5384
|
-
setTimeout(() => {
|
|
5385
|
-
done();
|
|
5386
|
-
}, options.duration / 2);
|
|
5387
|
-
}
|
|
5388
|
-
if (options.onSelect) {
|
|
5389
|
-
options.onSelect(state.hours, state.minutes);
|
|
5390
5761
|
}
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
if (options.twelveHour && value.length > 1) {
|
|
5397
|
-
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
5398
|
-
state.amOrPm = 'AM';
|
|
5399
|
-
amPmWasProvided = true;
|
|
5762
|
+
else {
|
|
5763
|
+
if (isHours) {
|
|
5764
|
+
if (value === 12)
|
|
5765
|
+
value = 0;
|
|
5766
|
+
value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
|
|
5400
5767
|
}
|
|
5401
|
-
else
|
|
5402
|
-
|
|
5403
|
-
|
|
5768
|
+
else {
|
|
5769
|
+
if (roundBy5)
|
|
5770
|
+
value *= 5;
|
|
5771
|
+
if (value === 60)
|
|
5772
|
+
value = 0;
|
|
5404
5773
|
}
|
|
5405
|
-
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
5406
5774
|
}
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
if (options.twelveHour) {
|
|
5411
|
-
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
5412
|
-
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
5413
|
-
}
|
|
5775
|
+
const currentValue = isHours ? attrs.hours : attrs.minutes;
|
|
5776
|
+
if (currentValue !== value) {
|
|
5777
|
+
vibrate(attrs);
|
|
5414
5778
|
}
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
if (
|
|
5419
|
-
|
|
5420
|
-
if (hours >= 12) {
|
|
5421
|
-
state.amOrPm = 'PM';
|
|
5422
|
-
if (hours > 12) {
|
|
5423
|
-
hours = hours - 12;
|
|
5424
|
-
}
|
|
5425
|
-
}
|
|
5426
|
-
else {
|
|
5427
|
-
state.amOrPm = 'AM';
|
|
5428
|
-
if (hours === 0) {
|
|
5429
|
-
hours = 12;
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
else {
|
|
5434
|
-
// AM/PM was provided, hours are already in 12-hour format
|
|
5435
|
-
// Just handle midnight/noon edge cases
|
|
5436
|
-
if (hours === 0 && state.amOrPm === 'AM') {
|
|
5437
|
-
hours = 12;
|
|
5438
|
-
}
|
|
5779
|
+
// Update the value
|
|
5780
|
+
if (isHours) {
|
|
5781
|
+
attrs.onTimeChange(value, attrs.minutes);
|
|
5782
|
+
if (attrs.spanHours) {
|
|
5783
|
+
attrs.spanHours.innerHTML = addLeadingZero(value);
|
|
5439
5784
|
}
|
|
5440
5785
|
}
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5786
|
+
else {
|
|
5787
|
+
attrs.onTimeChange(attrs.hours, value);
|
|
5788
|
+
if (attrs.spanMinutes) {
|
|
5789
|
+
attrs.spanMinutes.innerHTML = addLeadingZero(value);
|
|
5790
|
+
}
|
|
5445
5791
|
}
|
|
5446
|
-
|
|
5447
|
-
|
|
5792
|
+
// Set clock hand position
|
|
5793
|
+
if (state.hand && state.bg) {
|
|
5794
|
+
const cx1 = Math.sin(radian) * (radius - tickRadius);
|
|
5795
|
+
const cy1 = -Math.cos(radian) * (radius - tickRadius);
|
|
5796
|
+
const cx2 = Math.sin(radian) * radius;
|
|
5797
|
+
const cy2 = -Math.cos(radian) * radius;
|
|
5798
|
+
state.hand.setAttribute('x2', cx1.toString());
|
|
5799
|
+
state.hand.setAttribute('y2', cy1.toString());
|
|
5800
|
+
state.bg.setAttribute('cx', cx2.toString());
|
|
5801
|
+
state.bg.setAttribute('cy', cy2.toString());
|
|
5448
5802
|
}
|
|
5449
|
-
updateAmPmView();
|
|
5450
5803
|
};
|
|
5451
|
-
const
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
|
|
5455
|
-
}
|
|
5456
|
-
};
|
|
5457
|
-
const showView = (view, delay) => {
|
|
5458
|
-
const isHours = view === 'hours';
|
|
5459
|
-
const nextView = isHours ? state.hoursView : state.minutesView;
|
|
5460
|
-
const hideView = isHours ? state.minutesView : state.hoursView;
|
|
5461
|
-
state.currentView = view;
|
|
5462
|
-
if (state.spanHours) {
|
|
5463
|
-
state.spanHours.classList.toggle('text-primary', isHours);
|
|
5464
|
-
}
|
|
5465
|
-
if (state.spanMinutes) {
|
|
5466
|
-
state.spanMinutes.classList.toggle('text-primary', !isHours);
|
|
5467
|
-
}
|
|
5468
|
-
if (hideView) {
|
|
5469
|
-
hideView.classList.add('timepicker-dial-out');
|
|
5470
|
-
}
|
|
5471
|
-
if (nextView) {
|
|
5472
|
-
nextView.style.visibility = 'visible';
|
|
5473
|
-
nextView.classList.remove('timepicker-dial-out');
|
|
5474
|
-
}
|
|
5475
|
-
resetClock(delay);
|
|
5476
|
-
if (state.toggleViewTimer) {
|
|
5477
|
-
clearTimeout(state.toggleViewTimer);
|
|
5478
|
-
}
|
|
5479
|
-
state.toggleViewTimer = window.setTimeout(() => {
|
|
5480
|
-
if (hideView) {
|
|
5481
|
-
hideView.style.visibility = 'hidden';
|
|
5482
|
-
}
|
|
5483
|
-
}, options.duration);
|
|
5484
|
-
};
|
|
5485
|
-
const resetClock = (delay) => {
|
|
5486
|
-
const view = state.currentView;
|
|
5487
|
-
const value = state[view];
|
|
5804
|
+
const resetClock = (attrs) => {
|
|
5805
|
+
const view = attrs.currentView;
|
|
5806
|
+
const value = view === 'hours' ? attrs.hours : attrs.minutes;
|
|
5488
5807
|
const isHours = view === 'hours';
|
|
5489
5808
|
const unit = Math.PI / (isHours ? 6 : 30);
|
|
5490
5809
|
const radian = value * unit;
|
|
5491
|
-
const
|
|
5810
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5811
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5812
|
+
// In 12-hour mode, always use outer radius
|
|
5813
|
+
// In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
|
|
5814
|
+
let radius = outerRadius;
|
|
5815
|
+
if (!attrs.twelveHour && isHours && value > 0 && value < 13) {
|
|
5816
|
+
radius = innerRadius;
|
|
5817
|
+
}
|
|
5492
5818
|
const x = Math.sin(radian) * radius;
|
|
5493
5819
|
const y = -Math.cos(radian) * radius;
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5820
|
+
setHand(x, y, attrs);
|
|
5821
|
+
};
|
|
5822
|
+
const handleClockClickStart = (e, attrs) => {
|
|
5823
|
+
e.preventDefault();
|
|
5824
|
+
if (!state.plate)
|
|
5825
|
+
return;
|
|
5826
|
+
const _dialRadius = attrs.dialRadius || 135;
|
|
5827
|
+
const clockPlateBR = state.plate.getBoundingClientRect();
|
|
5828
|
+
const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
|
|
5829
|
+
state.x0 = offset.x + _dialRadius;
|
|
5830
|
+
state.y0 = offset.y + _dialRadius;
|
|
5831
|
+
state.moved = false;
|
|
5832
|
+
const clickPos = getPos(e);
|
|
5833
|
+
state.dx = clickPos.x - state.x0;
|
|
5834
|
+
state.dy = clickPos.y - state.y0;
|
|
5835
|
+
const startX = clickPos.x;
|
|
5836
|
+
const startY = clickPos.y;
|
|
5837
|
+
setHand(state.dx, state.dy, attrs, attrs.roundBy5);
|
|
5838
|
+
m.redraw();
|
|
5839
|
+
// Add document-level listeners to track dragging
|
|
5840
|
+
const moveHandler = (e) => {
|
|
5841
|
+
e.preventDefault();
|
|
5842
|
+
const clickPos = getPos(e);
|
|
5843
|
+
const x = clickPos.x - state.x0;
|
|
5844
|
+
const y = clickPos.y - state.y0;
|
|
5845
|
+
// Only consider it "moved" if dragged more than 5 pixels
|
|
5846
|
+
const distance = Math.sqrt(Math.pow(clickPos.x - startX, 2) + Math.pow(clickPos.y - startY, 2));
|
|
5847
|
+
if (distance > 5) {
|
|
5848
|
+
state.moved = true;
|
|
5849
|
+
}
|
|
5850
|
+
setHand(x, y, attrs, attrs.roundBy5);
|
|
5851
|
+
};
|
|
5852
|
+
const endHandler = () => {
|
|
5853
|
+
document.removeEventListener('mousemove', moveHandler);
|
|
5854
|
+
document.removeEventListener('touchmove', moveHandler);
|
|
5855
|
+
// After setting hour (either by click or drag), switch to minutes view
|
|
5856
|
+
if (attrs.currentView === 'hours' && attrs.onViewChange) {
|
|
5857
|
+
attrs.onViewChange('minutes');
|
|
5858
|
+
}
|
|
5859
|
+
state.moved = false;
|
|
5860
|
+
m.redraw();
|
|
5861
|
+
};
|
|
5862
|
+
document.addEventListener('mousemove', moveHandler);
|
|
5863
|
+
document.addEventListener('touchmove', moveHandler);
|
|
5864
|
+
document.addEventListener('mouseup', endHandler, { once: true });
|
|
5865
|
+
document.addEventListener('touchend', endHandler, { once: true });
|
|
5866
|
+
};
|
|
5867
|
+
const HourTicks = () => {
|
|
5868
|
+
return {
|
|
5869
|
+
view: ({ attrs }) => {
|
|
5870
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5871
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5872
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5873
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5874
|
+
const ticks = [];
|
|
5875
|
+
if (attrs.twelveHour) {
|
|
5876
|
+
for (let i = 1; i < 13; i++) {
|
|
5877
|
+
const radian = (i / 6) * Math.PI;
|
|
5878
|
+
const radius = outerRadius;
|
|
5879
|
+
const left = dialRadius + Math.sin(radian) * radius - tickRadius;
|
|
5880
|
+
const top = dialRadius - Math.cos(radian) * radius - tickRadius;
|
|
5881
|
+
ticks.push(m('.timepicker-tick', {
|
|
5882
|
+
style: {
|
|
5883
|
+
left: `${left}px`,
|
|
5884
|
+
top: `${top}px`,
|
|
5885
|
+
},
|
|
5886
|
+
}, i === 0 ? '00' : i.toString()));
|
|
5887
|
+
}
|
|
5499
5888
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5889
|
+
else {
|
|
5890
|
+
for (let i = 0; i < 24; i++) {
|
|
5891
|
+
const radian = (i / 6) * Math.PI;
|
|
5892
|
+
const inner = i > 0 && i < 13;
|
|
5893
|
+
const radius = inner ? innerRadius : outerRadius;
|
|
5894
|
+
const left = dialRadius + Math.sin(radian) * radius - tickRadius;
|
|
5895
|
+
const top = dialRadius - Math.cos(radian) * radius - tickRadius;
|
|
5896
|
+
ticks.push(m('.timepicker-tick', {
|
|
5897
|
+
style: {
|
|
5898
|
+
left: `${left}px`,
|
|
5899
|
+
top: `${top}px`,
|
|
5900
|
+
},
|
|
5901
|
+
}, i === 0 ? '00' : i.toString()));
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return ticks;
|
|
5905
|
+
},
|
|
5906
|
+
};
|
|
5506
5907
|
};
|
|
5507
|
-
const
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5908
|
+
const MinuteTicks = () => {
|
|
5909
|
+
return {
|
|
5910
|
+
view: ({ attrs }) => {
|
|
5911
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5912
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5913
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5914
|
+
const ticks = [];
|
|
5915
|
+
for (let i = 0; i < 60; i += 5) {
|
|
5916
|
+
const radian = (i / 30) * Math.PI;
|
|
5917
|
+
const left = dialRadius + Math.sin(radian) * outerRadius - tickRadius;
|
|
5918
|
+
const top = dialRadius - Math.cos(radian) * outerRadius - tickRadius;
|
|
5919
|
+
ticks.push(m('.timepicker-tick', {
|
|
5920
|
+
style: {
|
|
5921
|
+
left: `${left}px`,
|
|
5922
|
+
top: `${top}px`,
|
|
5923
|
+
},
|
|
5924
|
+
}, addLeadingZero(i)));
|
|
5925
|
+
}
|
|
5926
|
+
return ticks;
|
|
5927
|
+
},
|
|
5928
|
+
};
|
|
5929
|
+
};
|
|
5930
|
+
return {
|
|
5931
|
+
oncreate: ({ attrs }) => {
|
|
5932
|
+
resetClock(attrs);
|
|
5933
|
+
},
|
|
5934
|
+
view: ({ attrs }) => {
|
|
5935
|
+
// Handle view transitions
|
|
5936
|
+
const isHours = attrs.currentView === 'hours';
|
|
5937
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5938
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5939
|
+
const diameter = dialRadius * 2;
|
|
5940
|
+
// Calculate hand and background positions
|
|
5941
|
+
const view = attrs.currentView;
|
|
5942
|
+
const value = view === 'hours' ? attrs.hours : attrs.minutes;
|
|
5943
|
+
const unit = Math.PI / (view === 'hours' ? 6 : 30);
|
|
5944
|
+
const radian = value * unit;
|
|
5945
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5946
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5947
|
+
// In 12-hour mode, always use outer radius
|
|
5948
|
+
// In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
|
|
5949
|
+
let radius = outerRadius;
|
|
5950
|
+
if (!attrs.twelveHour && view === 'hours' && value > 0 && value < 13) {
|
|
5951
|
+
radius = innerRadius;
|
|
5952
|
+
}
|
|
5953
|
+
const cx1 = Math.sin(radian) * (radius - tickRadius);
|
|
5954
|
+
const cy1 = -Math.cos(radian) * (radius - tickRadius);
|
|
5955
|
+
const cx2 = Math.sin(radian) * radius;
|
|
5956
|
+
const cy2 = -Math.cos(radian) * radius;
|
|
5957
|
+
return [
|
|
5958
|
+
m('.timepicker-canvas', {
|
|
5959
|
+
oncreate: (vnode) => {
|
|
5960
|
+
state.canvas = vnode.dom;
|
|
5961
|
+
state.plate = vnode.dom.parentElement;
|
|
5962
|
+
},
|
|
5963
|
+
onmousedown: (e) => handleClockClickStart(e, attrs),
|
|
5964
|
+
ontouchstart: (e) => handleClockClickStart(e, attrs),
|
|
5965
|
+
}, [
|
|
5966
|
+
m('svg.timepicker-svg', {
|
|
5967
|
+
width: diameter,
|
|
5968
|
+
height: diameter,
|
|
5969
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
5970
|
+
}, [
|
|
5971
|
+
m('g', {
|
|
5972
|
+
transform: `translate(${dialRadius},${dialRadius})`,
|
|
5973
|
+
oncreate: (vnode) => {
|
|
5974
|
+
state.g = vnode.dom;
|
|
5975
|
+
},
|
|
5976
|
+
}, [
|
|
5977
|
+
m('line', {
|
|
5978
|
+
x1: '0',
|
|
5979
|
+
y1: '0',
|
|
5980
|
+
x2: cx1,
|
|
5981
|
+
y2: cy1,
|
|
5982
|
+
oncreate: (vnode) => {
|
|
5983
|
+
state.hand = vnode.dom;
|
|
5984
|
+
},
|
|
5985
|
+
}),
|
|
5986
|
+
m('circle.timepicker-canvas-bg', {
|
|
5987
|
+
cx: cx2,
|
|
5988
|
+
cy: cy2,
|
|
5989
|
+
r: tickRadius,
|
|
5990
|
+
oncreate: (vnode) => {
|
|
5991
|
+
state.bg = vnode.dom;
|
|
5992
|
+
},
|
|
5993
|
+
}),
|
|
5994
|
+
m('circle.timepicker-canvas-bearing', {
|
|
5995
|
+
cx: '0',
|
|
5996
|
+
cy: '0',
|
|
5997
|
+
r: '4',
|
|
5998
|
+
oncreate: (vnode) => {
|
|
5999
|
+
state.bearing = vnode.dom;
|
|
6000
|
+
},
|
|
6001
|
+
}),
|
|
6002
|
+
]),
|
|
6003
|
+
]),
|
|
6004
|
+
]),
|
|
6005
|
+
m(`.timepicker-dial.timepicker-hours${isHours ? '' : '.timepicker-dial-out'}`, {
|
|
6006
|
+
oncreate: (vnode) => {
|
|
6007
|
+
state.hoursView = vnode.dom;
|
|
6008
|
+
},
|
|
6009
|
+
style: {
|
|
6010
|
+
visibility: isHours ? 'visible' : 'hidden',
|
|
6011
|
+
},
|
|
6012
|
+
}, m(HourTicks, attrs)),
|
|
6013
|
+
m(`.timepicker-dial.timepicker-minutes${!isHours ? '' : '.timepicker-dial-out'}`, {
|
|
6014
|
+
oncreate: (vnode) => {
|
|
6015
|
+
state.minutesView = vnode.dom;
|
|
6016
|
+
},
|
|
6017
|
+
style: {
|
|
6018
|
+
visibility: !isHours ? 'visible' : 'hidden',
|
|
6019
|
+
},
|
|
6020
|
+
}, m(MinuteTicks, attrs)),
|
|
6021
|
+
];
|
|
6022
|
+
},
|
|
6023
|
+
onupdate: ({ attrs }) => {
|
|
6024
|
+
// Update clock hand when time or view changes
|
|
6025
|
+
resetClock(attrs);
|
|
6026
|
+
},
|
|
6027
|
+
};
|
|
6028
|
+
};
|
|
6029
|
+
|
|
6030
|
+
const defaultI18n$3 = {
|
|
6031
|
+
cancel: 'Cancel',
|
|
6032
|
+
clear: 'Clear',
|
|
6033
|
+
done: 'Ok',
|
|
6034
|
+
next: 'Next',
|
|
6035
|
+
};
|
|
6036
|
+
const defaultOptions = {
|
|
6037
|
+
dialRadius: 135,
|
|
6038
|
+
outerRadius: 105,
|
|
6039
|
+
innerRadius: 70,
|
|
6040
|
+
tickRadius: 20,
|
|
6041
|
+
duration: 350,
|
|
6042
|
+
container: null,
|
|
6043
|
+
defaultTime: 'now',
|
|
6044
|
+
fromNow: 0,
|
|
6045
|
+
showClearBtn: false,
|
|
6046
|
+
i18n: defaultI18n$3,
|
|
6047
|
+
autoClose: false,
|
|
6048
|
+
twelveHour: true,
|
|
6049
|
+
vibrate: true,
|
|
6050
|
+
roundBy5: false,
|
|
6051
|
+
displayMode: 'analog',
|
|
6052
|
+
minuteStep: 5,
|
|
6053
|
+
hourStep: 1,
|
|
6054
|
+
minTime: undefined,
|
|
6055
|
+
maxTime: undefined,
|
|
6056
|
+
onOpen: () => { },
|
|
6057
|
+
onOpenStart: () => { },
|
|
6058
|
+
onOpenEnd: () => { },
|
|
6059
|
+
onCloseStart: () => { },
|
|
6060
|
+
onCloseEnd: () => { },
|
|
6061
|
+
onSelect: () => { },
|
|
6062
|
+
};
|
|
6063
|
+
/**
|
|
6064
|
+
* TimePicker component based on original Materialize CSS timepicker
|
|
6065
|
+
*/
|
|
6066
|
+
const TimePicker = () => {
|
|
6067
|
+
let state;
|
|
6068
|
+
let options;
|
|
6069
|
+
// Use shared utilities from time-utils
|
|
6070
|
+
// const addLeadingZero = sharedAddLeadingZero;
|
|
6071
|
+
// const generateHourOptions = sharedGenerateHourOptions;
|
|
6072
|
+
// const generateMinuteOptions = sharedGenerateMinuteOptions;
|
|
6073
|
+
// const scrollToValue = sharedScrollToValue;
|
|
6074
|
+
// const snapToNearestItem = sharedSnapToNearestItem;
|
|
6075
|
+
const updateTimeFromInput = (inputValue) => {
|
|
6076
|
+
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
6077
|
+
let amPmWasProvided = false;
|
|
6078
|
+
if (options.twelveHour && value.length > 1) {
|
|
6079
|
+
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
6080
|
+
state.amOrPm = 'AM';
|
|
6081
|
+
amPmWasProvided = true;
|
|
5526
6082
|
}
|
|
5527
|
-
else {
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
if (value === 60)
|
|
5531
|
-
value = 0;
|
|
6083
|
+
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
6084
|
+
state.amOrPm = 'PM';
|
|
6085
|
+
amPmWasProvided = true;
|
|
5532
6086
|
}
|
|
6087
|
+
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
5533
6088
|
}
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
else {
|
|
5541
|
-
if (roundBy5)
|
|
5542
|
-
value *= 5;
|
|
5543
|
-
if (value === 60)
|
|
5544
|
-
value = 0;
|
|
6089
|
+
if (value[0] === 'now') {
|
|
6090
|
+
const now = new Date(+new Date() + options.fromNow);
|
|
6091
|
+
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
6092
|
+
if (options.twelveHour) {
|
|
6093
|
+
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
6094
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
5545
6095
|
}
|
|
5546
6096
|
}
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
}
|
|
5550
|
-
state[state.currentView] = value;
|
|
5551
|
-
if (isHours && state.spanHours) {
|
|
5552
|
-
state.spanHours.innerHTML = addLeadingZero(value);
|
|
5553
|
-
}
|
|
5554
|
-
else if (!isHours && state.spanMinutes) {
|
|
5555
|
-
state.spanMinutes.innerHTML = addLeadingZero(value);
|
|
5556
|
-
}
|
|
5557
|
-
// Set clock hand position
|
|
5558
|
-
if (state.hand && state.bg) {
|
|
5559
|
-
const cx1 = Math.sin(radian) * (radius - options.tickRadius);
|
|
5560
|
-
const cy1 = -Math.cos(radian) * (radius - options.tickRadius);
|
|
5561
|
-
const cx2 = Math.sin(radian) * radius;
|
|
5562
|
-
const cy2 = -Math.cos(radian) * radius;
|
|
5563
|
-
state.hand.setAttribute('x2', cx1.toString());
|
|
5564
|
-
state.hand.setAttribute('y2', cy1.toString());
|
|
5565
|
-
state.bg.setAttribute('cx', cx2.toString());
|
|
5566
|
-
state.bg.setAttribute('cy', cy2.toString());
|
|
5567
|
-
}
|
|
5568
|
-
};
|
|
5569
|
-
const buildSVGClock = () => {
|
|
5570
|
-
if (!state.canvas)
|
|
5571
|
-
return;
|
|
5572
|
-
const dialRadius = options.dialRadius;
|
|
5573
|
-
const tickRadius = options.tickRadius;
|
|
5574
|
-
const diameter = dialRadius * 2;
|
|
5575
|
-
const svg = createSVGEl('svg');
|
|
5576
|
-
svg.setAttribute('class', 'timepicker-svg');
|
|
5577
|
-
svg.setAttribute('width', diameter.toString());
|
|
5578
|
-
svg.setAttribute('height', diameter.toString());
|
|
5579
|
-
const g = createSVGEl('g');
|
|
5580
|
-
g.setAttribute('transform', `translate(${dialRadius},${dialRadius})`);
|
|
5581
|
-
const bearing = createSVGEl('circle');
|
|
5582
|
-
bearing.setAttribute('class', 'timepicker-canvas-bearing');
|
|
5583
|
-
bearing.setAttribute('cx', '0');
|
|
5584
|
-
bearing.setAttribute('cy', '0');
|
|
5585
|
-
bearing.setAttribute('r', '4');
|
|
5586
|
-
const hand = createSVGEl('line');
|
|
5587
|
-
hand.setAttribute('x1', '0');
|
|
5588
|
-
hand.setAttribute('y1', '0');
|
|
5589
|
-
const bg = createSVGEl('circle');
|
|
5590
|
-
bg.setAttribute('class', 'timepicker-canvas-bg');
|
|
5591
|
-
bg.setAttribute('r', tickRadius.toString());
|
|
5592
|
-
g.appendChild(hand);
|
|
5593
|
-
g.appendChild(bg);
|
|
5594
|
-
g.appendChild(bearing);
|
|
5595
|
-
svg.appendChild(g);
|
|
5596
|
-
state.canvas.appendChild(svg);
|
|
5597
|
-
state.hand = hand;
|
|
5598
|
-
state.bg = bg;
|
|
5599
|
-
state.bearing = bearing;
|
|
5600
|
-
state.g = g;
|
|
5601
|
-
};
|
|
5602
|
-
const buildHoursView = () => {
|
|
5603
|
-
if (!state.hoursView)
|
|
5604
|
-
return;
|
|
6097
|
+
let hours = +value[0] || 0;
|
|
6098
|
+
let minutes = +value[1] || 0;
|
|
5605
6099
|
if (options.twelveHour) {
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
6100
|
+
if (!amPmWasProvided) {
|
|
6101
|
+
// No AM/PM was provided, assume this is 24-hour format input - convert it
|
|
6102
|
+
if (hours >= 12) {
|
|
6103
|
+
state.amOrPm = 'PM';
|
|
6104
|
+
if (hours > 12) {
|
|
6105
|
+
hours = hours - 12;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
else {
|
|
6109
|
+
state.amOrPm = 'AM';
|
|
6110
|
+
if (hours === 0) {
|
|
6111
|
+
hours = 12;
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
5615
6114
|
}
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
const inner = i > 0 && i < 13;
|
|
5623
|
-
const radius = inner ? options.innerRadius : options.outerRadius;
|
|
5624
|
-
tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
|
|
5625
|
-
tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
|
|
5626
|
-
tick.innerHTML = i === 0 ? '00' : i.toString();
|
|
5627
|
-
state.hoursView.appendChild(tick);
|
|
6115
|
+
else {
|
|
6116
|
+
// AM/PM was provided, hours are already in 12-hour format
|
|
6117
|
+
// Just handle midnight/noon edge cases
|
|
6118
|
+
if (hours === 0 && state.amOrPm === 'AM') {
|
|
6119
|
+
hours = 12;
|
|
6120
|
+
}
|
|
5628
6121
|
}
|
|
5629
6122
|
}
|
|
6123
|
+
state.hours = hours;
|
|
6124
|
+
state.minutes = minutes;
|
|
6125
|
+
if (state.spanHours) {
|
|
6126
|
+
state.spanHours.innerHTML = addLeadingZero(state.hours);
|
|
6127
|
+
}
|
|
6128
|
+
if (state.spanMinutes) {
|
|
6129
|
+
state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
|
|
6130
|
+
}
|
|
6131
|
+
updateAmPmView();
|
|
5630
6132
|
};
|
|
5631
|
-
const
|
|
5632
|
-
if (
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
const tick = document.createElement('div');
|
|
5636
|
-
tick.className = 'timepicker-tick';
|
|
5637
|
-
const radian = (i / 30) * Math.PI;
|
|
5638
|
-
tick.style.left = options.dialRadius + Math.sin(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
5639
|
-
tick.style.top = options.dialRadius - Math.cos(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
5640
|
-
tick.innerHTML = addLeadingZero(i);
|
|
5641
|
-
state.minutesView.appendChild(tick);
|
|
6133
|
+
const updateAmPmView = () => {
|
|
6134
|
+
if (options.twelveHour && state.amBtn && state.pmBtn) {
|
|
6135
|
+
state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
|
|
6136
|
+
state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
|
|
5642
6137
|
}
|
|
5643
6138
|
};
|
|
5644
6139
|
const handleAmPmClick = (ampm) => {
|
|
@@ -5650,7 +6145,7 @@
|
|
|
5650
6145
|
return;
|
|
5651
6146
|
state.isOpen = true;
|
|
5652
6147
|
updateTimeFromInput(inputValue);
|
|
5653
|
-
|
|
6148
|
+
state.currentView = 'hours';
|
|
5654
6149
|
if (options.onOpen)
|
|
5655
6150
|
options.onOpen();
|
|
5656
6151
|
if (options.onOpenStart)
|
|
@@ -5684,6 +6179,7 @@
|
|
|
5684
6179
|
return {
|
|
5685
6180
|
view: ({ attrs }) => {
|
|
5686
6181
|
const { i18n, showClearBtn } = attrs;
|
|
6182
|
+
const isDigitalMode = options.displayMode === 'digital';
|
|
5687
6183
|
return [
|
|
5688
6184
|
m('.modal-content.timepicker-container', [
|
|
5689
6185
|
m('.timepicker-digital-display', [
|
|
@@ -5691,7 +6187,12 @@
|
|
|
5691
6187
|
m('.timepicker-display-column', [
|
|
5692
6188
|
m('span.timepicker-span-hours', {
|
|
5693
6189
|
class: state.currentView === 'hours' ? 'text-primary' : '',
|
|
5694
|
-
onclick: () =>
|
|
6190
|
+
onclick: () => {
|
|
6191
|
+
if (!isDigitalMode) {
|
|
6192
|
+
state.currentView = 'hours';
|
|
6193
|
+
m.redraw();
|
|
6194
|
+
}
|
|
6195
|
+
},
|
|
5695
6196
|
oncreate: (vnode) => {
|
|
5696
6197
|
state.spanHours = vnode.dom;
|
|
5697
6198
|
},
|
|
@@ -5699,7 +6200,12 @@
|
|
|
5699
6200
|
':',
|
|
5700
6201
|
m('span.timepicker-span-minutes', {
|
|
5701
6202
|
class: state.currentView === 'minutes' ? 'text-primary' : '',
|
|
5702
|
-
onclick: () =>
|
|
6203
|
+
onclick: () => {
|
|
6204
|
+
if (!isDigitalMode) {
|
|
6205
|
+
state.currentView = 'minutes';
|
|
6206
|
+
m.redraw();
|
|
6207
|
+
}
|
|
6208
|
+
},
|
|
5703
6209
|
oncreate: (vnode) => {
|
|
5704
6210
|
state.spanMinutes = vnode.dom;
|
|
5705
6211
|
},
|
|
@@ -5730,66 +6236,108 @@
|
|
|
5730
6236
|
]),
|
|
5731
6237
|
]),
|
|
5732
6238
|
]),
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
state.
|
|
5738
|
-
state.
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
setTimeout(() => resetClock(), 10);
|
|
5753
|
-
},
|
|
5754
|
-
}),
|
|
5755
|
-
m('.timepicker-dial.timepicker-hours', {
|
|
5756
|
-
oncreate: (vnode) => {
|
|
5757
|
-
state.hoursView = vnode.dom;
|
|
5758
|
-
buildHoursView();
|
|
6239
|
+
// Conditional rendering: digital or analog mode
|
|
6240
|
+
isDigitalMode
|
|
6241
|
+
? m('.timepicker-digital-mode', [
|
|
6242
|
+
m(DigitalClock, {
|
|
6243
|
+
hours: state.hours,
|
|
6244
|
+
minutes: state.minutes,
|
|
6245
|
+
amOrPm: state.amOrPm,
|
|
6246
|
+
twelveHour: options.twelveHour,
|
|
6247
|
+
minuteStep: options.minuteStep,
|
|
6248
|
+
hourStep: options.hourStep,
|
|
6249
|
+
minTime: options.minTime,
|
|
6250
|
+
maxTime: options.maxTime,
|
|
6251
|
+
onTimeChange: (hours, minutes, amOrPm) => {
|
|
6252
|
+
state.hours = hours;
|
|
6253
|
+
state.minutes = minutes;
|
|
6254
|
+
state.amOrPm = amOrPm;
|
|
6255
|
+
updateAmPmView();
|
|
6256
|
+
if (options.onSelect)
|
|
6257
|
+
options.onSelect(hours, minutes);
|
|
5759
6258
|
},
|
|
6259
|
+
spanHours: state.spanHours,
|
|
6260
|
+
spanMinutes: state.spanMinutes,
|
|
6261
|
+
spanAmPm: state.spanAmPm,
|
|
5760
6262
|
}),
|
|
5761
|
-
m('.timepicker-
|
|
6263
|
+
m('.timepicker-footer', {
|
|
5762
6264
|
oncreate: (vnode) => {
|
|
5763
|
-
state.
|
|
5764
|
-
buildMinutesView();
|
|
6265
|
+
state.footer = vnode.dom;
|
|
5765
6266
|
},
|
|
5766
|
-
}
|
|
5767
|
-
|
|
5768
|
-
m('.timepicker-footer', {
|
|
5769
|
-
oncreate: (vnode) => {
|
|
5770
|
-
state.footer = vnode.dom;
|
|
5771
|
-
},
|
|
5772
|
-
}, [
|
|
5773
|
-
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5774
|
-
type: 'button',
|
|
5775
|
-
tabindex: options.twelveHour ? '3' : '1',
|
|
5776
|
-
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
5777
|
-
onclick: () => clear(),
|
|
5778
|
-
}, i18n.clear),
|
|
5779
|
-
m('.confirmation-btns', [
|
|
5780
|
-
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6267
|
+
}, [
|
|
6268
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5781
6269
|
type: 'button',
|
|
5782
6270
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6271
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
6272
|
+
onclick: () => clear(),
|
|
6273
|
+
}, i18n.clear),
|
|
6274
|
+
m('.confirmation-btns', [
|
|
6275
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6276
|
+
type: 'button',
|
|
6277
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6278
|
+
onclick: () => close(),
|
|
6279
|
+
}, i18n.cancel),
|
|
6280
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6281
|
+
type: 'button',
|
|
6282
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6283
|
+
onclick: () => done(),
|
|
6284
|
+
}, i18n.done),
|
|
6285
|
+
]),
|
|
6286
|
+
]),
|
|
6287
|
+
])
|
|
6288
|
+
: m('.timepicker-analog-display', [
|
|
6289
|
+
m('.timepicker-plate', m(AnalogClock, {
|
|
6290
|
+
hours: state.hours,
|
|
6291
|
+
minutes: state.minutes,
|
|
6292
|
+
amOrPm: state.amOrPm,
|
|
6293
|
+
currentView: state.currentView,
|
|
6294
|
+
twelveHour: options.twelveHour,
|
|
6295
|
+
dialRadius: options.dialRadius,
|
|
6296
|
+
outerRadius: options.outerRadius,
|
|
6297
|
+
innerRadius: options.innerRadius,
|
|
6298
|
+
tickRadius: options.tickRadius,
|
|
6299
|
+
roundBy5: options.roundBy5,
|
|
6300
|
+
vibrate: options.vibrate,
|
|
6301
|
+
onTimeChange: (hours, minutes) => {
|
|
6302
|
+
state.hours = hours;
|
|
6303
|
+
state.minutes = minutes;
|
|
6304
|
+
if (options.onSelect)
|
|
6305
|
+
options.onSelect(hours, minutes);
|
|
6306
|
+
},
|
|
6307
|
+
onViewChange: (view) => {
|
|
6308
|
+
state.currentView = view;
|
|
6309
|
+
if (view === 'minutes' && options.autoClose) {
|
|
6310
|
+
setTimeout(() => done(), options.duration / 2);
|
|
6311
|
+
}
|
|
6312
|
+
},
|
|
6313
|
+
spanHours: state.spanHours,
|
|
6314
|
+
spanMinutes: state.spanMinutes,
|
|
6315
|
+
})),
|
|
6316
|
+
m('.timepicker-footer', {
|
|
6317
|
+
oncreate: (vnode) => {
|
|
6318
|
+
state.footer = vnode.dom;
|
|
6319
|
+
},
|
|
6320
|
+
}, [
|
|
6321
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5786
6322
|
type: 'button',
|
|
5787
6323
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5788
|
-
|
|
5789
|
-
|
|
6324
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
6325
|
+
onclick: () => clear(),
|
|
6326
|
+
}, i18n.clear),
|
|
6327
|
+
m('.confirmation-btns', [
|
|
6328
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6329
|
+
type: 'button',
|
|
6330
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6331
|
+
onclick: () => close(),
|
|
6332
|
+
}, i18n.cancel),
|
|
6333
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6334
|
+
type: 'button',
|
|
6335
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6336
|
+
onclick: () => done(),
|
|
6337
|
+
}, i18n.done),
|
|
6338
|
+
]),
|
|
5790
6339
|
]),
|
|
5791
6340
|
]),
|
|
5792
|
-
]),
|
|
5793
6341
|
]),
|
|
5794
6342
|
];
|
|
5795
6343
|
},
|
|
@@ -5802,7 +6350,7 @@
|
|
|
5802
6350
|
m.redraw();
|
|
5803
6351
|
}
|
|
5804
6352
|
};
|
|
5805
|
-
const renderPickerToPortal = (
|
|
6353
|
+
const renderPickerToPortal = () => {
|
|
5806
6354
|
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
5807
6355
|
style: {
|
|
5808
6356
|
position: 'fixed',
|
|
@@ -5863,11 +6411,6 @@
|
|
|
5863
6411
|
minutes: 0,
|
|
5864
6412
|
amOrPm: 'AM',
|
|
5865
6413
|
currentView: 'hours',
|
|
5866
|
-
moved: false,
|
|
5867
|
-
x0: 0,
|
|
5868
|
-
y0: 0,
|
|
5869
|
-
dx: 0,
|
|
5870
|
-
dy: 0,
|
|
5871
6414
|
portalContainerId: `timepicker-portal-${uniqueId()}`,
|
|
5872
6415
|
};
|
|
5873
6416
|
// Handle value after options are set
|
|
@@ -5879,27 +6422,17 @@
|
|
|
5879
6422
|
},
|
|
5880
6423
|
onremove: () => {
|
|
5881
6424
|
// Cleanup
|
|
5882
|
-
if (state.toggleViewTimer) {
|
|
5883
|
-
clearTimeout(state.toggleViewTimer);
|
|
5884
|
-
}
|
|
5885
|
-
if (state.vibrateTimer) {
|
|
5886
|
-
clearTimeout(state.vibrateTimer);
|
|
5887
|
-
}
|
|
5888
|
-
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
5889
|
-
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
5890
|
-
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
5891
|
-
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
5892
6425
|
document.removeEventListener('keydown', handleKeyDown);
|
|
5893
6426
|
// Clean up portal if picker was open
|
|
5894
6427
|
if (state.isOpen) {
|
|
5895
6428
|
clearPortal(state.portalContainerId);
|
|
5896
6429
|
}
|
|
5897
6430
|
},
|
|
5898
|
-
onupdate: (
|
|
5899
|
-
const { useModal = true } =
|
|
6431
|
+
onupdate: ({ attrs }) => {
|
|
6432
|
+
const { useModal = true } = attrs;
|
|
5900
6433
|
// Only render to portal when using modal mode
|
|
5901
6434
|
if (useModal && state.isOpen) {
|
|
5902
|
-
renderPickerToPortal(
|
|
6435
|
+
renderPickerToPortal();
|
|
5903
6436
|
}
|
|
5904
6437
|
else {
|
|
5905
6438
|
clearPortal(state.portalContainerId);
|
|
@@ -6429,7 +6962,7 @@
|
|
|
6429
6962
|
// Create dropdown with proper positioning
|
|
6430
6963
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
6431
6964
|
tabindex: 0,
|
|
6432
|
-
style: getPortalStyles(state.inputRef),
|
|
6965
|
+
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
6433
6966
|
oncreate: ({ dom }) => {
|
|
6434
6967
|
state.dropdownRef = dom;
|
|
6435
6968
|
},
|
|
@@ -6498,7 +7031,8 @@
|
|
|
6498
7031
|
selectedIds = state.internalSelectedIds;
|
|
6499
7032
|
}
|
|
6500
7033
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
6501
|
-
const
|
|
7034
|
+
const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
7035
|
+
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
6502
7036
|
// Update portal dropdown when inside modal
|
|
6503
7037
|
if (state.isInsideModal) {
|
|
6504
7038
|
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
@@ -6543,7 +7077,7 @@
|
|
|
6543
7077
|
onremove: () => {
|
|
6544
7078
|
state.dropdownRef = null;
|
|
6545
7079
|
},
|
|
6546
|
-
style: getDropdownStyles(state.inputRef, true, options),
|
|
7080
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, options)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
6547
7081
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
|
|
6548
7082
|
m(MaterialIcon, {
|
|
6549
7083
|
name: 'caret',
|
|
@@ -6779,45 +7313,50 @@
|
|
|
6779
7313
|
};
|
|
6780
7314
|
|
|
6781
7315
|
// Proper components to avoid anonymous closures
|
|
6782
|
-
const SelectedChip = {
|
|
6783
|
-
|
|
6784
|
-
option
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
e
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
]),
|
|
6794
|
-
};
|
|
6795
|
-
const DropdownOption = {
|
|
6796
|
-
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6797
|
-
const checkboxId = `search-select-option-${option.id}`;
|
|
6798
|
-
const optionLabel = option.label || option.id.toString();
|
|
6799
|
-
return m('li', {
|
|
6800
|
-
key: option.id,
|
|
6801
|
-
onclick: (e) => {
|
|
6802
|
-
e.preventDefault();
|
|
6803
|
-
e.stopPropagation();
|
|
6804
|
-
onToggle(option);
|
|
6805
|
-
},
|
|
6806
|
-
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
6807
|
-
onmouseover: () => {
|
|
6808
|
-
if (!option.disabled) {
|
|
6809
|
-
onMouseOver(index);
|
|
6810
|
-
}
|
|
6811
|
-
},
|
|
6812
|
-
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6813
|
-
m('input', {
|
|
6814
|
-
type: 'checkbox',
|
|
6815
|
-
id: checkboxId,
|
|
6816
|
-
checked: selectedIds.includes(option.id),
|
|
7316
|
+
const SelectedChip = () => {
|
|
7317
|
+
return {
|
|
7318
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
7319
|
+
option.label || option.id.toString(),
|
|
7320
|
+
m(MaterialIcon, {
|
|
7321
|
+
name: 'close',
|
|
7322
|
+
className: 'close',
|
|
7323
|
+
onclick: (e) => {
|
|
7324
|
+
e.stopPropagation();
|
|
7325
|
+
onRemove(option.id);
|
|
7326
|
+
},
|
|
6817
7327
|
}),
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
7328
|
+
]),
|
|
7329
|
+
};
|
|
7330
|
+
};
|
|
7331
|
+
const DropdownOption = () => {
|
|
7332
|
+
return {
|
|
7333
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
|
|
7334
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
7335
|
+
const optionLabel = option.label || option.id.toString();
|
|
7336
|
+
return m('li', {
|
|
7337
|
+
key: option.id,
|
|
7338
|
+
onclick: (e) => {
|
|
7339
|
+
e.preventDefault();
|
|
7340
|
+
e.stopPropagation();
|
|
7341
|
+
onToggle(option);
|
|
7342
|
+
},
|
|
7343
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
7344
|
+
onmouseover: () => {
|
|
7345
|
+
if (!option.disabled) {
|
|
7346
|
+
onMouseOver(index);
|
|
7347
|
+
}
|
|
7348
|
+
},
|
|
7349
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
7350
|
+
showCheckbox &&
|
|
7351
|
+
m('input', {
|
|
7352
|
+
type: 'checkbox',
|
|
7353
|
+
id: checkboxId,
|
|
7354
|
+
checked: selectedIds.includes(option.id),
|
|
7355
|
+
}),
|
|
7356
|
+
m('span', optionLabel),
|
|
7357
|
+
]));
|
|
7358
|
+
},
|
|
7359
|
+
};
|
|
6821
7360
|
};
|
|
6822
7361
|
/**
|
|
6823
7362
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
@@ -6832,6 +7371,7 @@
|
|
|
6832
7371
|
dropdownRef: null,
|
|
6833
7372
|
focusedIndex: -1,
|
|
6834
7373
|
internalSelectedIds: [],
|
|
7374
|
+
createdOptions: [],
|
|
6835
7375
|
};
|
|
6836
7376
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
6837
7377
|
const componentId = uniqueId();
|
|
@@ -6874,7 +7414,10 @@
|
|
|
6874
7414
|
// Handle add new option
|
|
6875
7415
|
return 'addNew';
|
|
6876
7416
|
}
|
|
6877
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
7417
|
+
else if (state.focusedIndex < filteredOptions.length) {
|
|
7418
|
+
// This will be handled in the view method where attrs are available
|
|
7419
|
+
return 'selectOption';
|
|
7420
|
+
}
|
|
6878
7421
|
}
|
|
6879
7422
|
break;
|
|
6880
7423
|
case 'Escape':
|
|
@@ -6885,11 +7428,22 @@
|
|
|
6885
7428
|
}
|
|
6886
7429
|
return null;
|
|
6887
7430
|
};
|
|
7431
|
+
// Create new option and add to state
|
|
7432
|
+
const createAndSelectOption = async (attrs) => {
|
|
7433
|
+
if (!attrs.oncreateNewOption || !state.searchTerm)
|
|
7434
|
+
return;
|
|
7435
|
+
const newOption = await attrs.oncreateNewOption(state.searchTerm);
|
|
7436
|
+
// Store the created option internally
|
|
7437
|
+
state.createdOptions.push(newOption);
|
|
7438
|
+
// Select the new option
|
|
7439
|
+
toggleOption(newOption, attrs);
|
|
7440
|
+
};
|
|
6888
7441
|
// Toggle option selection
|
|
6889
7442
|
const toggleOption = (option, attrs) => {
|
|
6890
7443
|
if (option.disabled)
|
|
6891
7444
|
return;
|
|
6892
7445
|
const controlled = isControlled(attrs);
|
|
7446
|
+
const { maxSelectedOptions } = attrs;
|
|
6893
7447
|
// Get current selected IDs from props or internal state
|
|
6894
7448
|
const currentSelectedIds = controlled
|
|
6895
7449
|
? attrs.checkedId !== undefined
|
|
@@ -6898,9 +7452,29 @@
|
|
|
6898
7452
|
: [attrs.checkedId]
|
|
6899
7453
|
: []
|
|
6900
7454
|
: state.internalSelectedIds;
|
|
6901
|
-
const
|
|
6902
|
-
|
|
6903
|
-
|
|
7455
|
+
const isSelected = currentSelectedIds.includes(option.id);
|
|
7456
|
+
let newIds;
|
|
7457
|
+
if (isSelected) {
|
|
7458
|
+
// Remove if already selected
|
|
7459
|
+
newIds = currentSelectedIds.filter((id) => id !== option.id);
|
|
7460
|
+
}
|
|
7461
|
+
else {
|
|
7462
|
+
// Check if we've reached the max selection limit
|
|
7463
|
+
if (maxSelectedOptions && currentSelectedIds.length >= maxSelectedOptions) {
|
|
7464
|
+
// If max=1, replace the selection
|
|
7465
|
+
if (maxSelectedOptions === 1) {
|
|
7466
|
+
newIds = [option.id];
|
|
7467
|
+
}
|
|
7468
|
+
else {
|
|
7469
|
+
// Otherwise, don't add more
|
|
7470
|
+
return;
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
else {
|
|
7474
|
+
// Add to selection
|
|
7475
|
+
newIds = [...currentSelectedIds, option.id];
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
6904
7478
|
// Update internal state for uncontrolled mode
|
|
6905
7479
|
if (!controlled) {
|
|
6906
7480
|
state.internalSelectedIds = newIds;
|
|
@@ -6962,21 +7536,32 @@
|
|
|
6962
7536
|
: [attrs.checkedId]
|
|
6963
7537
|
: []
|
|
6964
7538
|
: state.internalSelectedIds;
|
|
6965
|
-
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
7539
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
6966
7540
|
// Use i18n values if provided, otherwise use defaults
|
|
6967
7541
|
const texts = {
|
|
6968
7542
|
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6969
7543
|
addNewPrefix: i18n.addNewPrefix || '+',
|
|
7544
|
+
showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
|
|
7545
|
+
maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
|
|
6970
7546
|
};
|
|
7547
|
+
// Check if max selections is reached
|
|
7548
|
+
const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
|
|
7549
|
+
// Merge provided options with internally created options
|
|
7550
|
+
const allOptions = [...options, ...state.createdOptions];
|
|
6971
7551
|
// Get selected options for display
|
|
6972
|
-
const
|
|
7552
|
+
const selectedOptionsUnsorted = allOptions.filter((opt) => selectedIds.includes(opt.id));
|
|
7553
|
+
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
6973
7554
|
// Safely filter options
|
|
6974
|
-
const filteredOptions =
|
|
7555
|
+
const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6975
7556
|
!selectedIds.includes(option.id));
|
|
7557
|
+
// Apply display limit if configured
|
|
7558
|
+
const totalFilteredCount = filteredOptions.length;
|
|
7559
|
+
const displayedOptions = maxDisplayedOptions ? filteredOptions.slice(0, maxDisplayedOptions) : filteredOptions;
|
|
7560
|
+
const isTruncated = maxDisplayedOptions && totalFilteredCount > maxDisplayedOptions;
|
|
6976
7561
|
// Check if we should show the "add new option" element
|
|
6977
7562
|
const showAddNew = oncreateNewOption &&
|
|
6978
7563
|
state.searchTerm &&
|
|
6979
|
-
!
|
|
7564
|
+
!displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
|
|
6980
7565
|
// Render the dropdown
|
|
6981
7566
|
return m('.input-field.multi-select-dropdown', { className }, [
|
|
6982
7567
|
m('.chips.chips-initial.chips-container', {
|
|
@@ -7010,8 +7595,7 @@
|
|
|
7010
7595
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
7011
7596
|
}),
|
|
7012
7597
|
// Selected Options (chips)
|
|
7013
|
-
...selectedOptions.map((option) => m(SelectedChip, {
|
|
7014
|
-
// key: option.id,
|
|
7598
|
+
...selectedOptions.map((option) => m(SelectedChip(), {
|
|
7015
7599
|
option,
|
|
7016
7600
|
onRemove: (id) => removeOption(id, attrs),
|
|
7017
7601
|
})),
|
|
@@ -7049,7 +7633,7 @@
|
|
|
7049
7633
|
onremove: () => {
|
|
7050
7634
|
state.dropdownRef = null;
|
|
7051
7635
|
},
|
|
7052
|
-
style: getDropdownStyles(state.inputRef),
|
|
7636
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
|
|
7053
7637
|
}, [
|
|
7054
7638
|
m('li', // Search Input
|
|
7055
7639
|
{
|
|
@@ -7069,42 +7653,65 @@
|
|
|
7069
7653
|
state.focusedIndex = -1; // Reset focus when typing
|
|
7070
7654
|
},
|
|
7071
7655
|
onkeydown: async (e) => {
|
|
7072
|
-
const result = handleKeyDown(e,
|
|
7656
|
+
const result = handleKeyDown(e, displayedOptions, !!showAddNew);
|
|
7073
7657
|
if (result === 'addNew' && oncreateNewOption) {
|
|
7074
|
-
|
|
7075
|
-
toggleOption(option, attrs);
|
|
7658
|
+
await createAndSelectOption(attrs);
|
|
7076
7659
|
}
|
|
7077
|
-
else if (
|
|
7078
|
-
state.focusedIndex
|
|
7079
|
-
state.focusedIndex < filteredOptions.length) {
|
|
7080
|
-
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
7660
|
+
else if (result === 'selectOption' && state.focusedIndex < displayedOptions.length) {
|
|
7661
|
+
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
7081
7662
|
}
|
|
7082
7663
|
},
|
|
7083
7664
|
class: 'search-select-input',
|
|
7084
7665
|
}),
|
|
7085
7666
|
]),
|
|
7086
7667
|
// No options found message or list of options
|
|
7087
|
-
...(
|
|
7668
|
+
...(displayedOptions.length === 0 && !showAddNew
|
|
7088
7669
|
? [m('li.search-select-no-options', texts.noOptionsFound)]
|
|
7089
7670
|
: []),
|
|
7671
|
+
// Truncation message
|
|
7672
|
+
...(isTruncated
|
|
7673
|
+
? [
|
|
7674
|
+
m('li.search-select-truncation-info', {
|
|
7675
|
+
style: {
|
|
7676
|
+
fontStyle: 'italic',
|
|
7677
|
+
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7678
|
+
padding: '8px 16px',
|
|
7679
|
+
cursor: 'default',
|
|
7680
|
+
},
|
|
7681
|
+
}, texts.showingXofY
|
|
7682
|
+
.replace('{shown}', displayedOptions.length.toString())
|
|
7683
|
+
.replace('{total}', totalFilteredCount.toString())),
|
|
7684
|
+
]
|
|
7685
|
+
: []),
|
|
7686
|
+
// Max selections reached message
|
|
7687
|
+
...(isMaxSelectionsReached
|
|
7688
|
+
? [
|
|
7689
|
+
m('li.search-select-max-info', {
|
|
7690
|
+
style: {
|
|
7691
|
+
fontStyle: 'italic',
|
|
7692
|
+
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7693
|
+
padding: '8px 16px',
|
|
7694
|
+
cursor: 'default',
|
|
7695
|
+
},
|
|
7696
|
+
}, texts.maxSelectionsReached.replace('{max}', maxSelectedOptions.toString())),
|
|
7697
|
+
]
|
|
7698
|
+
: []),
|
|
7090
7699
|
// Add new option item
|
|
7091
7700
|
...(showAddNew
|
|
7092
7701
|
? [
|
|
7093
7702
|
m('li', {
|
|
7094
7703
|
onclick: async () => {
|
|
7095
|
-
|
|
7096
|
-
toggleOption(option, attrs);
|
|
7704
|
+
await createAndSelectOption(attrs);
|
|
7097
7705
|
},
|
|
7098
|
-
class: state.focusedIndex ===
|
|
7706
|
+
class: state.focusedIndex === displayedOptions.length ? 'active' : '',
|
|
7099
7707
|
onmouseover: () => {
|
|
7100
|
-
state.focusedIndex =
|
|
7708
|
+
state.focusedIndex = displayedOptions.length;
|
|
7101
7709
|
},
|
|
7102
7710
|
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
7103
7711
|
]
|
|
7104
7712
|
: []),
|
|
7105
7713
|
// List of filtered options
|
|
7106
|
-
...
|
|
7107
|
-
// key: option.id,
|
|
7714
|
+
...displayedOptions.map((option, index) => m(DropdownOption(), {
|
|
7108
7715
|
option,
|
|
7109
7716
|
index,
|
|
7110
7717
|
selectedIds,
|
|
@@ -7113,6 +7720,7 @@
|
|
|
7113
7720
|
onMouseOver: (idx) => {
|
|
7114
7721
|
state.focusedIndex = idx;
|
|
7115
7722
|
},
|
|
7723
|
+
showCheckbox: maxSelectedOptions !== 1,
|
|
7116
7724
|
})),
|
|
7117
7725
|
]),
|
|
7118
7726
|
]);
|
|
@@ -7120,6 +7728,411 @@
|
|
|
7120
7728
|
};
|
|
7121
7729
|
};
|
|
7122
7730
|
|
|
7731
|
+
const defaultI18n$2 = {
|
|
7732
|
+
cancel: 'Cancel',
|
|
7733
|
+
clear: 'Clear',
|
|
7734
|
+
done: 'Ok',
|
|
7735
|
+
next: 'Next',
|
|
7736
|
+
};
|
|
7737
|
+
/**
|
|
7738
|
+
* TimeRangePicker component for selecting time ranges
|
|
7739
|
+
* Custom implementation with embedded digital clock picker
|
|
7740
|
+
*/
|
|
7741
|
+
const TimeRangePicker = () => {
|
|
7742
|
+
let state;
|
|
7743
|
+
const calculateMinTime = (startTime, twelveHour) => {
|
|
7744
|
+
if (!startTime)
|
|
7745
|
+
return undefined;
|
|
7746
|
+
let hours = startTime.hours;
|
|
7747
|
+
let minutes = startTime.minutes + 1;
|
|
7748
|
+
let amOrPm = startTime.amOrPm;
|
|
7749
|
+
if (minutes >= 60) {
|
|
7750
|
+
minutes = 0;
|
|
7751
|
+
hours++;
|
|
7752
|
+
if (twelveHour) {
|
|
7753
|
+
if (hours > 12) {
|
|
7754
|
+
hours = 1;
|
|
7755
|
+
amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
|
|
7756
|
+
}
|
|
7757
|
+
else if (hours === 12) {
|
|
7758
|
+
amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
else {
|
|
7762
|
+
if (hours >= 24)
|
|
7763
|
+
hours = 0;
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
return formatTime({ hours, minutes, amOrPm }, twelveHour);
|
|
7767
|
+
};
|
|
7768
|
+
const handleNextOrDone = (validateRange, twelveHour, onchange) => {
|
|
7769
|
+
if (state.currentSelection === 'start') {
|
|
7770
|
+
// Move to end time selection
|
|
7771
|
+
state.currentSelection = 'end';
|
|
7772
|
+
state.currentView = 'hours'; // Reset to hours view for end time
|
|
7773
|
+
// If validation is enabled and end time is before or equal to start time, reset end time
|
|
7774
|
+
if (validateRange) {
|
|
7775
|
+
const startMins = timeToMinutes(state.tempStartTime, twelveHour);
|
|
7776
|
+
const endMins = timeToMinutes(state.tempEndTime, twelveHour);
|
|
7777
|
+
if (endMins <= startMins) {
|
|
7778
|
+
// Reset end time to start time
|
|
7779
|
+
state.tempEndTime = Object.assign({}, state.tempStartTime);
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
else {
|
|
7784
|
+
// Finalize selection
|
|
7785
|
+
state.startTime = Object.assign({}, state.tempStartTime);
|
|
7786
|
+
state.endTime = Object.assign({}, state.tempEndTime);
|
|
7787
|
+
state.isPickerOpen = false;
|
|
7788
|
+
state.currentSelection = 'start';
|
|
7789
|
+
// Call onchange callback
|
|
7790
|
+
if (onchange && state.startTime && state.endTime) {
|
|
7791
|
+
const startTimeStr = formatTime(state.startTime, twelveHour);
|
|
7792
|
+
const endTimeStr = formatTime(state.endTime, twelveHour);
|
|
7793
|
+
onchange(startTimeStr, endTimeStr);
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
};
|
|
7797
|
+
const TimeRangePickerModal = () => {
|
|
7798
|
+
return {
|
|
7799
|
+
view: ({ attrs }) => {
|
|
7800
|
+
const { i18n, showClearBtn, twelveHour, minuteStep, hourStep, minTime, maxTime, validateRange, displayMode = 'digital', dialRadius = 135, outerRadius = 105, innerRadius = 70, tickRadius = 20, roundBy5 = false, vibrate = true, } = attrs;
|
|
7801
|
+
const isAnalogMode = displayMode === 'analog';
|
|
7802
|
+
// Calculate effective minTime for end time selection
|
|
7803
|
+
const effectiveMinTime = state.currentSelection === 'end' && validateRange
|
|
7804
|
+
? calculateMinTime(state.tempStartTime, twelveHour)
|
|
7805
|
+
: minTime;
|
|
7806
|
+
return m('.modal-content.timepicker-container', [
|
|
7807
|
+
// Vertical time range display on the left
|
|
7808
|
+
m('.timerange-display-vertical', [
|
|
7809
|
+
m('.timerange-time-section', { class: state.currentSelection === 'start' ? 'active' : '' }, [
|
|
7810
|
+
m('.timerange-label', 'Start'),
|
|
7811
|
+
m('.timerange-time', [
|
|
7812
|
+
m('span.timerange-hours', {
|
|
7813
|
+
oncreate: (vnode) => {
|
|
7814
|
+
state.spanStartHours = vnode.dom;
|
|
7815
|
+
state.spanStartHours.innerHTML = addLeadingZero(state.tempStartTime.hours);
|
|
7816
|
+
},
|
|
7817
|
+
}, addLeadingZero(state.tempStartTime.hours)),
|
|
7818
|
+
':',
|
|
7819
|
+
m('span.timerange-minutes', {
|
|
7820
|
+
oncreate: (vnode) => {
|
|
7821
|
+
state.spanStartMinutes = vnode.dom;
|
|
7822
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(state.tempStartTime.minutes);
|
|
7823
|
+
},
|
|
7824
|
+
}, addLeadingZero(state.tempStartTime.minutes)),
|
|
7825
|
+
twelveHour &&
|
|
7826
|
+
m('span.timerange-ampm', {
|
|
7827
|
+
oncreate: (vnode) => {
|
|
7828
|
+
state.spanStartAmPm = vnode.dom;
|
|
7829
|
+
state.spanStartAmPm.innerHTML = ` ${state.tempStartTime.amOrPm}`;
|
|
7830
|
+
},
|
|
7831
|
+
}, ` ${state.tempStartTime.amOrPm}`),
|
|
7832
|
+
]),
|
|
7833
|
+
]),
|
|
7834
|
+
m('.timerange-time-section', { class: state.currentSelection === 'end' ? 'active' : '' }, [
|
|
7835
|
+
m('.timerange-label', 'End'),
|
|
7836
|
+
m('.timerange-time', [
|
|
7837
|
+
m('span.timerange-hours', {
|
|
7838
|
+
oncreate: (vnode) => {
|
|
7839
|
+
state.spanEndHours = vnode.dom;
|
|
7840
|
+
state.spanEndHours.innerHTML = addLeadingZero(state.tempEndTime.hours);
|
|
7841
|
+
},
|
|
7842
|
+
}, addLeadingZero(state.tempEndTime.hours)),
|
|
7843
|
+
':',
|
|
7844
|
+
m('span.timerange-minutes', {
|
|
7845
|
+
oncreate: (vnode) => {
|
|
7846
|
+
state.spanEndMinutes = vnode.dom;
|
|
7847
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(state.tempEndTime.minutes);
|
|
7848
|
+
},
|
|
7849
|
+
}, addLeadingZero(state.tempEndTime.minutes)),
|
|
7850
|
+
twelveHour &&
|
|
7851
|
+
m('span.timerange-ampm', {
|
|
7852
|
+
oncreate: (vnode) => {
|
|
7853
|
+
state.spanEndAmPm = vnode.dom;
|
|
7854
|
+
state.spanEndAmPm.innerHTML = ` ${state.tempEndTime.amOrPm}`;
|
|
7855
|
+
},
|
|
7856
|
+
}, ` ${state.tempEndTime.amOrPm}`),
|
|
7857
|
+
]),
|
|
7858
|
+
]),
|
|
7859
|
+
]),
|
|
7860
|
+
// Clock picker (analog or digital mode)
|
|
7861
|
+
isAnalogMode
|
|
7862
|
+
? m('.timepicker-analog-display', [
|
|
7863
|
+
m('.timepicker-plate', m(AnalogClock, {
|
|
7864
|
+
key: state.currentSelection, // Force component recreation when switching between start/end
|
|
7865
|
+
hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
|
|
7866
|
+
minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
|
|
7867
|
+
amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
|
|
7868
|
+
currentView: state.currentView,
|
|
7869
|
+
twelveHour,
|
|
7870
|
+
dialRadius,
|
|
7871
|
+
outerRadius,
|
|
7872
|
+
innerRadius,
|
|
7873
|
+
tickRadius,
|
|
7874
|
+
roundBy5,
|
|
7875
|
+
vibrate,
|
|
7876
|
+
onTimeChange: (hours, minutes) => {
|
|
7877
|
+
if (state.currentSelection === 'start') {
|
|
7878
|
+
state.tempStartTime = Object.assign(Object.assign({}, state.tempStartTime), { hours, minutes });
|
|
7879
|
+
if (state.spanStartHours)
|
|
7880
|
+
state.spanStartHours.innerHTML = addLeadingZero(hours);
|
|
7881
|
+
if (state.spanStartMinutes)
|
|
7882
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
|
|
7883
|
+
}
|
|
7884
|
+
else {
|
|
7885
|
+
state.tempEndTime = Object.assign(Object.assign({}, state.tempEndTime), { hours, minutes });
|
|
7886
|
+
if (state.spanEndHours)
|
|
7887
|
+
state.spanEndHours.innerHTML = addLeadingZero(hours);
|
|
7888
|
+
if (state.spanEndMinutes)
|
|
7889
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
|
|
7890
|
+
}
|
|
7891
|
+
},
|
|
7892
|
+
onViewChange: (view) => {
|
|
7893
|
+
state.currentView = view;
|
|
7894
|
+
},
|
|
7895
|
+
spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
|
|
7896
|
+
spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
|
|
7897
|
+
})),
|
|
7898
|
+
// Footer (inside analog display)
|
|
7899
|
+
m('.timepicker-footer', [
|
|
7900
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
7901
|
+
type: 'button',
|
|
7902
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
7903
|
+
onclick: () => {
|
|
7904
|
+
state.isPickerOpen = false;
|
|
7905
|
+
},
|
|
7906
|
+
}, i18n.clear),
|
|
7907
|
+
m('.confirmation-btns', [
|
|
7908
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7909
|
+
type: 'button',
|
|
7910
|
+
onclick: () => {
|
|
7911
|
+
state.isPickerOpen = false;
|
|
7912
|
+
state.currentSelection = 'start';
|
|
7913
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
7914
|
+
},
|
|
7915
|
+
}, i18n.cancel),
|
|
7916
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7917
|
+
type: 'button',
|
|
7918
|
+
onclick: () => {
|
|
7919
|
+
handleNextOrDone(validateRange, twelveHour, attrs.onchange);
|
|
7920
|
+
},
|
|
7921
|
+
}, state.currentSelection === 'start' ? i18n.next : i18n.done),
|
|
7922
|
+
]),
|
|
7923
|
+
]),
|
|
7924
|
+
])
|
|
7925
|
+
: m('.timepicker-digital-mode', [
|
|
7926
|
+
m(DigitalClock, {
|
|
7927
|
+
key: state.currentSelection, // Force component recreation when switching between start/end
|
|
7928
|
+
hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
|
|
7929
|
+
minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
|
|
7930
|
+
amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
|
|
7931
|
+
twelveHour,
|
|
7932
|
+
minuteStep,
|
|
7933
|
+
hourStep,
|
|
7934
|
+
minTime: effectiveMinTime,
|
|
7935
|
+
maxTime,
|
|
7936
|
+
onTimeChange: (hours, minutes, amOrPm) => {
|
|
7937
|
+
if (state.currentSelection === 'start') {
|
|
7938
|
+
state.tempStartTime = { hours, minutes, amOrPm };
|
|
7939
|
+
if (state.spanStartHours)
|
|
7940
|
+
state.spanStartHours.innerHTML = addLeadingZero(hours);
|
|
7941
|
+
if (state.spanStartMinutes)
|
|
7942
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
|
|
7943
|
+
if (state.spanStartAmPm)
|
|
7944
|
+
state.spanStartAmPm.innerHTML = ` ${amOrPm}`;
|
|
7945
|
+
}
|
|
7946
|
+
else {
|
|
7947
|
+
state.tempEndTime = { hours, minutes, amOrPm };
|
|
7948
|
+
if (state.spanEndHours)
|
|
7949
|
+
state.spanEndHours.innerHTML = addLeadingZero(hours);
|
|
7950
|
+
if (state.spanEndMinutes)
|
|
7951
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
|
|
7952
|
+
if (state.spanEndAmPm)
|
|
7953
|
+
state.spanEndAmPm.innerHTML = ` ${amOrPm}`;
|
|
7954
|
+
}
|
|
7955
|
+
},
|
|
7956
|
+
spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
|
|
7957
|
+
spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
|
|
7958
|
+
spanAmPm: state.currentSelection === 'start' ? state.spanStartAmPm : state.spanEndAmPm,
|
|
7959
|
+
}),
|
|
7960
|
+
// Footer (inside digital mode)
|
|
7961
|
+
m('.timepicker-footer', { key: 'timepicker-footer' }, [
|
|
7962
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
7963
|
+
type: 'button',
|
|
7964
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
7965
|
+
onclick: () => {
|
|
7966
|
+
state.isPickerOpen = false;
|
|
7967
|
+
},
|
|
7968
|
+
}, i18n.clear),
|
|
7969
|
+
m('.confirmation-btns', [
|
|
7970
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7971
|
+
type: 'button',
|
|
7972
|
+
onclick: () => {
|
|
7973
|
+
state.isPickerOpen = false;
|
|
7974
|
+
state.currentSelection = 'start';
|
|
7975
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
7976
|
+
},
|
|
7977
|
+
}, i18n.cancel),
|
|
7978
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7979
|
+
type: 'button',
|
|
7980
|
+
onclick: () => {
|
|
7981
|
+
handleNextOrDone(validateRange, twelveHour, attrs.onchange);
|
|
7982
|
+
},
|
|
7983
|
+
}, state.currentSelection === 'start' ? i18n.next : i18n.done),
|
|
7984
|
+
]),
|
|
7985
|
+
]),
|
|
7986
|
+
]),
|
|
7987
|
+
]);
|
|
7988
|
+
},
|
|
7989
|
+
};
|
|
7990
|
+
};
|
|
7991
|
+
const handleKeyDown = (e) => {
|
|
7992
|
+
if (e.key === 'Escape' && state.isPickerOpen) {
|
|
7993
|
+
state.isPickerOpen = false;
|
|
7994
|
+
clearPortal(state.portalContainerId);
|
|
7995
|
+
}
|
|
7996
|
+
};
|
|
7997
|
+
const renderPickerToPortal = (attrs) => {
|
|
7998
|
+
const mergedI18n = Object.assign(Object.assign({}, defaultI18n$2), attrs.i18n);
|
|
7999
|
+
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
8000
|
+
style: {
|
|
8001
|
+
position: 'fixed',
|
|
8002
|
+
top: '0',
|
|
8003
|
+
left: '0',
|
|
8004
|
+
width: '100%',
|
|
8005
|
+
height: '100%',
|
|
8006
|
+
pointerEvents: 'auto',
|
|
8007
|
+
display: 'flex',
|
|
8008
|
+
alignItems: 'center',
|
|
8009
|
+
justifyContent: 'center',
|
|
8010
|
+
},
|
|
8011
|
+
}, [
|
|
8012
|
+
// Modal overlay
|
|
8013
|
+
m('.modal-overlay', {
|
|
8014
|
+
style: {
|
|
8015
|
+
position: 'absolute',
|
|
8016
|
+
top: '0',
|
|
8017
|
+
left: '0',
|
|
8018
|
+
width: '100%',
|
|
8019
|
+
height: '100%',
|
|
8020
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
8021
|
+
zIndex: '1002',
|
|
8022
|
+
},
|
|
8023
|
+
onclick: () => {
|
|
8024
|
+
state.isPickerOpen = false;
|
|
8025
|
+
state.currentSelection = 'start';
|
|
8026
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
8027
|
+
},
|
|
8028
|
+
}),
|
|
8029
|
+
// Modal content
|
|
8030
|
+
m('.modal.timepicker-modal.open.timerange-modal', {
|
|
8031
|
+
style: {
|
|
8032
|
+
position: 'relative',
|
|
8033
|
+
zIndex: '1003',
|
|
8034
|
+
display: 'block',
|
|
8035
|
+
opacity: 1,
|
|
8036
|
+
top: 'auto',
|
|
8037
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
8038
|
+
margin: '0 auto',
|
|
8039
|
+
},
|
|
8040
|
+
}, [
|
|
8041
|
+
m(TimeRangePickerModal, {
|
|
8042
|
+
i18n: mergedI18n,
|
|
8043
|
+
showClearBtn: attrs.showClearBtn || false,
|
|
8044
|
+
twelveHour: attrs.twelveHour !== undefined ? attrs.twelveHour : true,
|
|
8045
|
+
minuteStep: attrs.minuteStep || 5,
|
|
8046
|
+
hourStep: attrs.hourStep || 1,
|
|
8047
|
+
minTime: attrs.minTime,
|
|
8048
|
+
maxTime: attrs.maxTime,
|
|
8049
|
+
validateRange: attrs.validateRange || false,
|
|
8050
|
+
onchange: attrs.onchange,
|
|
8051
|
+
displayMode: attrs.displayMode,
|
|
8052
|
+
dialRadius: attrs.dialRadius,
|
|
8053
|
+
outerRadius: attrs.outerRadius,
|
|
8054
|
+
innerRadius: attrs.innerRadius,
|
|
8055
|
+
tickRadius: attrs.tickRadius,
|
|
8056
|
+
roundBy5: attrs.roundBy5,
|
|
8057
|
+
vibrate: attrs.vibrate,
|
|
8058
|
+
}),
|
|
8059
|
+
]),
|
|
8060
|
+
]);
|
|
8061
|
+
renderToPortal(state.portalContainerId, pickerModal, 1004);
|
|
8062
|
+
};
|
|
8063
|
+
return {
|
|
8064
|
+
oninit: (vnode) => {
|
|
8065
|
+
const attrs = vnode.attrs;
|
|
8066
|
+
const twelveHour = attrs.twelveHour !== undefined ? attrs.twelveHour : true;
|
|
8067
|
+
const startTime = parseTime(attrs.startValue || '', twelveHour);
|
|
8068
|
+
const endTime = parseTime(attrs.endValue || '', twelveHour);
|
|
8069
|
+
state = {
|
|
8070
|
+
id: uniqueId(),
|
|
8071
|
+
currentSelection: 'start',
|
|
8072
|
+
startTime,
|
|
8073
|
+
endTime,
|
|
8074
|
+
tempStartTime: Object.assign({}, startTime),
|
|
8075
|
+
tempEndTime: Object.assign({}, endTime),
|
|
8076
|
+
isPickerOpen: false,
|
|
8077
|
+
portalContainerId: `timerange-portal-${uniqueId()}`,
|
|
8078
|
+
currentView: 'hours',
|
|
8079
|
+
};
|
|
8080
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
8081
|
+
},
|
|
8082
|
+
onremove: () => {
|
|
8083
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
8084
|
+
if (state.isPickerOpen) {
|
|
8085
|
+
clearPortal(state.portalContainerId);
|
|
8086
|
+
}
|
|
8087
|
+
},
|
|
8088
|
+
onupdate: ({ attrs }) => {
|
|
8089
|
+
if (state.isPickerOpen) {
|
|
8090
|
+
renderPickerToPortal(attrs);
|
|
8091
|
+
}
|
|
8092
|
+
else {
|
|
8093
|
+
clearPortal(state.portalContainerId);
|
|
8094
|
+
}
|
|
8095
|
+
},
|
|
8096
|
+
view: ({ attrs }) => {
|
|
8097
|
+
const { id = state.id, label, placeholder = 'Select time range', disabled, readonly, required, iconName, helperText, className: cn1, class: cn2, twelveHour = true, } = attrs;
|
|
8098
|
+
const className = cn1 || cn2 || 'col s12';
|
|
8099
|
+
const displayValue = state.startTime && state.endTime
|
|
8100
|
+
? `${formatTime(state.startTime, twelveHour)} - ${formatTime(state.endTime, twelveHour)}`
|
|
8101
|
+
: state.startTime
|
|
8102
|
+
? `${formatTime(state.startTime, twelveHour)} - ...`
|
|
8103
|
+
: '';
|
|
8104
|
+
return m('.input-field', { className }, [
|
|
8105
|
+
iconName && m('i.material-icons.prefix', iconName),
|
|
8106
|
+
// Display input
|
|
8107
|
+
m('input.timerangepicker', {
|
|
8108
|
+
id,
|
|
8109
|
+
type: 'text',
|
|
8110
|
+
value: displayValue,
|
|
8111
|
+
placeholder,
|
|
8112
|
+
readonly: true,
|
|
8113
|
+
disabled,
|
|
8114
|
+
required,
|
|
8115
|
+
onclick: () => {
|
|
8116
|
+
if (!disabled && !readonly) {
|
|
8117
|
+
state.isPickerOpen = true;
|
|
8118
|
+
state.currentSelection = 'start';
|
|
8119
|
+
state.currentView = 'hours'; // Reset to hours view when opening
|
|
8120
|
+
state.tempStartTime = Object.assign({}, state.startTime);
|
|
8121
|
+
state.tempEndTime = Object.assign({}, state.endTime);
|
|
8122
|
+
}
|
|
8123
|
+
},
|
|
8124
|
+
}),
|
|
8125
|
+
label &&
|
|
8126
|
+
m('label', {
|
|
8127
|
+
for: id,
|
|
8128
|
+
class: displayValue || placeholder ? 'active' : '',
|
|
8129
|
+
}, label),
|
|
8130
|
+
helperText && m('span.helper-text', helperText),
|
|
8131
|
+
]);
|
|
8132
|
+
},
|
|
8133
|
+
};
|
|
8134
|
+
};
|
|
8135
|
+
|
|
7123
8136
|
class Toast {
|
|
7124
8137
|
constructor(options = {}) {
|
|
7125
8138
|
this.options = Object.assign(Object.assign({}, Toast.defaults), options);
|
|
@@ -9723,6 +10736,7 @@
|
|
|
9723
10736
|
// ============================================================================
|
|
9724
10737
|
// All types are already exported via individual export declarations above
|
|
9725
10738
|
|
|
10739
|
+
exports.AnalogClock = AnalogClock;
|
|
9726
10740
|
exports.AnchorItem = AnchorItem;
|
|
9727
10741
|
exports.Autocomplete = Autocomplete;
|
|
9728
10742
|
exports.Breadcrumb = Breadcrumb;
|
|
@@ -9739,6 +10753,7 @@
|
|
|
9739
10753
|
exports.ColorInput = ColorInput;
|
|
9740
10754
|
exports.DataTable = DataTable;
|
|
9741
10755
|
exports.DatePicker = DatePicker;
|
|
10756
|
+
exports.DigitalClock = DigitalClock;
|
|
9742
10757
|
exports.DoubleRangeSlider = DoubleRangeSlider;
|
|
9743
10758
|
exports.Dropdown = Dropdown;
|
|
9744
10759
|
exports.EmailInput = EmailInput;
|
|
@@ -9791,6 +10806,7 @@
|
|
|
9791
10806
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
9792
10807
|
exports.ThemeToggle = ThemeToggle;
|
|
9793
10808
|
exports.TimePicker = TimePicker;
|
|
10809
|
+
exports.TimeRangePicker = TimeRangePicker;
|
|
9794
10810
|
exports.Timeline = Timeline;
|
|
9795
10811
|
exports.Toast = Toast;
|
|
9796
10812
|
exports.ToastComponent = ToastComponent;
|
|
@@ -9799,19 +10815,29 @@
|
|
|
9799
10815
|
exports.TreeView = TreeView;
|
|
9800
10816
|
exports.UrlInput = UrlInput;
|
|
9801
10817
|
exports.Wizard = Wizard;
|
|
10818
|
+
exports.addLeadingZero = addLeadingZero;
|
|
9802
10819
|
exports.clearPortal = clearPortal;
|
|
9803
10820
|
exports.createBreadcrumb = createBreadcrumb;
|
|
10821
|
+
exports.formatTime = formatTime;
|
|
10822
|
+
exports.generateHourOptions = generateHourOptions;
|
|
10823
|
+
exports.generateMinuteOptions = generateMinuteOptions;
|
|
9804
10824
|
exports.getDropdownStyles = getDropdownStyles;
|
|
9805
10825
|
exports.getPortalContainer = getPortalContainer;
|
|
9806
10826
|
exports.initPushpins = initPushpins;
|
|
9807
10827
|
exports.initTooltips = initTooltips;
|
|
9808
10828
|
exports.isNumeric = isNumeric;
|
|
10829
|
+
exports.isTimeDisabled = isTimeDisabled;
|
|
9809
10830
|
exports.isValidationError = isValidationError;
|
|
9810
10831
|
exports.isValidationSuccess = isValidationSuccess;
|
|
9811
10832
|
exports.padLeft = padLeft;
|
|
10833
|
+
exports.parseTime = parseTime;
|
|
9812
10834
|
exports.range = range;
|
|
9813
10835
|
exports.releasePortalContainer = releasePortalContainer;
|
|
9814
10836
|
exports.renderToPortal = renderToPortal;
|
|
10837
|
+
exports.scrollToValue = scrollToValue;
|
|
10838
|
+
exports.snapToNearestItem = snapToNearestItem;
|
|
10839
|
+
exports.sortOptions = sortOptions;
|
|
10840
|
+
exports.timeToMinutes = timeToMinutes;
|
|
9815
10841
|
exports.toast = toast;
|
|
9816
10842
|
exports.uniqueId = uniqueId;
|
|
9817
10843
|
exports.uuid4 = uuid4;
|