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