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