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