mithril-materialized 3.6.0 → 3.8.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/digital-clock.d.ts +48 -0
- package/dist/index.css +201 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +1450 -412
- package/dist/index.js +1462 -411
- package/dist/index.min.css +1 -1
- package/dist/index.umd.js +1462 -411
- package/dist/pickers.css +170 -0
- package/dist/select.d.ts +2 -1
- 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/toggle-button.d.ts +46 -0
- package/dist/toggle-group.d.ts +84 -0
- package/dist/utils.d.ts +3 -10
- package/package.json +1 -1
- package/sass/components/_timepicker.scss +196 -1
- package/sass/components/_toggle-group.scss +36 -0
- package/sass/materialize.scss +1 -0
package/dist/index.umd.js
CHANGED
|
@@ -1610,7 +1610,7 @@
|
|
|
1610
1610
|
};
|
|
1611
1611
|
};
|
|
1612
1612
|
|
|
1613
|
-
const defaultI18n$
|
|
1613
|
+
const defaultI18n$4 = {
|
|
1614
1614
|
cancel: 'Cancel',
|
|
1615
1615
|
clear: 'Clear',
|
|
1616
1616
|
done: 'Ok',
|
|
@@ -1718,9 +1718,9 @@
|
|
|
1718
1718
|
else if (attrs.displayFormat) {
|
|
1719
1719
|
finalFormat = attrs.displayFormat;
|
|
1720
1720
|
}
|
|
1721
|
-
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$
|
|
1721
|
+
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$4, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1722
1722
|
// Merge i18n properly
|
|
1723
|
-
merged.i18n = Object.assign(Object.assign({}, defaultI18n$
|
|
1723
|
+
merged.i18n = Object.assign(Object.assign({}, defaultI18n$4), attrs.i18n);
|
|
1724
1724
|
return merged;
|
|
1725
1725
|
};
|
|
1726
1726
|
const toString = (date, format) => {
|
|
@@ -4521,7 +4521,7 @@
|
|
|
4521
4521
|
opacity: 1,
|
|
4522
4522
|
};
|
|
4523
4523
|
};
|
|
4524
|
-
const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
|
|
4524
|
+
const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
|
|
4525
4525
|
if (!state.isInsideModal)
|
|
4526
4526
|
return;
|
|
4527
4527
|
// Clean up existing portal
|
|
@@ -4565,7 +4565,7 @@
|
|
|
4565
4565
|
// Create dropdown with proper positioning
|
|
4566
4566
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
4567
4567
|
tabindex: 0,
|
|
4568
|
-
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (
|
|
4568
|
+
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
|
|
4569
4569
|
oncreate: ({ dom }) => {
|
|
4570
4570
|
state.dropdownRef = dom;
|
|
4571
4571
|
},
|
|
@@ -4629,7 +4629,7 @@
|
|
|
4629
4629
|
state.focusedIndex = -1;
|
|
4630
4630
|
handleSelection(item.id);
|
|
4631
4631
|
}
|
|
4632
|
-
});
|
|
4632
|
+
}, attrs.maxHeight);
|
|
4633
4633
|
}
|
|
4634
4634
|
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
4635
4635
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
@@ -5298,45 +5298,417 @@
|
|
|
5298
5298
|
};
|
|
5299
5299
|
};
|
|
5300
5300
|
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5301
|
+
/**
|
|
5302
|
+
* Shared utility functions for TimePicker and TimeRangePicker components
|
|
5303
|
+
*/
|
|
5304
|
+
const addLeadingZero = (num) => {
|
|
5305
|
+
return (num < 10 ? '0' : '') + num;
|
|
5305
5306
|
};
|
|
5306
|
-
const
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5307
|
+
const parseTime = (timeStr, twelveHour) => {
|
|
5308
|
+
if (!timeStr) {
|
|
5309
|
+
return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
|
|
5310
|
+
}
|
|
5311
|
+
const parts = timeStr.trim().split(/[:\s]+/);
|
|
5312
|
+
if (parts.length < 2) {
|
|
5313
|
+
return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
|
|
5314
|
+
}
|
|
5315
|
+
let hours = parseInt(parts[0]) || 0;
|
|
5316
|
+
const minutes = parseInt(parts[1]) || 0;
|
|
5317
|
+
let amOrPm = 'AM';
|
|
5318
|
+
const upperStr = timeStr.toUpperCase();
|
|
5319
|
+
if (upperStr.includes('PM')) {
|
|
5320
|
+
amOrPm = 'PM';
|
|
5321
|
+
}
|
|
5322
|
+
else if (upperStr.includes('AM')) {
|
|
5323
|
+
amOrPm = 'AM';
|
|
5324
|
+
}
|
|
5325
|
+
if (twelveHour) {
|
|
5326
|
+
if (hours >= 12) {
|
|
5327
|
+
amOrPm = 'PM';
|
|
5328
|
+
if (hours > 12)
|
|
5329
|
+
hours -= 12;
|
|
5330
|
+
}
|
|
5331
|
+
else if (hours === 0) {
|
|
5332
|
+
hours = 12;
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
return { hours, minutes, amOrPm };
|
|
5336
|
+
};
|
|
5337
|
+
const formatTime = (time, twelveHour) => {
|
|
5338
|
+
if (!time)
|
|
5339
|
+
return '';
|
|
5340
|
+
const { hours, minutes, amOrPm } = time;
|
|
5341
|
+
const hoursStr = addLeadingZero(hours);
|
|
5342
|
+
const minutesStr = addLeadingZero(minutes);
|
|
5343
|
+
if (twelveHour) {
|
|
5344
|
+
return `${hoursStr}:${minutesStr} ${amOrPm}`;
|
|
5345
|
+
}
|
|
5346
|
+
let hours24 = hours;
|
|
5347
|
+
if (amOrPm === 'PM' && hours !== 12) {
|
|
5348
|
+
hours24 = hours + 12;
|
|
5349
|
+
}
|
|
5350
|
+
else if (amOrPm === 'AM' && hours === 12) {
|
|
5351
|
+
hours24 = 0;
|
|
5352
|
+
}
|
|
5353
|
+
return `${addLeadingZero(hours24)}:${minutesStr}`;
|
|
5354
|
+
};
|
|
5355
|
+
const timeToMinutes = (time, twelveHour) => {
|
|
5356
|
+
let h = time.hours;
|
|
5357
|
+
if (twelveHour) {
|
|
5358
|
+
if (time.amOrPm === 'PM' && h !== 12)
|
|
5359
|
+
h += 12;
|
|
5360
|
+
if (time.amOrPm === 'AM' && h === 12)
|
|
5361
|
+
h = 0;
|
|
5362
|
+
}
|
|
5363
|
+
return h * 60 + time.minutes;
|
|
5364
|
+
};
|
|
5365
|
+
const generateHourOptions = (twelveHour, hourStep) => {
|
|
5366
|
+
const start = twelveHour ? 1 : 0;
|
|
5367
|
+
const end = twelveHour ? 12 : 23;
|
|
5368
|
+
const hours = [];
|
|
5369
|
+
for (let i = start; i <= end; i += hourStep) {
|
|
5370
|
+
hours.push(i);
|
|
5371
|
+
}
|
|
5372
|
+
// Special case for 12-hour: include 12 first
|
|
5373
|
+
if (twelveHour && hourStep === 1) {
|
|
5374
|
+
return [12, ...hours.filter((h) => h !== 12)];
|
|
5375
|
+
}
|
|
5376
|
+
return hours;
|
|
5377
|
+
};
|
|
5378
|
+
const generateMinuteOptions = (minuteStep) => {
|
|
5379
|
+
const minutes = [];
|
|
5380
|
+
for (let i = 0; i < 60; i += minuteStep) {
|
|
5381
|
+
minutes.push(i);
|
|
5382
|
+
}
|
|
5383
|
+
return minutes;
|
|
5384
|
+
};
|
|
5385
|
+
const isTimeDisabled = (hours, minutes, amOrPm, minTime, maxTime, twelveHour) => {
|
|
5386
|
+
if (!minTime && !maxTime)
|
|
5387
|
+
return false;
|
|
5388
|
+
const currentMins = timeToMinutes({ hours, minutes, amOrPm }, twelveHour || false);
|
|
5389
|
+
if (minTime) {
|
|
5390
|
+
const minParsed = parseTime(minTime, twelveHour || false);
|
|
5391
|
+
const minMins = timeToMinutes(minParsed, twelveHour || false);
|
|
5392
|
+
if (currentMins < minMins)
|
|
5393
|
+
return true;
|
|
5394
|
+
}
|
|
5395
|
+
if (maxTime) {
|
|
5396
|
+
const maxParsed = parseTime(maxTime, twelveHour || false);
|
|
5397
|
+
const maxMins = timeToMinutes(maxParsed, twelveHour || false);
|
|
5398
|
+
if (currentMins > maxMins)
|
|
5399
|
+
return true;
|
|
5400
|
+
}
|
|
5401
|
+
return false;
|
|
5402
|
+
};
|
|
5403
|
+
const scrollToValue = (container, index, itemHeight, animated = true) => {
|
|
5404
|
+
const scrollTop = index * itemHeight - (container.clientHeight / 2 - itemHeight / 2);
|
|
5405
|
+
if (animated) {
|
|
5406
|
+
container.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
|
5407
|
+
}
|
|
5408
|
+
else {
|
|
5409
|
+
container.scrollTop = scrollTop;
|
|
5410
|
+
}
|
|
5411
|
+
};
|
|
5412
|
+
const snapToNearestItem = (container, itemHeight, onSnap) => {
|
|
5413
|
+
const scrollTop = container.scrollTop;
|
|
5414
|
+
const centerOffset = container.clientHeight / 2;
|
|
5415
|
+
const nearestIndex = Math.round((scrollTop + centerOffset - itemHeight / 2) / itemHeight);
|
|
5416
|
+
scrollToValue(container, nearestIndex, itemHeight, true);
|
|
5417
|
+
onSnap(nearestIndex);
|
|
5327
5418
|
};
|
|
5419
|
+
|
|
5328
5420
|
/**
|
|
5329
|
-
*
|
|
5421
|
+
* DigitalClock component - A scrollable digital time picker
|
|
5422
|
+
*
|
|
5423
|
+
* @example
|
|
5424
|
+
* ```typescript
|
|
5425
|
+
* m(DigitalClock, {
|
|
5426
|
+
* hours: 10,
|
|
5427
|
+
* minutes: 30,
|
|
5428
|
+
* amOrPm: 'AM',
|
|
5429
|
+
* twelveHour: true,
|
|
5430
|
+
* minuteStep: 5,
|
|
5431
|
+
* onTimeChange: (hours, minutes, amOrPm) => {
|
|
5432
|
+
* console.log(`Time changed to ${hours}:${minutes} ${amOrPm}`);
|
|
5433
|
+
* }
|
|
5434
|
+
* })
|
|
5435
|
+
* ```
|
|
5330
5436
|
*/
|
|
5331
|
-
const
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5437
|
+
const DigitalClock = () => {
|
|
5438
|
+
const ITEM_HEIGHT = 48;
|
|
5439
|
+
const state = {};
|
|
5440
|
+
return {
|
|
5441
|
+
view: ({ attrs }) => {
|
|
5442
|
+
const { hours, minutes, amOrPm, twelveHour, minuteStep = 5, hourStep = 1, minTime, maxTime, onTimeChange, spanHours, spanMinutes, spanAmPm, } = attrs;
|
|
5443
|
+
const hourOptions = generateHourOptions(twelveHour, hourStep);
|
|
5444
|
+
const minuteOptions = generateMinuteOptions(minuteStep);
|
|
5445
|
+
return m('.timepicker-digital-clock', [
|
|
5446
|
+
// Hours column
|
|
5447
|
+
m('.digital-clock-column', {
|
|
5448
|
+
oncreate: (vnode) => {
|
|
5449
|
+
state.hourScrollContainer = vnode.dom;
|
|
5450
|
+
const currentIndex = hourOptions.indexOf(hours);
|
|
5451
|
+
if (currentIndex >= 0) {
|
|
5452
|
+
scrollToValue(state.hourScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5453
|
+
}
|
|
5454
|
+
},
|
|
5455
|
+
onwheel: (e) => {
|
|
5456
|
+
e.preventDefault();
|
|
5457
|
+
if (!state.hourScrollContainer)
|
|
5458
|
+
return;
|
|
5459
|
+
const delta = Math.sign(e.deltaY);
|
|
5460
|
+
const currentIndex = hourOptions.indexOf(hours);
|
|
5461
|
+
const newIndex = Math.max(0, Math.min(hourOptions.length - 1, currentIndex + delta));
|
|
5462
|
+
const newHour = hourOptions[newIndex];
|
|
5463
|
+
if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5464
|
+
onTimeChange(newHour, minutes, amOrPm);
|
|
5465
|
+
if (spanHours) {
|
|
5466
|
+
spanHours.innerHTML = addLeadingZero(newHour);
|
|
5467
|
+
}
|
|
5468
|
+
scrollToValue(state.hourScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5469
|
+
m.redraw();
|
|
5470
|
+
}
|
|
5471
|
+
},
|
|
5472
|
+
onscroll: () => {
|
|
5473
|
+
if (state.hourScrollTimeout) {
|
|
5474
|
+
clearTimeout(state.hourScrollTimeout);
|
|
5475
|
+
}
|
|
5476
|
+
state.hourScrollTimeout = window.setTimeout(() => {
|
|
5477
|
+
if (!state.hourScrollContainer)
|
|
5478
|
+
return;
|
|
5479
|
+
snapToNearestItem(state.hourScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5480
|
+
const actualIndex = index - 2; // Account for padding
|
|
5481
|
+
if (actualIndex >= 0 && actualIndex < hourOptions.length) {
|
|
5482
|
+
const newHour = hourOptions[actualIndex];
|
|
5483
|
+
if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5484
|
+
onTimeChange(newHour, minutes, amOrPm);
|
|
5485
|
+
if (spanHours) {
|
|
5486
|
+
spanHours.innerHTML = addLeadingZero(newHour);
|
|
5487
|
+
}
|
|
5488
|
+
m.redraw();
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
});
|
|
5492
|
+
}, 150);
|
|
5493
|
+
},
|
|
5494
|
+
}, [
|
|
5495
|
+
// Padding items for centering
|
|
5496
|
+
m('.digital-clock-item.padding'),
|
|
5497
|
+
m('.digital-clock-item.padding'),
|
|
5498
|
+
// Hour items
|
|
5499
|
+
...hourOptions.map((hour) => {
|
|
5500
|
+
const disabled = isTimeDisabled(hour, minutes, amOrPm, minTime, maxTime, twelveHour);
|
|
5501
|
+
return m('.digital-clock-item', {
|
|
5502
|
+
class: `${hour === hours ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5503
|
+
onclick: () => {
|
|
5504
|
+
if (disabled)
|
|
5505
|
+
return;
|
|
5506
|
+
onTimeChange(hour, minutes, amOrPm);
|
|
5507
|
+
if (spanHours) {
|
|
5508
|
+
spanHours.innerHTML = addLeadingZero(hour);
|
|
5509
|
+
}
|
|
5510
|
+
if (state.hourScrollContainer) {
|
|
5511
|
+
const index = hourOptions.indexOf(hour);
|
|
5512
|
+
scrollToValue(state.hourScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5513
|
+
}
|
|
5514
|
+
m.redraw();
|
|
5515
|
+
},
|
|
5516
|
+
}, addLeadingZero(hour));
|
|
5517
|
+
}),
|
|
5518
|
+
// Padding items for centering
|
|
5519
|
+
m('.digital-clock-item.padding'),
|
|
5520
|
+
m('.digital-clock-item.padding'),
|
|
5521
|
+
]),
|
|
5522
|
+
// Separator
|
|
5523
|
+
m('.digital-clock-separator', ':'),
|
|
5524
|
+
// Minutes column
|
|
5525
|
+
m('.digital-clock-column', {
|
|
5526
|
+
oncreate: (vnode) => {
|
|
5527
|
+
state.minuteScrollContainer = vnode.dom;
|
|
5528
|
+
const currentIndex = minuteOptions.indexOf(minutes);
|
|
5529
|
+
if (currentIndex >= 0) {
|
|
5530
|
+
scrollToValue(state.minuteScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5531
|
+
}
|
|
5532
|
+
},
|
|
5533
|
+
onwheel: (e) => {
|
|
5534
|
+
e.preventDefault();
|
|
5535
|
+
if (!state.minuteScrollContainer)
|
|
5536
|
+
return;
|
|
5537
|
+
const delta = Math.sign(e.deltaY);
|
|
5538
|
+
const currentIndex = minuteOptions.indexOf(minutes);
|
|
5539
|
+
const newIndex = Math.max(0, Math.min(minuteOptions.length - 1, currentIndex + delta));
|
|
5540
|
+
const newMinute = minuteOptions[newIndex];
|
|
5541
|
+
if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5542
|
+
onTimeChange(hours, newMinute, amOrPm);
|
|
5543
|
+
if (spanMinutes) {
|
|
5544
|
+
spanMinutes.innerHTML = addLeadingZero(newMinute);
|
|
5545
|
+
}
|
|
5546
|
+
scrollToValue(state.minuteScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5547
|
+
m.redraw();
|
|
5548
|
+
}
|
|
5549
|
+
},
|
|
5550
|
+
onscroll: () => {
|
|
5551
|
+
if (state.minuteScrollTimeout) {
|
|
5552
|
+
clearTimeout(state.minuteScrollTimeout);
|
|
5553
|
+
}
|
|
5554
|
+
state.minuteScrollTimeout = window.setTimeout(() => {
|
|
5555
|
+
if (!state.minuteScrollContainer)
|
|
5556
|
+
return;
|
|
5557
|
+
snapToNearestItem(state.minuteScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5558
|
+
const actualIndex = index - 2; // Account for padding
|
|
5559
|
+
if (actualIndex >= 0 && actualIndex < minuteOptions.length) {
|
|
5560
|
+
const newMinute = minuteOptions[actualIndex];
|
|
5561
|
+
if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
|
|
5562
|
+
onTimeChange(hours, newMinute, amOrPm);
|
|
5563
|
+
if (spanMinutes) {
|
|
5564
|
+
spanMinutes.innerHTML = addLeadingZero(newMinute);
|
|
5565
|
+
}
|
|
5566
|
+
m.redraw();
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
});
|
|
5570
|
+
}, 150);
|
|
5571
|
+
},
|
|
5572
|
+
}, [
|
|
5573
|
+
// Padding items for centering
|
|
5574
|
+
m('.digital-clock-item.padding'),
|
|
5575
|
+
m('.digital-clock-item.padding'),
|
|
5576
|
+
// Minute items
|
|
5577
|
+
...minuteOptions.map((minute) => {
|
|
5578
|
+
const disabled = isTimeDisabled(hours, minute, amOrPm, minTime, maxTime, twelveHour);
|
|
5579
|
+
return m('.digital-clock-item', {
|
|
5580
|
+
class: `${minute === minutes ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5581
|
+
onclick: () => {
|
|
5582
|
+
if (disabled)
|
|
5583
|
+
return;
|
|
5584
|
+
onTimeChange(hours, minute, amOrPm);
|
|
5585
|
+
if (spanMinutes) {
|
|
5586
|
+
spanMinutes.innerHTML = addLeadingZero(minute);
|
|
5587
|
+
}
|
|
5588
|
+
if (state.minuteScrollContainer) {
|
|
5589
|
+
const index = minuteOptions.indexOf(minute);
|
|
5590
|
+
scrollToValue(state.minuteScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5591
|
+
}
|
|
5592
|
+
m.redraw();
|
|
5593
|
+
},
|
|
5594
|
+
}, addLeadingZero(minute));
|
|
5595
|
+
}),
|
|
5596
|
+
// Padding items for centering
|
|
5597
|
+
m('.digital-clock-item.padding'),
|
|
5598
|
+
m('.digital-clock-item.padding'),
|
|
5599
|
+
]),
|
|
5600
|
+
// AM/PM column (if twelveHour)
|
|
5601
|
+
twelveHour &&
|
|
5602
|
+
m('.digital-clock-column.ampm-column', {
|
|
5603
|
+
oncreate: (vnode) => {
|
|
5604
|
+
state.amPmScrollContainer = vnode.dom;
|
|
5605
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5606
|
+
const currentIndex = amPmOptions.indexOf(amOrPm);
|
|
5607
|
+
if (currentIndex >= 0) {
|
|
5608
|
+
scrollToValue(state.amPmScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
|
|
5609
|
+
}
|
|
5610
|
+
},
|
|
5611
|
+
onwheel: (e) => {
|
|
5612
|
+
e.preventDefault();
|
|
5613
|
+
const delta = Math.sign(e.deltaY);
|
|
5614
|
+
const newAmPm = delta > 0 ? 'PM' : 'AM';
|
|
5615
|
+
if (newAmPm !== amOrPm && !isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
|
|
5616
|
+
onTimeChange(hours, minutes, newAmPm);
|
|
5617
|
+
if (spanAmPm) {
|
|
5618
|
+
spanAmPm.innerHTML = newAmPm;
|
|
5619
|
+
}
|
|
5620
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5621
|
+
const newIndex = amPmOptions.indexOf(newAmPm);
|
|
5622
|
+
if (state.amPmScrollContainer) {
|
|
5623
|
+
scrollToValue(state.amPmScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
|
|
5624
|
+
}
|
|
5625
|
+
m.redraw();
|
|
5626
|
+
}
|
|
5627
|
+
},
|
|
5628
|
+
onscroll: () => {
|
|
5629
|
+
if (state.amPmScrollTimeout) {
|
|
5630
|
+
clearTimeout(state.amPmScrollTimeout);
|
|
5631
|
+
}
|
|
5632
|
+
state.amPmScrollTimeout = window.setTimeout(() => {
|
|
5633
|
+
if (!state.amPmScrollContainer)
|
|
5634
|
+
return;
|
|
5635
|
+
snapToNearestItem(state.amPmScrollContainer, ITEM_HEIGHT, (index) => {
|
|
5636
|
+
const actualIndex = index - 2;
|
|
5637
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5638
|
+
if (actualIndex >= 0 && actualIndex < amPmOptions.length) {
|
|
5639
|
+
const newAmPm = amPmOptions[actualIndex];
|
|
5640
|
+
if (!isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
|
|
5641
|
+
onTimeChange(hours, minutes, newAmPm);
|
|
5642
|
+
if (spanAmPm) {
|
|
5643
|
+
spanAmPm.innerHTML = newAmPm;
|
|
5644
|
+
}
|
|
5645
|
+
m.redraw();
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
});
|
|
5649
|
+
}, 150);
|
|
5650
|
+
},
|
|
5651
|
+
}, [
|
|
5652
|
+
// Padding items
|
|
5653
|
+
m('.digital-clock-item.padding'),
|
|
5654
|
+
m('.digital-clock-item.padding'),
|
|
5655
|
+
// AM/PM items
|
|
5656
|
+
['AM', 'PM'].map((ampm) => {
|
|
5657
|
+
const disabled = isTimeDisabled(hours, minutes, ampm, minTime, maxTime, twelveHour);
|
|
5658
|
+
return m('.digital-clock-item', {
|
|
5659
|
+
class: `${ampm === amOrPm ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
|
|
5660
|
+
onclick: () => {
|
|
5661
|
+
if (disabled)
|
|
5662
|
+
return;
|
|
5663
|
+
onTimeChange(hours, minutes, ampm);
|
|
5664
|
+
if (spanAmPm) {
|
|
5665
|
+
spanAmPm.innerHTML = ampm;
|
|
5666
|
+
}
|
|
5667
|
+
if (state.amPmScrollContainer) {
|
|
5668
|
+
const amPmOptions = ['AM', 'PM'];
|
|
5669
|
+
const index = amPmOptions.indexOf(ampm);
|
|
5670
|
+
scrollToValue(state.amPmScrollContainer, index + 2, ITEM_HEIGHT, true);
|
|
5671
|
+
}
|
|
5672
|
+
m.redraw();
|
|
5673
|
+
},
|
|
5674
|
+
}, ampm);
|
|
5675
|
+
}),
|
|
5676
|
+
// Padding items
|
|
5677
|
+
m('.digital-clock-item.padding'),
|
|
5678
|
+
m('.digital-clock-item.padding'),
|
|
5679
|
+
]),
|
|
5680
|
+
]);
|
|
5681
|
+
},
|
|
5336
5682
|
};
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5683
|
+
};
|
|
5684
|
+
|
|
5685
|
+
/**
|
|
5686
|
+
* AnalogClock component - A draggable analog clock face for time selection
|
|
5687
|
+
*
|
|
5688
|
+
* @example
|
|
5689
|
+
* ```typescript
|
|
5690
|
+
* m(AnalogClock, {
|
|
5691
|
+
* hours: 10,
|
|
5692
|
+
* minutes: 30,
|
|
5693
|
+
* amOrPm: 'AM',
|
|
5694
|
+
* currentView: 'hours',
|
|
5695
|
+
* twelveHour: true,
|
|
5696
|
+
* onTimeChange: (hours, minutes) => {
|
|
5697
|
+
* console.log(`Time changed to ${hours}:${minutes}`);
|
|
5698
|
+
* },
|
|
5699
|
+
* onViewChange: (view) => {
|
|
5700
|
+
* console.log(`View changed to ${view}`);
|
|
5701
|
+
* }
|
|
5702
|
+
* })
|
|
5703
|
+
* ```
|
|
5704
|
+
*/
|
|
5705
|
+
const AnalogClock = () => {
|
|
5706
|
+
const state = {
|
|
5707
|
+
moved: false,
|
|
5708
|
+
x0: 0,
|
|
5709
|
+
y0: 0,
|
|
5710
|
+
dx: 0,
|
|
5711
|
+
dy: 0,
|
|
5340
5712
|
};
|
|
5341
5713
|
const getPos = (e) => {
|
|
5342
5714
|
const touchEvent = e;
|
|
@@ -5346,202 +5718,36 @@
|
|
|
5346
5718
|
}
|
|
5347
5719
|
return { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
5348
5720
|
};
|
|
5349
|
-
const vibrate = () => {
|
|
5721
|
+
const vibrate = (attrs) => {
|
|
5350
5722
|
if (state.vibrateTimer) {
|
|
5351
5723
|
clearTimeout(state.vibrateTimer);
|
|
5352
5724
|
}
|
|
5353
|
-
if (
|
|
5725
|
+
if (attrs.vibrate && navigator.vibrate) {
|
|
5354
5726
|
navigator.vibrate(10);
|
|
5355
5727
|
state.vibrateTimer = window.setTimeout(() => {
|
|
5356
5728
|
state.vibrateTimer = undefined;
|
|
5357
5729
|
}, 100);
|
|
5358
5730
|
}
|
|
5359
5731
|
};
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
const clockPlateBR = state.plate.getBoundingClientRect();
|
|
5365
|
-
const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
|
|
5366
|
-
state.x0 = offset.x + options.dialRadius;
|
|
5367
|
-
state.y0 = offset.y + options.dialRadius;
|
|
5368
|
-
state.moved = false;
|
|
5369
|
-
const clickPos = getPos(e);
|
|
5370
|
-
state.dx = clickPos.x - state.x0;
|
|
5371
|
-
state.dy = clickPos.y - state.y0;
|
|
5372
|
-
setHand(state.dx, state.dy, options.roundBy5);
|
|
5373
|
-
document.addEventListener('mousemove', handleDocumentClickMove);
|
|
5374
|
-
document.addEventListener('touchmove', handleDocumentClickMove);
|
|
5375
|
-
document.addEventListener('mouseup', handleDocumentClickEnd);
|
|
5376
|
-
document.addEventListener('touchend', handleDocumentClickEnd);
|
|
5377
|
-
};
|
|
5378
|
-
const handleDocumentClickMove = (e) => {
|
|
5379
|
-
e.preventDefault();
|
|
5380
|
-
const clickPos = getPos(e);
|
|
5381
|
-
const x = clickPos.x - state.x0;
|
|
5382
|
-
const y = clickPos.y - state.y0;
|
|
5383
|
-
state.moved = true;
|
|
5384
|
-
setHand(x, y, options.roundBy5);
|
|
5385
|
-
m.redraw();
|
|
5386
|
-
};
|
|
5387
|
-
const handleDocumentClickEnd = (e) => {
|
|
5388
|
-
e.preventDefault();
|
|
5389
|
-
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
5390
|
-
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
5391
|
-
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
5392
|
-
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
5393
|
-
const clickPos = getPos(e);
|
|
5394
|
-
const x = clickPos.x - state.x0;
|
|
5395
|
-
const y = clickPos.y - state.y0;
|
|
5396
|
-
if (state.moved && x === state.dx && y === state.dy) {
|
|
5397
|
-
setHand(x, y);
|
|
5398
|
-
}
|
|
5399
|
-
if (state.currentView === 'hours') {
|
|
5400
|
-
showView('minutes', options.duration / 2);
|
|
5401
|
-
}
|
|
5402
|
-
else if (options.autoClose) {
|
|
5403
|
-
if (state.minutesView) {
|
|
5404
|
-
state.minutesView.classList.add('timepicker-dial-out');
|
|
5405
|
-
}
|
|
5406
|
-
setTimeout(() => {
|
|
5407
|
-
done();
|
|
5408
|
-
}, options.duration / 2);
|
|
5409
|
-
}
|
|
5410
|
-
if (options.onSelect) {
|
|
5411
|
-
options.onSelect(state.hours, state.minutes);
|
|
5412
|
-
}
|
|
5413
|
-
m.redraw();
|
|
5414
|
-
};
|
|
5415
|
-
const updateTimeFromInput = (inputValue) => {
|
|
5416
|
-
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
5417
|
-
let amPmWasProvided = false;
|
|
5418
|
-
if (options.twelveHour && value.length > 1) {
|
|
5419
|
-
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
5420
|
-
state.amOrPm = 'AM';
|
|
5421
|
-
amPmWasProvided = true;
|
|
5422
|
-
}
|
|
5423
|
-
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
5424
|
-
state.amOrPm = 'PM';
|
|
5425
|
-
amPmWasProvided = true;
|
|
5426
|
-
}
|
|
5427
|
-
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
5428
|
-
}
|
|
5429
|
-
if (value[0] === 'now') {
|
|
5430
|
-
const now = new Date(+new Date() + options.fromNow);
|
|
5431
|
-
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
5432
|
-
if (options.twelveHour) {
|
|
5433
|
-
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
5434
|
-
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
|
-
let hours = +value[0] || 0;
|
|
5438
|
-
let minutes = +value[1] || 0;
|
|
5439
|
-
if (options.twelveHour) {
|
|
5440
|
-
if (!amPmWasProvided) {
|
|
5441
|
-
// No AM/PM was provided, assume this is 24-hour format input - convert it
|
|
5442
|
-
if (hours >= 12) {
|
|
5443
|
-
state.amOrPm = 'PM';
|
|
5444
|
-
if (hours > 12) {
|
|
5445
|
-
hours = hours - 12;
|
|
5446
|
-
}
|
|
5447
|
-
}
|
|
5448
|
-
else {
|
|
5449
|
-
state.amOrPm = 'AM';
|
|
5450
|
-
if (hours === 0) {
|
|
5451
|
-
hours = 12;
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
}
|
|
5455
|
-
else {
|
|
5456
|
-
// AM/PM was provided, hours are already in 12-hour format
|
|
5457
|
-
// Just handle midnight/noon edge cases
|
|
5458
|
-
if (hours === 0 && state.amOrPm === 'AM') {
|
|
5459
|
-
hours = 12;
|
|
5460
|
-
}
|
|
5461
|
-
}
|
|
5462
|
-
}
|
|
5463
|
-
state.hours = hours;
|
|
5464
|
-
state.minutes = minutes;
|
|
5465
|
-
if (state.spanHours) {
|
|
5466
|
-
state.spanHours.innerHTML = addLeadingZero(state.hours);
|
|
5467
|
-
}
|
|
5468
|
-
if (state.spanMinutes) {
|
|
5469
|
-
state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
|
|
5470
|
-
}
|
|
5471
|
-
updateAmPmView();
|
|
5472
|
-
};
|
|
5473
|
-
const updateAmPmView = () => {
|
|
5474
|
-
if (options.twelveHour && state.amBtn && state.pmBtn) {
|
|
5475
|
-
state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
|
|
5476
|
-
state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
|
|
5477
|
-
}
|
|
5478
|
-
};
|
|
5479
|
-
const showView = (view, delay) => {
|
|
5480
|
-
const isHours = view === 'hours';
|
|
5481
|
-
const nextView = isHours ? state.hoursView : state.minutesView;
|
|
5482
|
-
const hideView = isHours ? state.minutesView : state.hoursView;
|
|
5483
|
-
state.currentView = view;
|
|
5484
|
-
if (state.spanHours) {
|
|
5485
|
-
state.spanHours.classList.toggle('text-primary', isHours);
|
|
5486
|
-
}
|
|
5487
|
-
if (state.spanMinutes) {
|
|
5488
|
-
state.spanMinutes.classList.toggle('text-primary', !isHours);
|
|
5489
|
-
}
|
|
5490
|
-
if (hideView) {
|
|
5491
|
-
hideView.classList.add('timepicker-dial-out');
|
|
5492
|
-
}
|
|
5493
|
-
if (nextView) {
|
|
5494
|
-
nextView.style.visibility = 'visible';
|
|
5495
|
-
nextView.classList.remove('timepicker-dial-out');
|
|
5496
|
-
}
|
|
5497
|
-
resetClock(delay);
|
|
5498
|
-
if (state.toggleViewTimer) {
|
|
5499
|
-
clearTimeout(state.toggleViewTimer);
|
|
5500
|
-
}
|
|
5501
|
-
state.toggleViewTimer = window.setTimeout(() => {
|
|
5502
|
-
if (hideView) {
|
|
5503
|
-
hideView.style.visibility = 'hidden';
|
|
5504
|
-
}
|
|
5505
|
-
}, options.duration);
|
|
5506
|
-
};
|
|
5507
|
-
const resetClock = (delay) => {
|
|
5508
|
-
const view = state.currentView;
|
|
5509
|
-
const value = state[view];
|
|
5510
|
-
const isHours = view === 'hours';
|
|
5511
|
-
const unit = Math.PI / (isHours ? 6 : 30);
|
|
5512
|
-
const radian = value * unit;
|
|
5513
|
-
const radius = isHours && value > 0 && value < 13 ? options.innerRadius : options.outerRadius;
|
|
5514
|
-
const x = Math.sin(radian) * radius;
|
|
5515
|
-
const y = -Math.cos(radian) * radius;
|
|
5516
|
-
if (delay && state.canvas) {
|
|
5517
|
-
state.canvas.classList.add('timepicker-canvas-out');
|
|
5518
|
-
setTimeout(() => {
|
|
5519
|
-
if (state.canvas) {
|
|
5520
|
-
state.canvas.classList.remove('timepicker-canvas-out');
|
|
5521
|
-
}
|
|
5522
|
-
setHand(x, y);
|
|
5523
|
-
}, delay);
|
|
5524
|
-
}
|
|
5525
|
-
else {
|
|
5526
|
-
setHand(x, y);
|
|
5527
|
-
}
|
|
5528
|
-
};
|
|
5529
|
-
const setHand = (x, y, roundBy5, _dragging) => {
|
|
5732
|
+
const setHand = (x, y, attrs, roundBy5, _dragging) => {
|
|
5733
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5734
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5735
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5530
5736
|
let radian = Math.atan2(x, -y);
|
|
5531
|
-
const isHours =
|
|
5737
|
+
const isHours = attrs.currentView === 'hours';
|
|
5532
5738
|
const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
|
|
5533
5739
|
const z = Math.sqrt(x * x + y * y);
|
|
5534
|
-
const inner = isHours && z < (
|
|
5535
|
-
let radius = inner ?
|
|
5536
|
-
if (
|
|
5537
|
-
radius =
|
|
5740
|
+
const inner = isHours && z < (outerRadius + innerRadius) / 2;
|
|
5741
|
+
let radius = inner ? innerRadius : outerRadius;
|
|
5742
|
+
if (attrs.twelveHour) {
|
|
5743
|
+
radius = outerRadius;
|
|
5538
5744
|
}
|
|
5539
5745
|
if (radian < 0) {
|
|
5540
5746
|
radian = Math.PI * 2 + radian;
|
|
5541
5747
|
}
|
|
5542
5748
|
let value = Math.round(radian / unit);
|
|
5543
5749
|
radian = value * unit;
|
|
5544
|
-
if (
|
|
5750
|
+
if (attrs.twelveHour) {
|
|
5545
5751
|
if (isHours) {
|
|
5546
5752
|
if (value === 0)
|
|
5547
5753
|
value = 12;
|
|
@@ -5566,20 +5772,27 @@
|
|
|
5566
5772
|
value = 0;
|
|
5567
5773
|
}
|
|
5568
5774
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5775
|
+
const currentValue = isHours ? attrs.hours : attrs.minutes;
|
|
5776
|
+
if (currentValue !== value) {
|
|
5777
|
+
vibrate(attrs);
|
|
5571
5778
|
}
|
|
5572
|
-
|
|
5573
|
-
if (isHours
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5779
|
+
// Update the value
|
|
5780
|
+
if (isHours) {
|
|
5781
|
+
attrs.onTimeChange(value, attrs.minutes);
|
|
5782
|
+
if (attrs.spanHours) {
|
|
5783
|
+
attrs.spanHours.innerHTML = addLeadingZero(value);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
else {
|
|
5787
|
+
attrs.onTimeChange(attrs.hours, value);
|
|
5788
|
+
if (attrs.spanMinutes) {
|
|
5789
|
+
attrs.spanMinutes.innerHTML = addLeadingZero(value);
|
|
5790
|
+
}
|
|
5578
5791
|
}
|
|
5579
5792
|
// Set clock hand position
|
|
5580
5793
|
if (state.hand && state.bg) {
|
|
5581
|
-
const cx1 = Math.sin(radian) * (radius -
|
|
5582
|
-
const cy1 = -Math.cos(radian) * (radius -
|
|
5794
|
+
const cx1 = Math.sin(radian) * (radius - tickRadius);
|
|
5795
|
+
const cy1 = -Math.cos(radian) * (radius - tickRadius);
|
|
5583
5796
|
const cx2 = Math.sin(radian) * radius;
|
|
5584
5797
|
const cy2 = -Math.cos(radian) * radius;
|
|
5585
5798
|
state.hand.setAttribute('x2', cx1.toString());
|
|
@@ -5588,79 +5801,339 @@
|
|
|
5588
5801
|
state.bg.setAttribute('cy', cy2.toString());
|
|
5589
5802
|
}
|
|
5590
5803
|
};
|
|
5591
|
-
const
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
const
|
|
5595
|
-
const
|
|
5596
|
-
const
|
|
5597
|
-
const
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
bg.setAttribute('class', 'timepicker-canvas-bg');
|
|
5613
|
-
bg.setAttribute('r', tickRadius.toString());
|
|
5614
|
-
g.appendChild(hand);
|
|
5615
|
-
g.appendChild(bg);
|
|
5616
|
-
g.appendChild(bearing);
|
|
5617
|
-
svg.appendChild(g);
|
|
5618
|
-
state.canvas.appendChild(svg);
|
|
5619
|
-
state.hand = hand;
|
|
5620
|
-
state.bg = bg;
|
|
5621
|
-
state.bearing = bearing;
|
|
5622
|
-
state.g = g;
|
|
5623
|
-
};
|
|
5624
|
-
const buildHoursView = () => {
|
|
5625
|
-
if (!state.hoursView)
|
|
5804
|
+
const resetClock = (attrs) => {
|
|
5805
|
+
const view = attrs.currentView;
|
|
5806
|
+
const value = view === 'hours' ? attrs.hours : attrs.minutes;
|
|
5807
|
+
const isHours = view === 'hours';
|
|
5808
|
+
const unit = Math.PI / (isHours ? 6 : 30);
|
|
5809
|
+
const radian = value * unit;
|
|
5810
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5811
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5812
|
+
// In 12-hour mode, always use outer radius
|
|
5813
|
+
// In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
|
|
5814
|
+
let radius = outerRadius;
|
|
5815
|
+
if (!attrs.twelveHour && isHours && value > 0 && value < 13) {
|
|
5816
|
+
radius = innerRadius;
|
|
5817
|
+
}
|
|
5818
|
+
const x = Math.sin(radian) * radius;
|
|
5819
|
+
const y = -Math.cos(radian) * radius;
|
|
5820
|
+
setHand(x, y, attrs);
|
|
5821
|
+
};
|
|
5822
|
+
const handleClockClickStart = (e, attrs) => {
|
|
5823
|
+
e.preventDefault();
|
|
5824
|
+
if (!state.plate)
|
|
5626
5825
|
return;
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5826
|
+
const _dialRadius = attrs.dialRadius || 135;
|
|
5827
|
+
const clockPlateBR = state.plate.getBoundingClientRect();
|
|
5828
|
+
const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
|
|
5829
|
+
state.x0 = offset.x + _dialRadius;
|
|
5830
|
+
state.y0 = offset.y + _dialRadius;
|
|
5831
|
+
state.moved = false;
|
|
5832
|
+
const clickPos = getPos(e);
|
|
5833
|
+
state.dx = clickPos.x - state.x0;
|
|
5834
|
+
state.dy = clickPos.y - state.y0;
|
|
5835
|
+
const startX = clickPos.x;
|
|
5836
|
+
const startY = clickPos.y;
|
|
5837
|
+
setHand(state.dx, state.dy, attrs, attrs.roundBy5);
|
|
5838
|
+
m.redraw();
|
|
5839
|
+
// Add document-level listeners to track dragging
|
|
5840
|
+
const moveHandler = (e) => {
|
|
5841
|
+
e.preventDefault();
|
|
5842
|
+
const clickPos = getPos(e);
|
|
5843
|
+
const x = clickPos.x - state.x0;
|
|
5844
|
+
const y = clickPos.y - state.y0;
|
|
5845
|
+
// Only consider it "moved" if dragged more than 5 pixels
|
|
5846
|
+
const distance = Math.sqrt(Math.pow(clickPos.x - startX, 2) + Math.pow(clickPos.y - startY, 2));
|
|
5847
|
+
if (distance > 5) {
|
|
5848
|
+
state.moved = true;
|
|
5849
|
+
}
|
|
5850
|
+
setHand(x, y, attrs, attrs.roundBy5);
|
|
5851
|
+
};
|
|
5852
|
+
const endHandler = () => {
|
|
5853
|
+
document.removeEventListener('mousemove', moveHandler);
|
|
5854
|
+
document.removeEventListener('touchmove', moveHandler);
|
|
5855
|
+
// After setting hour (either by click or drag), switch to minutes view
|
|
5856
|
+
if (attrs.currentView === 'hours' && attrs.onViewChange) {
|
|
5857
|
+
attrs.onViewChange('minutes');
|
|
5858
|
+
}
|
|
5859
|
+
state.moved = false;
|
|
5860
|
+
m.redraw();
|
|
5861
|
+
};
|
|
5862
|
+
document.addEventListener('mousemove', moveHandler);
|
|
5863
|
+
document.addEventListener('touchmove', moveHandler);
|
|
5864
|
+
document.addEventListener('mouseup', endHandler, { once: true });
|
|
5865
|
+
document.addEventListener('touchend', endHandler, { once: true });
|
|
5866
|
+
};
|
|
5867
|
+
const HourTicks = () => {
|
|
5868
|
+
return {
|
|
5869
|
+
view: ({ attrs }) => {
|
|
5870
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5871
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5872
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5873
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5874
|
+
const ticks = [];
|
|
5875
|
+
if (attrs.twelveHour) {
|
|
5876
|
+
for (let i = 1; i < 13; i++) {
|
|
5877
|
+
const radian = (i / 6) * Math.PI;
|
|
5878
|
+
const radius = outerRadius;
|
|
5879
|
+
const left = dialRadius + Math.sin(radian) * radius - tickRadius;
|
|
5880
|
+
const top = dialRadius - Math.cos(radian) * radius - tickRadius;
|
|
5881
|
+
ticks.push(m('.timepicker-tick', {
|
|
5882
|
+
style: {
|
|
5883
|
+
left: `${left}px`,
|
|
5884
|
+
top: `${top}px`,
|
|
5885
|
+
},
|
|
5886
|
+
}, i === 0 ? '00' : i.toString()));
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
else {
|
|
5890
|
+
for (let i = 0; i < 24; i++) {
|
|
5891
|
+
const radian = (i / 6) * Math.PI;
|
|
5892
|
+
const inner = i > 0 && i < 13;
|
|
5893
|
+
const radius = inner ? innerRadius : outerRadius;
|
|
5894
|
+
const left = dialRadius + Math.sin(radian) * radius - tickRadius;
|
|
5895
|
+
const top = dialRadius - Math.cos(radian) * radius - tickRadius;
|
|
5896
|
+
ticks.push(m('.timepicker-tick', {
|
|
5897
|
+
style: {
|
|
5898
|
+
left: `${left}px`,
|
|
5899
|
+
top: `${top}px`,
|
|
5900
|
+
},
|
|
5901
|
+
}, i === 0 ? '00' : i.toString()));
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return ticks;
|
|
5905
|
+
},
|
|
5906
|
+
};
|
|
5907
|
+
};
|
|
5908
|
+
const MinuteTicks = () => {
|
|
5909
|
+
return {
|
|
5910
|
+
view: ({ attrs }) => {
|
|
5911
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5912
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5913
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5914
|
+
const ticks = [];
|
|
5915
|
+
for (let i = 0; i < 60; i += 5) {
|
|
5916
|
+
const radian = (i / 30) * Math.PI;
|
|
5917
|
+
const left = dialRadius + Math.sin(radian) * outerRadius - tickRadius;
|
|
5918
|
+
const top = dialRadius - Math.cos(radian) * outerRadius - tickRadius;
|
|
5919
|
+
ticks.push(m('.timepicker-tick', {
|
|
5920
|
+
style: {
|
|
5921
|
+
left: `${left}px`,
|
|
5922
|
+
top: `${top}px`,
|
|
5923
|
+
},
|
|
5924
|
+
}, addLeadingZero(i)));
|
|
5925
|
+
}
|
|
5926
|
+
return ticks;
|
|
5927
|
+
},
|
|
5928
|
+
};
|
|
5929
|
+
};
|
|
5930
|
+
return {
|
|
5931
|
+
oncreate: ({ attrs }) => {
|
|
5932
|
+
resetClock(attrs);
|
|
5933
|
+
},
|
|
5934
|
+
view: ({ attrs }) => {
|
|
5935
|
+
// Handle view transitions
|
|
5936
|
+
const isHours = attrs.currentView === 'hours';
|
|
5937
|
+
const dialRadius = attrs.dialRadius || 135;
|
|
5938
|
+
const tickRadius = attrs.tickRadius || 20;
|
|
5939
|
+
const diameter = dialRadius * 2;
|
|
5940
|
+
// Calculate hand and background positions
|
|
5941
|
+
const view = attrs.currentView;
|
|
5942
|
+
const value = view === 'hours' ? attrs.hours : attrs.minutes;
|
|
5943
|
+
const unit = Math.PI / (view === 'hours' ? 6 : 30);
|
|
5944
|
+
const radian = value * unit;
|
|
5945
|
+
const outerRadius = attrs.outerRadius || 105;
|
|
5946
|
+
const innerRadius = attrs.innerRadius || 70;
|
|
5947
|
+
// In 12-hour mode, always use outer radius
|
|
5948
|
+
// In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
|
|
5949
|
+
let radius = outerRadius;
|
|
5950
|
+
if (!attrs.twelveHour && view === 'hours' && value > 0 && value < 13) {
|
|
5951
|
+
radius = innerRadius;
|
|
5952
|
+
}
|
|
5953
|
+
const cx1 = Math.sin(radian) * (radius - tickRadius);
|
|
5954
|
+
const cy1 = -Math.cos(radian) * (radius - tickRadius);
|
|
5955
|
+
const cx2 = Math.sin(radian) * radius;
|
|
5956
|
+
const cy2 = -Math.cos(radian) * radius;
|
|
5957
|
+
return [
|
|
5958
|
+
m('.timepicker-canvas', {
|
|
5959
|
+
oncreate: (vnode) => {
|
|
5960
|
+
state.canvas = vnode.dom;
|
|
5961
|
+
state.plate = vnode.dom.parentElement;
|
|
5962
|
+
},
|
|
5963
|
+
onmousedown: (e) => handleClockClickStart(e, attrs),
|
|
5964
|
+
ontouchstart: (e) => handleClockClickStart(e, attrs),
|
|
5965
|
+
}, [
|
|
5966
|
+
m('svg.timepicker-svg', {
|
|
5967
|
+
width: diameter,
|
|
5968
|
+
height: diameter,
|
|
5969
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
5970
|
+
}, [
|
|
5971
|
+
m('g', {
|
|
5972
|
+
transform: `translate(${dialRadius},${dialRadius})`,
|
|
5973
|
+
oncreate: (vnode) => {
|
|
5974
|
+
state.g = vnode.dom;
|
|
5975
|
+
},
|
|
5976
|
+
}, [
|
|
5977
|
+
m('line', {
|
|
5978
|
+
x1: '0',
|
|
5979
|
+
y1: '0',
|
|
5980
|
+
x2: cx1,
|
|
5981
|
+
y2: cy1,
|
|
5982
|
+
oncreate: (vnode) => {
|
|
5983
|
+
state.hand = vnode.dom;
|
|
5984
|
+
},
|
|
5985
|
+
}),
|
|
5986
|
+
m('circle.timepicker-canvas-bg', {
|
|
5987
|
+
cx: cx2,
|
|
5988
|
+
cy: cy2,
|
|
5989
|
+
r: tickRadius,
|
|
5990
|
+
oncreate: (vnode) => {
|
|
5991
|
+
state.bg = vnode.dom;
|
|
5992
|
+
},
|
|
5993
|
+
}),
|
|
5994
|
+
m('circle.timepicker-canvas-bearing', {
|
|
5995
|
+
cx: '0',
|
|
5996
|
+
cy: '0',
|
|
5997
|
+
r: '4',
|
|
5998
|
+
oncreate: (vnode) => {
|
|
5999
|
+
state.bearing = vnode.dom;
|
|
6000
|
+
},
|
|
6001
|
+
}),
|
|
6002
|
+
]),
|
|
6003
|
+
]),
|
|
6004
|
+
]),
|
|
6005
|
+
m(`.timepicker-dial.timepicker-hours${isHours ? '' : '.timepicker-dial-out'}`, {
|
|
6006
|
+
oncreate: (vnode) => {
|
|
6007
|
+
state.hoursView = vnode.dom;
|
|
6008
|
+
},
|
|
6009
|
+
style: {
|
|
6010
|
+
visibility: isHours ? 'visible' : 'hidden',
|
|
6011
|
+
},
|
|
6012
|
+
}, m(HourTicks, attrs)),
|
|
6013
|
+
m(`.timepicker-dial.timepicker-minutes${!isHours ? '' : '.timepicker-dial-out'}`, {
|
|
6014
|
+
oncreate: (vnode) => {
|
|
6015
|
+
state.minutesView = vnode.dom;
|
|
6016
|
+
},
|
|
6017
|
+
style: {
|
|
6018
|
+
visibility: !isHours ? 'visible' : 'hidden',
|
|
6019
|
+
},
|
|
6020
|
+
}, m(MinuteTicks, attrs)),
|
|
6021
|
+
];
|
|
6022
|
+
},
|
|
6023
|
+
onupdate: ({ attrs }) => {
|
|
6024
|
+
// Update clock hand when time or view changes
|
|
6025
|
+
resetClock(attrs);
|
|
6026
|
+
},
|
|
6027
|
+
};
|
|
6028
|
+
};
|
|
6029
|
+
|
|
6030
|
+
const defaultI18n$3 = {
|
|
6031
|
+
cancel: 'Cancel',
|
|
6032
|
+
clear: 'Clear',
|
|
6033
|
+
done: 'Ok',
|
|
6034
|
+
next: 'Next',
|
|
6035
|
+
};
|
|
6036
|
+
const defaultOptions = {
|
|
6037
|
+
dialRadius: 135,
|
|
6038
|
+
outerRadius: 105,
|
|
6039
|
+
innerRadius: 70,
|
|
6040
|
+
tickRadius: 20,
|
|
6041
|
+
duration: 350,
|
|
6042
|
+
container: null,
|
|
6043
|
+
defaultTime: 'now',
|
|
6044
|
+
fromNow: 0,
|
|
6045
|
+
showClearBtn: false,
|
|
6046
|
+
i18n: defaultI18n$3,
|
|
6047
|
+
autoClose: false,
|
|
6048
|
+
twelveHour: true,
|
|
6049
|
+
vibrate: true,
|
|
6050
|
+
roundBy5: false,
|
|
6051
|
+
displayMode: 'analog',
|
|
6052
|
+
minuteStep: 5,
|
|
6053
|
+
hourStep: 1,
|
|
6054
|
+
minTime: undefined,
|
|
6055
|
+
maxTime: undefined,
|
|
6056
|
+
onOpen: () => { },
|
|
6057
|
+
onOpenStart: () => { },
|
|
6058
|
+
onOpenEnd: () => { },
|
|
6059
|
+
onCloseStart: () => { },
|
|
6060
|
+
onCloseEnd: () => { },
|
|
6061
|
+
onSelect: () => { },
|
|
6062
|
+
};
|
|
6063
|
+
/**
|
|
6064
|
+
* TimePicker component based on original Materialize CSS timepicker
|
|
6065
|
+
*/
|
|
6066
|
+
const TimePicker = () => {
|
|
6067
|
+
let state;
|
|
6068
|
+
let options;
|
|
6069
|
+
// Use shared utilities from time-utils
|
|
6070
|
+
// const addLeadingZero = sharedAddLeadingZero;
|
|
6071
|
+
// const generateHourOptions = sharedGenerateHourOptions;
|
|
6072
|
+
// const generateMinuteOptions = sharedGenerateMinuteOptions;
|
|
6073
|
+
// const scrollToValue = sharedScrollToValue;
|
|
6074
|
+
// const snapToNearestItem = sharedSnapToNearestItem;
|
|
6075
|
+
const updateTimeFromInput = (inputValue) => {
|
|
6076
|
+
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
6077
|
+
let amPmWasProvided = false;
|
|
6078
|
+
if (options.twelveHour && value.length > 1) {
|
|
6079
|
+
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
6080
|
+
state.amOrPm = 'AM';
|
|
6081
|
+
amPmWasProvided = true;
|
|
5637
6082
|
}
|
|
6083
|
+
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
6084
|
+
state.amOrPm = 'PM';
|
|
6085
|
+
amPmWasProvided = true;
|
|
6086
|
+
}
|
|
6087
|
+
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
5638
6088
|
}
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
6089
|
+
if (value[0] === 'now') {
|
|
6090
|
+
const now = new Date(+new Date() + options.fromNow);
|
|
6091
|
+
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
6092
|
+
if (options.twelveHour) {
|
|
6093
|
+
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
6094
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
let hours = +value[0] || 0;
|
|
6098
|
+
let minutes = +value[1] || 0;
|
|
6099
|
+
if (options.twelveHour) {
|
|
6100
|
+
if (!amPmWasProvided) {
|
|
6101
|
+
// No AM/PM was provided, assume this is 24-hour format input - convert it
|
|
6102
|
+
if (hours >= 12) {
|
|
6103
|
+
state.amOrPm = 'PM';
|
|
6104
|
+
if (hours > 12) {
|
|
6105
|
+
hours = hours - 12;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
else {
|
|
6109
|
+
state.amOrPm = 'AM';
|
|
6110
|
+
if (hours === 0) {
|
|
6111
|
+
hours = 12;
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
5650
6114
|
}
|
|
6115
|
+
else {
|
|
6116
|
+
// AM/PM was provided, hours are already in 12-hour format
|
|
6117
|
+
// Just handle midnight/noon edge cases
|
|
6118
|
+
if (hours === 0 && state.amOrPm === 'AM') {
|
|
6119
|
+
hours = 12;
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
state.hours = hours;
|
|
6124
|
+
state.minutes = minutes;
|
|
6125
|
+
if (state.spanHours) {
|
|
6126
|
+
state.spanHours.innerHTML = addLeadingZero(state.hours);
|
|
6127
|
+
}
|
|
6128
|
+
if (state.spanMinutes) {
|
|
6129
|
+
state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
|
|
5651
6130
|
}
|
|
6131
|
+
updateAmPmView();
|
|
5652
6132
|
};
|
|
5653
|
-
const
|
|
5654
|
-
if (
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
const tick = document.createElement('div');
|
|
5658
|
-
tick.className = 'timepicker-tick';
|
|
5659
|
-
const radian = (i / 30) * Math.PI;
|
|
5660
|
-
tick.style.left = options.dialRadius + Math.sin(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
5661
|
-
tick.style.top = options.dialRadius - Math.cos(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
5662
|
-
tick.innerHTML = addLeadingZero(i);
|
|
5663
|
-
state.minutesView.appendChild(tick);
|
|
6133
|
+
const updateAmPmView = () => {
|
|
6134
|
+
if (options.twelveHour && state.amBtn && state.pmBtn) {
|
|
6135
|
+
state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
|
|
6136
|
+
state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
|
|
5664
6137
|
}
|
|
5665
6138
|
};
|
|
5666
6139
|
const handleAmPmClick = (ampm) => {
|
|
@@ -5672,7 +6145,7 @@
|
|
|
5672
6145
|
return;
|
|
5673
6146
|
state.isOpen = true;
|
|
5674
6147
|
updateTimeFromInput(inputValue);
|
|
5675
|
-
|
|
6148
|
+
state.currentView = 'hours';
|
|
5676
6149
|
if (options.onOpen)
|
|
5677
6150
|
options.onOpen();
|
|
5678
6151
|
if (options.onOpenStart)
|
|
@@ -5706,6 +6179,7 @@
|
|
|
5706
6179
|
return {
|
|
5707
6180
|
view: ({ attrs }) => {
|
|
5708
6181
|
const { i18n, showClearBtn } = attrs;
|
|
6182
|
+
const isDigitalMode = options.displayMode === 'digital';
|
|
5709
6183
|
return [
|
|
5710
6184
|
m('.modal-content.timepicker-container', [
|
|
5711
6185
|
m('.timepicker-digital-display', [
|
|
@@ -5713,7 +6187,12 @@
|
|
|
5713
6187
|
m('.timepicker-display-column', [
|
|
5714
6188
|
m('span.timepicker-span-hours', {
|
|
5715
6189
|
class: state.currentView === 'hours' ? 'text-primary' : '',
|
|
5716
|
-
onclick: () =>
|
|
6190
|
+
onclick: () => {
|
|
6191
|
+
if (!isDigitalMode) {
|
|
6192
|
+
state.currentView = 'hours';
|
|
6193
|
+
m.redraw();
|
|
6194
|
+
}
|
|
6195
|
+
},
|
|
5717
6196
|
oncreate: (vnode) => {
|
|
5718
6197
|
state.spanHours = vnode.dom;
|
|
5719
6198
|
},
|
|
@@ -5721,7 +6200,12 @@
|
|
|
5721
6200
|
':',
|
|
5722
6201
|
m('span.timepicker-span-minutes', {
|
|
5723
6202
|
class: state.currentView === 'minutes' ? 'text-primary' : '',
|
|
5724
|
-
onclick: () =>
|
|
6203
|
+
onclick: () => {
|
|
6204
|
+
if (!isDigitalMode) {
|
|
6205
|
+
state.currentView = 'minutes';
|
|
6206
|
+
m.redraw();
|
|
6207
|
+
}
|
|
6208
|
+
},
|
|
5725
6209
|
oncreate: (vnode) => {
|
|
5726
6210
|
state.spanMinutes = vnode.dom;
|
|
5727
6211
|
},
|
|
@@ -5752,66 +6236,108 @@
|
|
|
5752
6236
|
]),
|
|
5753
6237
|
]),
|
|
5754
6238
|
]),
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
state.
|
|
5760
|
-
state.
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
setTimeout(() => resetClock(), 10);
|
|
5775
|
-
},
|
|
5776
|
-
}),
|
|
5777
|
-
m('.timepicker-dial.timepicker-hours', {
|
|
5778
|
-
oncreate: (vnode) => {
|
|
5779
|
-
state.hoursView = vnode.dom;
|
|
5780
|
-
buildHoursView();
|
|
6239
|
+
// Conditional rendering: digital or analog mode
|
|
6240
|
+
isDigitalMode
|
|
6241
|
+
? m('.timepicker-digital-mode', [
|
|
6242
|
+
m(DigitalClock, {
|
|
6243
|
+
hours: state.hours,
|
|
6244
|
+
minutes: state.minutes,
|
|
6245
|
+
amOrPm: state.amOrPm,
|
|
6246
|
+
twelveHour: options.twelveHour,
|
|
6247
|
+
minuteStep: options.minuteStep,
|
|
6248
|
+
hourStep: options.hourStep,
|
|
6249
|
+
minTime: options.minTime,
|
|
6250
|
+
maxTime: options.maxTime,
|
|
6251
|
+
onTimeChange: (hours, minutes, amOrPm) => {
|
|
6252
|
+
state.hours = hours;
|
|
6253
|
+
state.minutes = minutes;
|
|
6254
|
+
state.amOrPm = amOrPm;
|
|
6255
|
+
updateAmPmView();
|
|
6256
|
+
if (options.onSelect)
|
|
6257
|
+
options.onSelect(hours, minutes);
|
|
5781
6258
|
},
|
|
6259
|
+
spanHours: state.spanHours,
|
|
6260
|
+
spanMinutes: state.spanMinutes,
|
|
6261
|
+
spanAmPm: state.spanAmPm,
|
|
5782
6262
|
}),
|
|
5783
|
-
m('.timepicker-
|
|
6263
|
+
m('.timepicker-footer', {
|
|
5784
6264
|
oncreate: (vnode) => {
|
|
5785
|
-
state.
|
|
5786
|
-
buildMinutesView();
|
|
6265
|
+
state.footer = vnode.dom;
|
|
5787
6266
|
},
|
|
5788
|
-
}
|
|
5789
|
-
|
|
5790
|
-
m('.timepicker-footer', {
|
|
5791
|
-
oncreate: (vnode) => {
|
|
5792
|
-
state.footer = vnode.dom;
|
|
5793
|
-
},
|
|
5794
|
-
}, [
|
|
5795
|
-
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5796
|
-
type: 'button',
|
|
5797
|
-
tabindex: options.twelveHour ? '3' : '1',
|
|
5798
|
-
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
5799
|
-
onclick: () => clear(),
|
|
5800
|
-
}, i18n.clear),
|
|
5801
|
-
m('.confirmation-btns', [
|
|
5802
|
-
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6267
|
+
}, [
|
|
6268
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5803
6269
|
type: 'button',
|
|
5804
6270
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
6271
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
6272
|
+
onclick: () => clear(),
|
|
6273
|
+
}, i18n.clear),
|
|
6274
|
+
m('.confirmation-btns', [
|
|
6275
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6276
|
+
type: 'button',
|
|
6277
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6278
|
+
onclick: () => close(),
|
|
6279
|
+
}, i18n.cancel),
|
|
6280
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6281
|
+
type: 'button',
|
|
6282
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6283
|
+
onclick: () => done(),
|
|
6284
|
+
}, i18n.done),
|
|
6285
|
+
]),
|
|
6286
|
+
]),
|
|
6287
|
+
])
|
|
6288
|
+
: m('.timepicker-analog-display', [
|
|
6289
|
+
m('.timepicker-plate', m(AnalogClock, {
|
|
6290
|
+
hours: state.hours,
|
|
6291
|
+
minutes: state.minutes,
|
|
6292
|
+
amOrPm: state.amOrPm,
|
|
6293
|
+
currentView: state.currentView,
|
|
6294
|
+
twelveHour: options.twelveHour,
|
|
6295
|
+
dialRadius: options.dialRadius,
|
|
6296
|
+
outerRadius: options.outerRadius,
|
|
6297
|
+
innerRadius: options.innerRadius,
|
|
6298
|
+
tickRadius: options.tickRadius,
|
|
6299
|
+
roundBy5: options.roundBy5,
|
|
6300
|
+
vibrate: options.vibrate,
|
|
6301
|
+
onTimeChange: (hours, minutes) => {
|
|
6302
|
+
state.hours = hours;
|
|
6303
|
+
state.minutes = minutes;
|
|
6304
|
+
if (options.onSelect)
|
|
6305
|
+
options.onSelect(hours, minutes);
|
|
6306
|
+
},
|
|
6307
|
+
onViewChange: (view) => {
|
|
6308
|
+
state.currentView = view;
|
|
6309
|
+
if (view === 'minutes' && options.autoClose) {
|
|
6310
|
+
setTimeout(() => done(), options.duration / 2);
|
|
6311
|
+
}
|
|
6312
|
+
},
|
|
6313
|
+
spanHours: state.spanHours,
|
|
6314
|
+
spanMinutes: state.spanMinutes,
|
|
6315
|
+
})),
|
|
6316
|
+
m('.timepicker-footer', {
|
|
6317
|
+
oncreate: (vnode) => {
|
|
6318
|
+
state.footer = vnode.dom;
|
|
6319
|
+
},
|
|
6320
|
+
}, [
|
|
6321
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
5808
6322
|
type: 'button',
|
|
5809
6323
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5810
|
-
|
|
5811
|
-
|
|
6324
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
6325
|
+
onclick: () => clear(),
|
|
6326
|
+
}, i18n.clear),
|
|
6327
|
+
m('.confirmation-btns', [
|
|
6328
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6329
|
+
type: 'button',
|
|
6330
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6331
|
+
onclick: () => close(),
|
|
6332
|
+
}, i18n.cancel),
|
|
6333
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
6334
|
+
type: 'button',
|
|
6335
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
6336
|
+
onclick: () => done(),
|
|
6337
|
+
}, i18n.done),
|
|
6338
|
+
]),
|
|
5812
6339
|
]),
|
|
5813
6340
|
]),
|
|
5814
|
-
]),
|
|
5815
6341
|
]),
|
|
5816
6342
|
];
|
|
5817
6343
|
},
|
|
@@ -5824,7 +6350,7 @@
|
|
|
5824
6350
|
m.redraw();
|
|
5825
6351
|
}
|
|
5826
6352
|
};
|
|
5827
|
-
const renderPickerToPortal = (
|
|
6353
|
+
const renderPickerToPortal = () => {
|
|
5828
6354
|
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
5829
6355
|
style: {
|
|
5830
6356
|
position: 'fixed',
|
|
@@ -5885,11 +6411,6 @@
|
|
|
5885
6411
|
minutes: 0,
|
|
5886
6412
|
amOrPm: 'AM',
|
|
5887
6413
|
currentView: 'hours',
|
|
5888
|
-
moved: false,
|
|
5889
|
-
x0: 0,
|
|
5890
|
-
y0: 0,
|
|
5891
|
-
dx: 0,
|
|
5892
|
-
dy: 0,
|
|
5893
6414
|
portalContainerId: `timepicker-portal-${uniqueId()}`,
|
|
5894
6415
|
};
|
|
5895
6416
|
// Handle value after options are set
|
|
@@ -5901,27 +6422,17 @@
|
|
|
5901
6422
|
},
|
|
5902
6423
|
onremove: () => {
|
|
5903
6424
|
// Cleanup
|
|
5904
|
-
if (state.toggleViewTimer) {
|
|
5905
|
-
clearTimeout(state.toggleViewTimer);
|
|
5906
|
-
}
|
|
5907
|
-
if (state.vibrateTimer) {
|
|
5908
|
-
clearTimeout(state.vibrateTimer);
|
|
5909
|
-
}
|
|
5910
|
-
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
5911
|
-
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
5912
|
-
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
5913
|
-
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
5914
6425
|
document.removeEventListener('keydown', handleKeyDown);
|
|
5915
6426
|
// Clean up portal if picker was open
|
|
5916
6427
|
if (state.isOpen) {
|
|
5917
6428
|
clearPortal(state.portalContainerId);
|
|
5918
6429
|
}
|
|
5919
6430
|
},
|
|
5920
|
-
onupdate: (
|
|
5921
|
-
const { useModal = true } =
|
|
6431
|
+
onupdate: ({ attrs }) => {
|
|
6432
|
+
const { useModal = true } = attrs;
|
|
5922
6433
|
// Only render to portal when using modal mode
|
|
5923
6434
|
if (useModal && state.isOpen) {
|
|
5924
|
-
renderPickerToPortal(
|
|
6435
|
+
renderPickerToPortal();
|
|
5925
6436
|
}
|
|
5926
6437
|
else {
|
|
5927
6438
|
clearPortal(state.portalContainerId);
|
|
@@ -6802,46 +7313,50 @@
|
|
|
6802
7313
|
};
|
|
6803
7314
|
|
|
6804
7315
|
// Proper components to avoid anonymous closures
|
|
6805
|
-
const SelectedChip = {
|
|
6806
|
-
|
|
6807
|
-
option
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
e
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
7316
|
+
const SelectedChip = () => {
|
|
7317
|
+
return {
|
|
7318
|
+
view: ({ attrs: { option, onRemove } }) => m('.chip', [
|
|
7319
|
+
option.label || option.id.toString(),
|
|
7320
|
+
m(MaterialIcon, {
|
|
7321
|
+
name: 'close',
|
|
7322
|
+
className: 'close',
|
|
7323
|
+
onclick: (e) => {
|
|
7324
|
+
e.stopPropagation();
|
|
7325
|
+
onRemove(option.id);
|
|
7326
|
+
},
|
|
7327
|
+
}),
|
|
7328
|
+
]),
|
|
7329
|
+
};
|
|
7330
|
+
};
|
|
7331
|
+
const DropdownOption = () => {
|
|
7332
|
+
return {
|
|
7333
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
|
|
7334
|
+
const checkboxId = `search-select-option-${option.id}`;
|
|
7335
|
+
const optionLabel = option.label || option.id.toString();
|
|
7336
|
+
return m('li', {
|
|
7337
|
+
key: option.id,
|
|
7338
|
+
onclick: (e) => {
|
|
7339
|
+
e.preventDefault();
|
|
7340
|
+
e.stopPropagation();
|
|
7341
|
+
onToggle(option);
|
|
7342
|
+
},
|
|
7343
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
7344
|
+
onmouseover: () => {
|
|
7345
|
+
if (!option.disabled) {
|
|
7346
|
+
onMouseOver(index);
|
|
7347
|
+
}
|
|
7348
|
+
},
|
|
7349
|
+
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
7350
|
+
showCheckbox &&
|
|
7351
|
+
m('input', {
|
|
7352
|
+
type: 'checkbox',
|
|
7353
|
+
id: checkboxId,
|
|
7354
|
+
checked: selectedIds.includes(option.id),
|
|
7355
|
+
}),
|
|
7356
|
+
m('span', optionLabel),
|
|
7357
|
+
]));
|
|
7358
|
+
},
|
|
7359
|
+
};
|
|
6845
7360
|
};
|
|
6846
7361
|
/**
|
|
6847
7362
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
@@ -7080,8 +7595,7 @@
|
|
|
7080
7595
|
style: { position: 'absolute', left: '-9999px', opacity: 0 },
|
|
7081
7596
|
}),
|
|
7082
7597
|
// Selected Options (chips)
|
|
7083
|
-
...selectedOptions.map((option) => m(SelectedChip, {
|
|
7084
|
-
// key: option.id,
|
|
7598
|
+
...selectedOptions.map((option) => m(SelectedChip(), {
|
|
7085
7599
|
option,
|
|
7086
7600
|
onRemove: (id) => removeOption(id, attrs),
|
|
7087
7601
|
})),
|
|
@@ -7197,8 +7711,7 @@
|
|
|
7197
7711
|
]
|
|
7198
7712
|
: []),
|
|
7199
7713
|
// List of filtered options
|
|
7200
|
-
...displayedOptions.map((option, index) => m(DropdownOption, {
|
|
7201
|
-
// key: option.id,
|
|
7714
|
+
...displayedOptions.map((option, index) => m(DropdownOption(), {
|
|
7202
7715
|
option,
|
|
7203
7716
|
index,
|
|
7204
7717
|
selectedIds,
|
|
@@ -7215,6 +7728,411 @@
|
|
|
7215
7728
|
};
|
|
7216
7729
|
};
|
|
7217
7730
|
|
|
7731
|
+
const defaultI18n$2 = {
|
|
7732
|
+
cancel: 'Cancel',
|
|
7733
|
+
clear: 'Clear',
|
|
7734
|
+
done: 'Ok',
|
|
7735
|
+
next: 'Next',
|
|
7736
|
+
};
|
|
7737
|
+
/**
|
|
7738
|
+
* TimeRangePicker component for selecting time ranges
|
|
7739
|
+
* Custom implementation with embedded digital clock picker
|
|
7740
|
+
*/
|
|
7741
|
+
const TimeRangePicker = () => {
|
|
7742
|
+
let state;
|
|
7743
|
+
const calculateMinTime = (startTime, twelveHour) => {
|
|
7744
|
+
if (!startTime)
|
|
7745
|
+
return undefined;
|
|
7746
|
+
let hours = startTime.hours;
|
|
7747
|
+
let minutes = startTime.minutes + 1;
|
|
7748
|
+
let amOrPm = startTime.amOrPm;
|
|
7749
|
+
if (minutes >= 60) {
|
|
7750
|
+
minutes = 0;
|
|
7751
|
+
hours++;
|
|
7752
|
+
if (twelveHour) {
|
|
7753
|
+
if (hours > 12) {
|
|
7754
|
+
hours = 1;
|
|
7755
|
+
amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
|
|
7756
|
+
}
|
|
7757
|
+
else if (hours === 12) {
|
|
7758
|
+
amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
else {
|
|
7762
|
+
if (hours >= 24)
|
|
7763
|
+
hours = 0;
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
return formatTime({ hours, minutes, amOrPm }, twelveHour);
|
|
7767
|
+
};
|
|
7768
|
+
const handleNextOrDone = (validateRange, twelveHour, onchange) => {
|
|
7769
|
+
if (state.currentSelection === 'start') {
|
|
7770
|
+
// Move to end time selection
|
|
7771
|
+
state.currentSelection = 'end';
|
|
7772
|
+
state.currentView = 'hours'; // Reset to hours view for end time
|
|
7773
|
+
// If validation is enabled and end time is before or equal to start time, reset end time
|
|
7774
|
+
if (validateRange) {
|
|
7775
|
+
const startMins = timeToMinutes(state.tempStartTime, twelveHour);
|
|
7776
|
+
const endMins = timeToMinutes(state.tempEndTime, twelveHour);
|
|
7777
|
+
if (endMins <= startMins) {
|
|
7778
|
+
// Reset end time to start time
|
|
7779
|
+
state.tempEndTime = Object.assign({}, state.tempStartTime);
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
else {
|
|
7784
|
+
// Finalize selection
|
|
7785
|
+
state.startTime = Object.assign({}, state.tempStartTime);
|
|
7786
|
+
state.endTime = Object.assign({}, state.tempEndTime);
|
|
7787
|
+
state.isPickerOpen = false;
|
|
7788
|
+
state.currentSelection = 'start';
|
|
7789
|
+
// Call onchange callback
|
|
7790
|
+
if (onchange && state.startTime && state.endTime) {
|
|
7791
|
+
const startTimeStr = formatTime(state.startTime, twelveHour);
|
|
7792
|
+
const endTimeStr = formatTime(state.endTime, twelveHour);
|
|
7793
|
+
onchange(startTimeStr, endTimeStr);
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
};
|
|
7797
|
+
const TimeRangePickerModal = () => {
|
|
7798
|
+
return {
|
|
7799
|
+
view: ({ attrs }) => {
|
|
7800
|
+
const { i18n, showClearBtn, twelveHour, minuteStep, hourStep, minTime, maxTime, validateRange, displayMode = 'digital', dialRadius = 135, outerRadius = 105, innerRadius = 70, tickRadius = 20, roundBy5 = false, vibrate = true, } = attrs;
|
|
7801
|
+
const isAnalogMode = displayMode === 'analog';
|
|
7802
|
+
// Calculate effective minTime for end time selection
|
|
7803
|
+
const effectiveMinTime = state.currentSelection === 'end' && validateRange
|
|
7804
|
+
? calculateMinTime(state.tempStartTime, twelveHour)
|
|
7805
|
+
: minTime;
|
|
7806
|
+
return m('.modal-content.timepicker-container', [
|
|
7807
|
+
// Vertical time range display on the left
|
|
7808
|
+
m('.timerange-display-vertical', [
|
|
7809
|
+
m('.timerange-time-section', { class: state.currentSelection === 'start' ? 'active' : '' }, [
|
|
7810
|
+
m('.timerange-label', 'Start'),
|
|
7811
|
+
m('.timerange-time', [
|
|
7812
|
+
m('span.timerange-hours', {
|
|
7813
|
+
oncreate: (vnode) => {
|
|
7814
|
+
state.spanStartHours = vnode.dom;
|
|
7815
|
+
state.spanStartHours.innerHTML = addLeadingZero(state.tempStartTime.hours);
|
|
7816
|
+
},
|
|
7817
|
+
}, addLeadingZero(state.tempStartTime.hours)),
|
|
7818
|
+
':',
|
|
7819
|
+
m('span.timerange-minutes', {
|
|
7820
|
+
oncreate: (vnode) => {
|
|
7821
|
+
state.spanStartMinutes = vnode.dom;
|
|
7822
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(state.tempStartTime.minutes);
|
|
7823
|
+
},
|
|
7824
|
+
}, addLeadingZero(state.tempStartTime.minutes)),
|
|
7825
|
+
twelveHour &&
|
|
7826
|
+
m('span.timerange-ampm', {
|
|
7827
|
+
oncreate: (vnode) => {
|
|
7828
|
+
state.spanStartAmPm = vnode.dom;
|
|
7829
|
+
state.spanStartAmPm.innerHTML = ` ${state.tempStartTime.amOrPm}`;
|
|
7830
|
+
},
|
|
7831
|
+
}, ` ${state.tempStartTime.amOrPm}`),
|
|
7832
|
+
]),
|
|
7833
|
+
]),
|
|
7834
|
+
m('.timerange-time-section', { class: state.currentSelection === 'end' ? 'active' : '' }, [
|
|
7835
|
+
m('.timerange-label', 'End'),
|
|
7836
|
+
m('.timerange-time', [
|
|
7837
|
+
m('span.timerange-hours', {
|
|
7838
|
+
oncreate: (vnode) => {
|
|
7839
|
+
state.spanEndHours = vnode.dom;
|
|
7840
|
+
state.spanEndHours.innerHTML = addLeadingZero(state.tempEndTime.hours);
|
|
7841
|
+
},
|
|
7842
|
+
}, addLeadingZero(state.tempEndTime.hours)),
|
|
7843
|
+
':',
|
|
7844
|
+
m('span.timerange-minutes', {
|
|
7845
|
+
oncreate: (vnode) => {
|
|
7846
|
+
state.spanEndMinutes = vnode.dom;
|
|
7847
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(state.tempEndTime.minutes);
|
|
7848
|
+
},
|
|
7849
|
+
}, addLeadingZero(state.tempEndTime.minutes)),
|
|
7850
|
+
twelveHour &&
|
|
7851
|
+
m('span.timerange-ampm', {
|
|
7852
|
+
oncreate: (vnode) => {
|
|
7853
|
+
state.spanEndAmPm = vnode.dom;
|
|
7854
|
+
state.spanEndAmPm.innerHTML = ` ${state.tempEndTime.amOrPm}`;
|
|
7855
|
+
},
|
|
7856
|
+
}, ` ${state.tempEndTime.amOrPm}`),
|
|
7857
|
+
]),
|
|
7858
|
+
]),
|
|
7859
|
+
]),
|
|
7860
|
+
// Clock picker (analog or digital mode)
|
|
7861
|
+
isAnalogMode
|
|
7862
|
+
? m('.timepicker-analog-display', [
|
|
7863
|
+
m('.timepicker-plate', m(AnalogClock, {
|
|
7864
|
+
key: state.currentSelection, // Force component recreation when switching between start/end
|
|
7865
|
+
hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
|
|
7866
|
+
minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
|
|
7867
|
+
amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
|
|
7868
|
+
currentView: state.currentView,
|
|
7869
|
+
twelveHour,
|
|
7870
|
+
dialRadius,
|
|
7871
|
+
outerRadius,
|
|
7872
|
+
innerRadius,
|
|
7873
|
+
tickRadius,
|
|
7874
|
+
roundBy5,
|
|
7875
|
+
vibrate,
|
|
7876
|
+
onTimeChange: (hours, minutes) => {
|
|
7877
|
+
if (state.currentSelection === 'start') {
|
|
7878
|
+
state.tempStartTime = Object.assign(Object.assign({}, state.tempStartTime), { hours, minutes });
|
|
7879
|
+
if (state.spanStartHours)
|
|
7880
|
+
state.spanStartHours.innerHTML = addLeadingZero(hours);
|
|
7881
|
+
if (state.spanStartMinutes)
|
|
7882
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
|
|
7883
|
+
}
|
|
7884
|
+
else {
|
|
7885
|
+
state.tempEndTime = Object.assign(Object.assign({}, state.tempEndTime), { hours, minutes });
|
|
7886
|
+
if (state.spanEndHours)
|
|
7887
|
+
state.spanEndHours.innerHTML = addLeadingZero(hours);
|
|
7888
|
+
if (state.spanEndMinutes)
|
|
7889
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
|
|
7890
|
+
}
|
|
7891
|
+
},
|
|
7892
|
+
onViewChange: (view) => {
|
|
7893
|
+
state.currentView = view;
|
|
7894
|
+
},
|
|
7895
|
+
spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
|
|
7896
|
+
spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
|
|
7897
|
+
})),
|
|
7898
|
+
// Footer (inside analog display)
|
|
7899
|
+
m('.timepicker-footer', [
|
|
7900
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
7901
|
+
type: 'button',
|
|
7902
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
7903
|
+
onclick: () => {
|
|
7904
|
+
state.isPickerOpen = false;
|
|
7905
|
+
},
|
|
7906
|
+
}, i18n.clear),
|
|
7907
|
+
m('.confirmation-btns', [
|
|
7908
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7909
|
+
type: 'button',
|
|
7910
|
+
onclick: () => {
|
|
7911
|
+
state.isPickerOpen = false;
|
|
7912
|
+
state.currentSelection = 'start';
|
|
7913
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
7914
|
+
},
|
|
7915
|
+
}, i18n.cancel),
|
|
7916
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7917
|
+
type: 'button',
|
|
7918
|
+
onclick: () => {
|
|
7919
|
+
handleNextOrDone(validateRange, twelveHour, attrs.onchange);
|
|
7920
|
+
},
|
|
7921
|
+
}, state.currentSelection === 'start' ? i18n.next : i18n.done),
|
|
7922
|
+
]),
|
|
7923
|
+
]),
|
|
7924
|
+
])
|
|
7925
|
+
: m('.timepicker-digital-mode', [
|
|
7926
|
+
m(DigitalClock, {
|
|
7927
|
+
key: state.currentSelection, // Force component recreation when switching between start/end
|
|
7928
|
+
hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
|
|
7929
|
+
minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
|
|
7930
|
+
amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
|
|
7931
|
+
twelveHour,
|
|
7932
|
+
minuteStep,
|
|
7933
|
+
hourStep,
|
|
7934
|
+
minTime: effectiveMinTime,
|
|
7935
|
+
maxTime,
|
|
7936
|
+
onTimeChange: (hours, minutes, amOrPm) => {
|
|
7937
|
+
if (state.currentSelection === 'start') {
|
|
7938
|
+
state.tempStartTime = { hours, minutes, amOrPm };
|
|
7939
|
+
if (state.spanStartHours)
|
|
7940
|
+
state.spanStartHours.innerHTML = addLeadingZero(hours);
|
|
7941
|
+
if (state.spanStartMinutes)
|
|
7942
|
+
state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
|
|
7943
|
+
if (state.spanStartAmPm)
|
|
7944
|
+
state.spanStartAmPm.innerHTML = ` ${amOrPm}`;
|
|
7945
|
+
}
|
|
7946
|
+
else {
|
|
7947
|
+
state.tempEndTime = { hours, minutes, amOrPm };
|
|
7948
|
+
if (state.spanEndHours)
|
|
7949
|
+
state.spanEndHours.innerHTML = addLeadingZero(hours);
|
|
7950
|
+
if (state.spanEndMinutes)
|
|
7951
|
+
state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
|
|
7952
|
+
if (state.spanEndAmPm)
|
|
7953
|
+
state.spanEndAmPm.innerHTML = ` ${amOrPm}`;
|
|
7954
|
+
}
|
|
7955
|
+
},
|
|
7956
|
+
spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
|
|
7957
|
+
spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
|
|
7958
|
+
spanAmPm: state.currentSelection === 'start' ? state.spanStartAmPm : state.spanEndAmPm,
|
|
7959
|
+
}),
|
|
7960
|
+
// Footer (inside digital mode)
|
|
7961
|
+
m('.timepicker-footer', { key: 'timepicker-footer' }, [
|
|
7962
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
7963
|
+
type: 'button',
|
|
7964
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
7965
|
+
onclick: () => {
|
|
7966
|
+
state.isPickerOpen = false;
|
|
7967
|
+
},
|
|
7968
|
+
}, i18n.clear),
|
|
7969
|
+
m('.confirmation-btns', [
|
|
7970
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7971
|
+
type: 'button',
|
|
7972
|
+
onclick: () => {
|
|
7973
|
+
state.isPickerOpen = false;
|
|
7974
|
+
state.currentSelection = 'start';
|
|
7975
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
7976
|
+
},
|
|
7977
|
+
}, i18n.cancel),
|
|
7978
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
7979
|
+
type: 'button',
|
|
7980
|
+
onclick: () => {
|
|
7981
|
+
handleNextOrDone(validateRange, twelveHour, attrs.onchange);
|
|
7982
|
+
},
|
|
7983
|
+
}, state.currentSelection === 'start' ? i18n.next : i18n.done),
|
|
7984
|
+
]),
|
|
7985
|
+
]),
|
|
7986
|
+
]),
|
|
7987
|
+
]);
|
|
7988
|
+
},
|
|
7989
|
+
};
|
|
7990
|
+
};
|
|
7991
|
+
const handleKeyDown = (e) => {
|
|
7992
|
+
if (e.key === 'Escape' && state.isPickerOpen) {
|
|
7993
|
+
state.isPickerOpen = false;
|
|
7994
|
+
clearPortal(state.portalContainerId);
|
|
7995
|
+
}
|
|
7996
|
+
};
|
|
7997
|
+
const renderPickerToPortal = (attrs) => {
|
|
7998
|
+
const mergedI18n = Object.assign(Object.assign({}, defaultI18n$2), attrs.i18n);
|
|
7999
|
+
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
8000
|
+
style: {
|
|
8001
|
+
position: 'fixed',
|
|
8002
|
+
top: '0',
|
|
8003
|
+
left: '0',
|
|
8004
|
+
width: '100%',
|
|
8005
|
+
height: '100%',
|
|
8006
|
+
pointerEvents: 'auto',
|
|
8007
|
+
display: 'flex',
|
|
8008
|
+
alignItems: 'center',
|
|
8009
|
+
justifyContent: 'center',
|
|
8010
|
+
},
|
|
8011
|
+
}, [
|
|
8012
|
+
// Modal overlay
|
|
8013
|
+
m('.modal-overlay', {
|
|
8014
|
+
style: {
|
|
8015
|
+
position: 'absolute',
|
|
8016
|
+
top: '0',
|
|
8017
|
+
left: '0',
|
|
8018
|
+
width: '100%',
|
|
8019
|
+
height: '100%',
|
|
8020
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
8021
|
+
zIndex: '1002',
|
|
8022
|
+
},
|
|
8023
|
+
onclick: () => {
|
|
8024
|
+
state.isPickerOpen = false;
|
|
8025
|
+
state.currentSelection = 'start';
|
|
8026
|
+
state.currentView = 'hours'; // Reset to hours view
|
|
8027
|
+
},
|
|
8028
|
+
}),
|
|
8029
|
+
// Modal content
|
|
8030
|
+
m('.modal.timepicker-modal.open.timerange-modal', {
|
|
8031
|
+
style: {
|
|
8032
|
+
position: 'relative',
|
|
8033
|
+
zIndex: '1003',
|
|
8034
|
+
display: 'block',
|
|
8035
|
+
opacity: 1,
|
|
8036
|
+
top: 'auto',
|
|
8037
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
8038
|
+
margin: '0 auto',
|
|
8039
|
+
},
|
|
8040
|
+
}, [
|
|
8041
|
+
m(TimeRangePickerModal, {
|
|
8042
|
+
i18n: mergedI18n,
|
|
8043
|
+
showClearBtn: attrs.showClearBtn || false,
|
|
8044
|
+
twelveHour: attrs.twelveHour !== undefined ? attrs.twelveHour : true,
|
|
8045
|
+
minuteStep: attrs.minuteStep || 5,
|
|
8046
|
+
hourStep: attrs.hourStep || 1,
|
|
8047
|
+
minTime: attrs.minTime,
|
|
8048
|
+
maxTime: attrs.maxTime,
|
|
8049
|
+
validateRange: attrs.validateRange || false,
|
|
8050
|
+
onchange: attrs.onchange,
|
|
8051
|
+
displayMode: attrs.displayMode,
|
|
8052
|
+
dialRadius: attrs.dialRadius,
|
|
8053
|
+
outerRadius: attrs.outerRadius,
|
|
8054
|
+
innerRadius: attrs.innerRadius,
|
|
8055
|
+
tickRadius: attrs.tickRadius,
|
|
8056
|
+
roundBy5: attrs.roundBy5,
|
|
8057
|
+
vibrate: attrs.vibrate,
|
|
8058
|
+
}),
|
|
8059
|
+
]),
|
|
8060
|
+
]);
|
|
8061
|
+
renderToPortal(state.portalContainerId, pickerModal, 1004);
|
|
8062
|
+
};
|
|
8063
|
+
return {
|
|
8064
|
+
oninit: (vnode) => {
|
|
8065
|
+
const attrs = vnode.attrs;
|
|
8066
|
+
const twelveHour = attrs.twelveHour !== undefined ? attrs.twelveHour : true;
|
|
8067
|
+
const startTime = parseTime(attrs.startValue || '', twelveHour);
|
|
8068
|
+
const endTime = parseTime(attrs.endValue || '', twelveHour);
|
|
8069
|
+
state = {
|
|
8070
|
+
id: uniqueId(),
|
|
8071
|
+
currentSelection: 'start',
|
|
8072
|
+
startTime,
|
|
8073
|
+
endTime,
|
|
8074
|
+
tempStartTime: Object.assign({}, startTime),
|
|
8075
|
+
tempEndTime: Object.assign({}, endTime),
|
|
8076
|
+
isPickerOpen: false,
|
|
8077
|
+
portalContainerId: `timerange-portal-${uniqueId()}`,
|
|
8078
|
+
currentView: 'hours',
|
|
8079
|
+
};
|
|
8080
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
8081
|
+
},
|
|
8082
|
+
onremove: () => {
|
|
8083
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
8084
|
+
if (state.isPickerOpen) {
|
|
8085
|
+
clearPortal(state.portalContainerId);
|
|
8086
|
+
}
|
|
8087
|
+
},
|
|
8088
|
+
onupdate: ({ attrs }) => {
|
|
8089
|
+
if (state.isPickerOpen) {
|
|
8090
|
+
renderPickerToPortal(attrs);
|
|
8091
|
+
}
|
|
8092
|
+
else {
|
|
8093
|
+
clearPortal(state.portalContainerId);
|
|
8094
|
+
}
|
|
8095
|
+
},
|
|
8096
|
+
view: ({ attrs }) => {
|
|
8097
|
+
const { id = state.id, label, placeholder = 'Select time range', disabled, readonly, required, iconName, helperText, className: cn1, class: cn2, twelveHour = true, } = attrs;
|
|
8098
|
+
const className = cn1 || cn2 || 'col s12';
|
|
8099
|
+
const displayValue = state.startTime && state.endTime
|
|
8100
|
+
? `${formatTime(state.startTime, twelveHour)} - ${formatTime(state.endTime, twelveHour)}`
|
|
8101
|
+
: state.startTime
|
|
8102
|
+
? `${formatTime(state.startTime, twelveHour)} - ...`
|
|
8103
|
+
: '';
|
|
8104
|
+
return m('.input-field', { className }, [
|
|
8105
|
+
iconName && m('i.material-icons.prefix', iconName),
|
|
8106
|
+
// Display input
|
|
8107
|
+
m('input.timerangepicker', {
|
|
8108
|
+
id,
|
|
8109
|
+
type: 'text',
|
|
8110
|
+
value: displayValue,
|
|
8111
|
+
placeholder,
|
|
8112
|
+
readonly: true,
|
|
8113
|
+
disabled,
|
|
8114
|
+
required,
|
|
8115
|
+
onclick: () => {
|
|
8116
|
+
if (!disabled && !readonly) {
|
|
8117
|
+
state.isPickerOpen = true;
|
|
8118
|
+
state.currentSelection = 'start';
|
|
8119
|
+
state.currentView = 'hours'; // Reset to hours view when opening
|
|
8120
|
+
state.tempStartTime = Object.assign({}, state.startTime);
|
|
8121
|
+
state.tempEndTime = Object.assign({}, state.endTime);
|
|
8122
|
+
}
|
|
8123
|
+
},
|
|
8124
|
+
}),
|
|
8125
|
+
label &&
|
|
8126
|
+
m('label', {
|
|
8127
|
+
for: id,
|
|
8128
|
+
class: displayValue || placeholder ? 'active' : '',
|
|
8129
|
+
}, label),
|
|
8130
|
+
helperText && m('span.helper-text', helperText),
|
|
8131
|
+
]);
|
|
8132
|
+
},
|
|
8133
|
+
};
|
|
8134
|
+
};
|
|
8135
|
+
|
|
7218
8136
|
class Toast {
|
|
7219
8137
|
constructor(options = {}) {
|
|
7220
8138
|
this.options = Object.assign(Object.assign({}, Toast.defaults), options);
|
|
@@ -9797,6 +10715,126 @@
|
|
|
9797
10715
|
};
|
|
9798
10716
|
};
|
|
9799
10717
|
|
|
10718
|
+
/**
|
|
10719
|
+
* ToggleButton component.
|
|
10720
|
+
*
|
|
10721
|
+
* A button that can be toggled on/off. Typically used within a ToggleGroup
|
|
10722
|
+
* to create grouped button controls where one or more buttons can be selected.
|
|
10723
|
+
*
|
|
10724
|
+
* @example
|
|
10725
|
+
* ```typescript
|
|
10726
|
+
* m(ToggleButton, {
|
|
10727
|
+
* value: 'bold',
|
|
10728
|
+
* iconName: 'format_bold',
|
|
10729
|
+
* checked: true,
|
|
10730
|
+
* tooltip: 'Bold',
|
|
10731
|
+
* onchange: () => console.log('Toggled')
|
|
10732
|
+
* })
|
|
10733
|
+
* ```
|
|
10734
|
+
*/
|
|
10735
|
+
const ToggleButton = () => {
|
|
10736
|
+
return {
|
|
10737
|
+
view: ({ attrs }) => {
|
|
10738
|
+
const { checked, iconName, icon, label, onchange, className, tooltip } = attrs, rest = __rest(attrs, ["checked", "iconName", "icon", "label", "onchange", "className", "tooltip"]);
|
|
10739
|
+
const classes = [className, checked ? 'checked' : ''].filter(Boolean).join(' ');
|
|
10740
|
+
return m('button.btn-flat.waves-effect.toggle-button', Object.assign(Object.assign({}, rest), { className: classes, title: tooltip, onclick: () => {
|
|
10741
|
+
if (onchange) {
|
|
10742
|
+
onchange();
|
|
10743
|
+
}
|
|
10744
|
+
}, onmousedown: WavesEffect.onMouseDown, onmouseup: WavesEffect.onMouseUp, onmouseleave: WavesEffect.onMouseLeave, ontouchstart: WavesEffect.onTouchStart, ontouchend: WavesEffect.onTouchEnd }), [icon, iconName && m(Icon, { iconName, className: attrs.iconClass }), label]);
|
|
10745
|
+
},
|
|
10746
|
+
};
|
|
10747
|
+
};
|
|
10748
|
+
|
|
10749
|
+
/**
|
|
10750
|
+
* ToggleGroup component.
|
|
10751
|
+
*
|
|
10752
|
+
* A group of toggle buttons that can operate in single-select or multi-select mode.
|
|
10753
|
+
* The component supports both controlled and uncontrolled modes.
|
|
10754
|
+
*
|
|
10755
|
+
* **Controlled mode**: Manage the state externally using the `value` prop.
|
|
10756
|
+
* **Uncontrolled mode**: Let the component manage its own state using `defaultValue`.
|
|
10757
|
+
*
|
|
10758
|
+
* @example
|
|
10759
|
+
* ```typescript
|
|
10760
|
+
* // Single-select, controlled mode
|
|
10761
|
+
* let selected = 'left';
|
|
10762
|
+
* m(ToggleGroup, {
|
|
10763
|
+
* value: selected,
|
|
10764
|
+
* onchange: (v) => selected = v,
|
|
10765
|
+
* items: [
|
|
10766
|
+
* { value: 'left', iconName: 'format_align_left', tooltip: 'Align Left' },
|
|
10767
|
+
* { value: 'center', iconName: 'format_align_center', tooltip: 'Align Center' },
|
|
10768
|
+
* { value: 'right', iconName: 'format_align_right', tooltip: 'Align Right' }
|
|
10769
|
+
* ]
|
|
10770
|
+
* });
|
|
10771
|
+
*
|
|
10772
|
+
* // Multi-select, controlled mode
|
|
10773
|
+
* let selected = ['bold', 'italic'];
|
|
10774
|
+
* m(ToggleGroup, {
|
|
10775
|
+
* multiple: true,
|
|
10776
|
+
* value: selected,
|
|
10777
|
+
* onchange: (v) => selected = v,
|
|
10778
|
+
* items: [
|
|
10779
|
+
* { value: 'bold', iconName: 'format_bold', tooltip: 'Bold' },
|
|
10780
|
+
* { value: 'italic', iconName: 'format_italic', tooltip: 'Italic' },
|
|
10781
|
+
* { value: 'underline', iconName: 'format_underlined', tooltip: 'Underline' }
|
|
10782
|
+
* ]
|
|
10783
|
+
* });
|
|
10784
|
+
*
|
|
10785
|
+
* // Uncontrolled mode
|
|
10786
|
+
* m(ToggleGroup, {
|
|
10787
|
+
* defaultValue: 'left',
|
|
10788
|
+
* onchange: (v) => console.log('Changed to:', v),
|
|
10789
|
+
* items: [...]
|
|
10790
|
+
* });
|
|
10791
|
+
* ```
|
|
10792
|
+
*/
|
|
10793
|
+
const ToggleGroup = () => {
|
|
10794
|
+
let internalValue;
|
|
10795
|
+
const handleSelect = (attrs, item, currentValue) => {
|
|
10796
|
+
if (attrs.disabled || item.disabled) {
|
|
10797
|
+
return;
|
|
10798
|
+
}
|
|
10799
|
+
const { value, multiple, onchange } = attrs;
|
|
10800
|
+
const isControlled = value !== undefined;
|
|
10801
|
+
if (multiple) {
|
|
10802
|
+
const currentValues = (Array.isArray(currentValue) ? currentValue : currentValue !== undefined ? [currentValue] : []);
|
|
10803
|
+
const newValues = currentValues.includes(item.value)
|
|
10804
|
+
? currentValues.filter((v) => v !== item.value)
|
|
10805
|
+
: [...currentValues, item.value];
|
|
10806
|
+
if (!isControlled) {
|
|
10807
|
+
internalValue = newValues;
|
|
10808
|
+
}
|
|
10809
|
+
if (onchange) {
|
|
10810
|
+
onchange(newValues);
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
else {
|
|
10814
|
+
const newValue = item.value;
|
|
10815
|
+
if (!isControlled) {
|
|
10816
|
+
internalValue = newValue;
|
|
10817
|
+
}
|
|
10818
|
+
if (onchange) {
|
|
10819
|
+
onchange(newValue);
|
|
10820
|
+
}
|
|
10821
|
+
}
|
|
10822
|
+
};
|
|
10823
|
+
return {
|
|
10824
|
+
oninit: ({ attrs }) => {
|
|
10825
|
+
internalValue = attrs.defaultValue;
|
|
10826
|
+
},
|
|
10827
|
+
view: ({ attrs }) => {
|
|
10828
|
+
const { value, items, multiple } = attrs;
|
|
10829
|
+
const isControlled = value !== undefined;
|
|
10830
|
+
const currentValue = isControlled ? value : internalValue;
|
|
10831
|
+
return m('.toggle-group', items.map((item) => m(ToggleButton, Object.assign(Object.assign({}, item), { checked: multiple && Array.isArray(currentValue)
|
|
10832
|
+
? currentValue.includes(item.value)
|
|
10833
|
+
: currentValue === item.value, onchange: () => handleSelect(attrs, item, currentValue) }))));
|
|
10834
|
+
},
|
|
10835
|
+
};
|
|
10836
|
+
};
|
|
10837
|
+
|
|
9800
10838
|
/**
|
|
9801
10839
|
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
9802
10840
|
* These types improve type safety and developer experience across all components
|
|
@@ -9818,6 +10856,7 @@
|
|
|
9818
10856
|
// ============================================================================
|
|
9819
10857
|
// All types are already exported via individual export declarations above
|
|
9820
10858
|
|
|
10859
|
+
exports.AnalogClock = AnalogClock;
|
|
9821
10860
|
exports.AnchorItem = AnchorItem;
|
|
9822
10861
|
exports.Autocomplete = Autocomplete;
|
|
9823
10862
|
exports.Breadcrumb = Breadcrumb;
|
|
@@ -9834,6 +10873,7 @@
|
|
|
9834
10873
|
exports.ColorInput = ColorInput;
|
|
9835
10874
|
exports.DataTable = DataTable;
|
|
9836
10875
|
exports.DatePicker = DatePicker;
|
|
10876
|
+
exports.DigitalClock = DigitalClock;
|
|
9837
10877
|
exports.DoubleRangeSlider = DoubleRangeSlider;
|
|
9838
10878
|
exports.Dropdown = Dropdown;
|
|
9839
10879
|
exports.EmailInput = EmailInput;
|
|
@@ -9886,28 +10926,39 @@
|
|
|
9886
10926
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
9887
10927
|
exports.ThemeToggle = ThemeToggle;
|
|
9888
10928
|
exports.TimePicker = TimePicker;
|
|
10929
|
+
exports.TimeRangePicker = TimeRangePicker;
|
|
9889
10930
|
exports.Timeline = Timeline;
|
|
9890
10931
|
exports.Toast = Toast;
|
|
9891
10932
|
exports.ToastComponent = ToastComponent;
|
|
10933
|
+
exports.ToggleGroup = ToggleGroup;
|
|
9892
10934
|
exports.Tooltip = Tooltip;
|
|
9893
10935
|
exports.TooltipComponent = TooltipComponent;
|
|
9894
10936
|
exports.TreeView = TreeView;
|
|
9895
10937
|
exports.UrlInput = UrlInput;
|
|
9896
10938
|
exports.Wizard = Wizard;
|
|
10939
|
+
exports.addLeadingZero = addLeadingZero;
|
|
9897
10940
|
exports.clearPortal = clearPortal;
|
|
9898
10941
|
exports.createBreadcrumb = createBreadcrumb;
|
|
10942
|
+
exports.formatTime = formatTime;
|
|
10943
|
+
exports.generateHourOptions = generateHourOptions;
|
|
10944
|
+
exports.generateMinuteOptions = generateMinuteOptions;
|
|
9899
10945
|
exports.getDropdownStyles = getDropdownStyles;
|
|
9900
10946
|
exports.getPortalContainer = getPortalContainer;
|
|
9901
10947
|
exports.initPushpins = initPushpins;
|
|
9902
10948
|
exports.initTooltips = initTooltips;
|
|
9903
10949
|
exports.isNumeric = isNumeric;
|
|
10950
|
+
exports.isTimeDisabled = isTimeDisabled;
|
|
9904
10951
|
exports.isValidationError = isValidationError;
|
|
9905
10952
|
exports.isValidationSuccess = isValidationSuccess;
|
|
9906
10953
|
exports.padLeft = padLeft;
|
|
10954
|
+
exports.parseTime = parseTime;
|
|
9907
10955
|
exports.range = range;
|
|
9908
10956
|
exports.releasePortalContainer = releasePortalContainer;
|
|
9909
10957
|
exports.renderToPortal = renderToPortal;
|
|
10958
|
+
exports.scrollToValue = scrollToValue;
|
|
10959
|
+
exports.snapToNearestItem = snapToNearestItem;
|
|
9910
10960
|
exports.sortOptions = sortOptions;
|
|
10961
|
+
exports.timeToMinutes = timeToMinutes;
|
|
9911
10962
|
exports.toast = toast;
|
|
9912
10963
|
exports.uniqueId = uniqueId;
|
|
9913
10964
|
exports.uuid4 = uuid4;
|