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