mithril-materialized 2.0.0-beta.3 → 2.0.0-beta.5
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/autocomplete.d.ts +3 -3
- package/dist/button.d.ts +10 -10
- package/dist/carousel.d.ts +2 -2
- package/dist/chip.d.ts +2 -2
- package/dist/code-block.d.ts +2 -2
- package/dist/collapsible.d.ts +2 -2
- package/dist/collection.d.ts +2 -2
- package/dist/datepicker.d.ts +66 -0
- package/dist/dropdown.d.ts +2 -2
- package/dist/floating-action-button.d.ts +2 -2
- package/dist/index.css +190 -62
- package/dist/index.d.ts +7 -1
- package/dist/index.esm.js +1900 -769
- package/dist/index.js +1908 -768
- package/dist/index.min.css +8 -0
- package/dist/index.umd.js +1908 -768
- package/dist/input-options.d.ts +1 -1
- package/dist/input.d.ts +9 -9
- package/dist/label.d.ts +2 -2
- package/dist/material-box.d.ts +2 -2
- package/dist/modal.d.ts +2 -2
- package/dist/option.d.ts +4 -4
- package/dist/pagination.d.ts +2 -2
- package/dist/parallax.d.ts +2 -2
- package/dist/pushpin.d.ts +32 -0
- package/dist/radio.d.ts +4 -4
- package/dist/search-select.d.ts +2 -2
- package/dist/select.d.ts +2 -2
- package/dist/switch.d.ts +2 -2
- package/dist/tabs.d.ts +2 -2
- package/dist/timepicker.d.ts +42 -0
- package/dist/toast.d.ts +45 -0
- package/dist/tooltip.d.ts +59 -0
- package/package.json +4 -2
- package/sass/components/_datepicker.scss +32 -3
- package/sass/components/_timepicker.scss +174 -82
- package/dist/pickers.d.ts +0 -130
package/dist/index.umd.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// Utility functions for the library
|
|
41
42
|
/**
|
|
42
43
|
* Create a unique ID
|
|
43
44
|
* @see https://stackoverflow.com/a/2117523/319711
|
|
@@ -1344,6 +1345,709 @@
|
|
|
1344
1345
|
};
|
|
1345
1346
|
};
|
|
1346
1347
|
|
|
1348
|
+
const defaultI18n = {
|
|
1349
|
+
cancel: 'Cancel',
|
|
1350
|
+
clear: 'Clear',
|
|
1351
|
+
done: 'Ok',
|
|
1352
|
+
previousMonth: '\u2039',
|
|
1353
|
+
nextMonth: '\u203a',
|
|
1354
|
+
months: [
|
|
1355
|
+
'January',
|
|
1356
|
+
'February',
|
|
1357
|
+
'March',
|
|
1358
|
+
'April',
|
|
1359
|
+
'May',
|
|
1360
|
+
'June',
|
|
1361
|
+
'July',
|
|
1362
|
+
'August',
|
|
1363
|
+
'September',
|
|
1364
|
+
'October',
|
|
1365
|
+
'November',
|
|
1366
|
+
'December',
|
|
1367
|
+
],
|
|
1368
|
+
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
1369
|
+
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
1370
|
+
weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
1371
|
+
weekdaysAbbrev: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
|
1372
|
+
};
|
|
1373
|
+
// Utility functions based on Materialize CSS implementation
|
|
1374
|
+
const isDate = (obj) => {
|
|
1375
|
+
return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
|
|
1376
|
+
};
|
|
1377
|
+
const isWeekend = (date) => {
|
|
1378
|
+
const day = date.getDay();
|
|
1379
|
+
return day === 0 || day === 6;
|
|
1380
|
+
};
|
|
1381
|
+
const setToStartOfDay = (date) => {
|
|
1382
|
+
if (isDate(date))
|
|
1383
|
+
date.setHours(0, 0, 0, 0);
|
|
1384
|
+
};
|
|
1385
|
+
const getDaysInMonth = (year, month) => {
|
|
1386
|
+
return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
1387
|
+
};
|
|
1388
|
+
const isLeapYear = (year) => {
|
|
1389
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
1390
|
+
};
|
|
1391
|
+
const compareDates = (a, b) => {
|
|
1392
|
+
return a.getTime() === b.getTime();
|
|
1393
|
+
};
|
|
1394
|
+
// Week number calculation utilities
|
|
1395
|
+
const getWeekNumber = (date, weekNumbering, firstDay) => {
|
|
1396
|
+
if (weekNumbering === 'iso') {
|
|
1397
|
+
return getISOWeekNumber(date);
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
return getLocalWeekNumber(date, firstDay);
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
const getISOWeekNumber = (date) => {
|
|
1404
|
+
// ISO 8601 week numbering
|
|
1405
|
+
const tempDate = new Date(date.getTime());
|
|
1406
|
+
const dayNum = (date.getDay() + 6) % 7; // Make Monday = 0
|
|
1407
|
+
tempDate.setDate(tempDate.getDate() - dayNum + 3); // Thursday in target week
|
|
1408
|
+
const firstThursday = new Date(tempDate.getFullYear(), 0, 4); // First Thursday of year
|
|
1409
|
+
const firstThursdayDayNum = (firstThursday.getDay() + 6) % 7; // Make Monday = 0
|
|
1410
|
+
firstThursday.setDate(firstThursday.getDate() - firstThursdayDayNum + 3);
|
|
1411
|
+
return Math.ceil((tempDate.getTime() - firstThursday.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
|
|
1412
|
+
};
|
|
1413
|
+
const getLocalWeekNumber = (date, firstDay) => {
|
|
1414
|
+
// Local week numbering based on firstDay setting
|
|
1415
|
+
const tempDate = new Date(date.getFullYear(), 0, 1);
|
|
1416
|
+
const firstDayOfYear = tempDate.getDay();
|
|
1417
|
+
// Calculate days from first day of year to the start of first week
|
|
1418
|
+
let daysToFirstWeek = (firstDay - firstDayOfYear + 7) % 7;
|
|
1419
|
+
if (daysToFirstWeek === 0 && firstDayOfYear !== firstDay) {
|
|
1420
|
+
daysToFirstWeek = 7;
|
|
1421
|
+
}
|
|
1422
|
+
const firstWeekStart = new Date(tempDate.getTime());
|
|
1423
|
+
firstWeekStart.setDate(1 + daysToFirstWeek);
|
|
1424
|
+
if (date < firstWeekStart) {
|
|
1425
|
+
// Date is in the last week of previous year
|
|
1426
|
+
return getLocalWeekNumber(new Date(date.getFullYear() - 1, 11, 31), firstDay);
|
|
1427
|
+
}
|
|
1428
|
+
const daysDiff = Math.floor((date.getTime() - firstWeekStart.getTime()) / (24 * 60 * 60 * 1000));
|
|
1429
|
+
return Math.floor(daysDiff / 7) + 1;
|
|
1430
|
+
};
|
|
1431
|
+
/**
|
|
1432
|
+
* Enhanced DatePicker component based on Materialize CSS datepicker
|
|
1433
|
+
*/
|
|
1434
|
+
const DatePicker = () => {
|
|
1435
|
+
let state;
|
|
1436
|
+
const mergeOptions = (attrs) => {
|
|
1437
|
+
// Handle HTML attributes
|
|
1438
|
+
let yearRange = 10;
|
|
1439
|
+
if (attrs.yearrange) {
|
|
1440
|
+
const parts = attrs.yearrange.split(',');
|
|
1441
|
+
if (parts.length === 2) {
|
|
1442
|
+
yearRange = [parseInt(parts[0], 10), parseInt(parts[1], 10)];
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
else if (attrs.yearRange) {
|
|
1446
|
+
yearRange = attrs.yearRange;
|
|
1447
|
+
}
|
|
1448
|
+
// Handle format - priority: format attribute > displayFormat > default
|
|
1449
|
+
let finalFormat = 'mmm dd, yyyy';
|
|
1450
|
+
if (attrs.format) {
|
|
1451
|
+
finalFormat = attrs.format;
|
|
1452
|
+
}
|
|
1453
|
+
else if (attrs.displayFormat) {
|
|
1454
|
+
finalFormat = attrs.displayFormat;
|
|
1455
|
+
}
|
|
1456
|
+
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, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1457
|
+
// Merge i18n properly
|
|
1458
|
+
merged.i18n = Object.assign(Object.assign({}, defaultI18n), attrs.i18n);
|
|
1459
|
+
return merged;
|
|
1460
|
+
};
|
|
1461
|
+
const toString = (date, format) => {
|
|
1462
|
+
if (!date || !isDate(date)) {
|
|
1463
|
+
return '';
|
|
1464
|
+
}
|
|
1465
|
+
// Split format into tokens - match longer patterns first
|
|
1466
|
+
const formatTokens = /(dddd|ddd|dd|d|mmmm|mmm|mm|m|yyyy|yy)/g;
|
|
1467
|
+
let result = format;
|
|
1468
|
+
// Replace all format tokens with actual values
|
|
1469
|
+
result = result.replace(formatTokens, (match) => {
|
|
1470
|
+
if (state.formats[match]) {
|
|
1471
|
+
return String(state.formats[match]());
|
|
1472
|
+
}
|
|
1473
|
+
return match;
|
|
1474
|
+
});
|
|
1475
|
+
return result;
|
|
1476
|
+
};
|
|
1477
|
+
const setDate = (date, preventOnSelect = false, options) => {
|
|
1478
|
+
if (!date) {
|
|
1479
|
+
state.date = null;
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
if (typeof date === 'string') {
|
|
1483
|
+
date = new Date(Date.parse(date));
|
|
1484
|
+
}
|
|
1485
|
+
if (!isDate(date)) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
const min = options.minDate;
|
|
1489
|
+
const max = options.maxDate;
|
|
1490
|
+
if (isDate(min) && date < min) {
|
|
1491
|
+
date = min;
|
|
1492
|
+
}
|
|
1493
|
+
else if (isDate(max) && date > max) {
|
|
1494
|
+
date = max;
|
|
1495
|
+
}
|
|
1496
|
+
state.date = new Date(date.getTime());
|
|
1497
|
+
setToStartOfDay(state.date);
|
|
1498
|
+
gotoDate(state.date);
|
|
1499
|
+
if (!preventOnSelect && options.onSelect) {
|
|
1500
|
+
options.onSelect(state.date);
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
const gotoDate = (date) => {
|
|
1504
|
+
if (!isDate(date)) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
state.calendars = [
|
|
1508
|
+
{
|
|
1509
|
+
month: date.getMonth(),
|
|
1510
|
+
year: date.getFullYear(),
|
|
1511
|
+
},
|
|
1512
|
+
];
|
|
1513
|
+
};
|
|
1514
|
+
const nextMonth = () => {
|
|
1515
|
+
state.calendars[0].month++;
|
|
1516
|
+
adjustCalendars();
|
|
1517
|
+
};
|
|
1518
|
+
const prevMonth = () => {
|
|
1519
|
+
state.calendars[0].month--;
|
|
1520
|
+
adjustCalendars();
|
|
1521
|
+
};
|
|
1522
|
+
const adjustCalendars = () => {
|
|
1523
|
+
state.calendars[0] = adjustCalendar(state.calendars[0]);
|
|
1524
|
+
};
|
|
1525
|
+
const adjustCalendar = (calendar) => {
|
|
1526
|
+
if (calendar.month < 0) {
|
|
1527
|
+
calendar.year -= Math.ceil(Math.abs(calendar.month) / 12);
|
|
1528
|
+
calendar.month += 12;
|
|
1529
|
+
}
|
|
1530
|
+
if (calendar.month > 11) {
|
|
1531
|
+
calendar.year += Math.floor(Math.abs(calendar.month) / 12);
|
|
1532
|
+
calendar.month -= 12;
|
|
1533
|
+
}
|
|
1534
|
+
return calendar;
|
|
1535
|
+
};
|
|
1536
|
+
const Day = () => {
|
|
1537
|
+
return {
|
|
1538
|
+
view: ({ attrs }) => {
|
|
1539
|
+
const { opts, options } = attrs;
|
|
1540
|
+
const arr = [];
|
|
1541
|
+
let ariaSelected = 'false';
|
|
1542
|
+
if (opts.isEmpty) {
|
|
1543
|
+
if (opts.showDaysInNextAndPreviousMonths) {
|
|
1544
|
+
arr.push('is-outside-current-month');
|
|
1545
|
+
arr.push('is-selection-disabled');
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
return m('td.is-empty');
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (opts.isDisabled) {
|
|
1552
|
+
arr.push('is-disabled');
|
|
1553
|
+
}
|
|
1554
|
+
if (opts.isToday) {
|
|
1555
|
+
arr.push('is-today');
|
|
1556
|
+
}
|
|
1557
|
+
if (opts.isSelected) {
|
|
1558
|
+
arr.push('is-selected');
|
|
1559
|
+
ariaSelected = 'true';
|
|
1560
|
+
}
|
|
1561
|
+
if (opts.hasEvent) {
|
|
1562
|
+
arr.push('has-event');
|
|
1563
|
+
}
|
|
1564
|
+
return m('td', {
|
|
1565
|
+
'data-day': opts.day,
|
|
1566
|
+
class: arr.join(' '),
|
|
1567
|
+
'aria-selected': ariaSelected,
|
|
1568
|
+
}, [
|
|
1569
|
+
m('button.datepicker-day-button', {
|
|
1570
|
+
type: 'button',
|
|
1571
|
+
'data-year': opts.year,
|
|
1572
|
+
'data-month': opts.month,
|
|
1573
|
+
'data-day': opts.day,
|
|
1574
|
+
onclick: (e) => {
|
|
1575
|
+
const target = e.target;
|
|
1576
|
+
if (!opts.isDisabled) {
|
|
1577
|
+
const year = parseInt(target.getAttribute('data-year') || '0', 10);
|
|
1578
|
+
const month = parseInt(target.getAttribute('data-month') || '0', 10);
|
|
1579
|
+
const day = parseInt(target.getAttribute('data-day') || '0', 10);
|
|
1580
|
+
const selectedDate = new Date(year, month, day);
|
|
1581
|
+
setDate(selectedDate, false, options);
|
|
1582
|
+
if (options.autoClose) {
|
|
1583
|
+
state.isOpen = false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
}, opts.day),
|
|
1588
|
+
]);
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
};
|
|
1592
|
+
const Calendar = () => {
|
|
1593
|
+
return {
|
|
1594
|
+
view: ({ attrs }) => {
|
|
1595
|
+
const { year, month, options, randId } = attrs;
|
|
1596
|
+
const now = new Date();
|
|
1597
|
+
const days = getDaysInMonth(year, month);
|
|
1598
|
+
let before = new Date(year, month, 1).getDay();
|
|
1599
|
+
const data = [];
|
|
1600
|
+
let row = [];
|
|
1601
|
+
setToStartOfDay(now);
|
|
1602
|
+
if (options.firstDay > 0) {
|
|
1603
|
+
before -= options.firstDay;
|
|
1604
|
+
if (before < 0) {
|
|
1605
|
+
before += 7;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const previousMonth = month === 0 ? 11 : month - 1;
|
|
1609
|
+
const nextMonth = month === 11 ? 0 : month + 1;
|
|
1610
|
+
const yearOfPreviousMonth = month === 0 ? year - 1 : year;
|
|
1611
|
+
const yearOfNextMonth = month === 11 ? year + 1 : year;
|
|
1612
|
+
const daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
|
|
1613
|
+
let cells = days + before;
|
|
1614
|
+
let after = cells;
|
|
1615
|
+
while (after > 7) {
|
|
1616
|
+
after -= 7;
|
|
1617
|
+
}
|
|
1618
|
+
cells += 7 - after;
|
|
1619
|
+
for (let i = 0, r = 0; i < cells; i++) {
|
|
1620
|
+
const day = new Date(year, month, 1 + (i - before));
|
|
1621
|
+
const isSelected = isDate(state.date) ? compareDates(day, state.date) : false;
|
|
1622
|
+
const isToday = compareDates(day, now);
|
|
1623
|
+
const isEmpty = i < before || i >= days + before;
|
|
1624
|
+
let dayNumber = 1 + (i - before);
|
|
1625
|
+
let monthNumber = month;
|
|
1626
|
+
let yearNumber = year;
|
|
1627
|
+
if (isEmpty) {
|
|
1628
|
+
if (i < before) {
|
|
1629
|
+
dayNumber = daysInPreviousMonth + dayNumber;
|
|
1630
|
+
monthNumber = previousMonth;
|
|
1631
|
+
yearNumber = yearOfPreviousMonth;
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
dayNumber = dayNumber - days;
|
|
1635
|
+
monthNumber = nextMonth;
|
|
1636
|
+
yearNumber = yearOfNextMonth;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const isDisabled = (options.minDate && day < options.minDate) ||
|
|
1640
|
+
(options.maxDate && day > options.maxDate) ||
|
|
1641
|
+
(options.disableWeekends && isWeekend(day)) ||
|
|
1642
|
+
(options.disableDayFn && options.disableDayFn(day));
|
|
1643
|
+
const dayConfig = {
|
|
1644
|
+
day: dayNumber,
|
|
1645
|
+
month: monthNumber,
|
|
1646
|
+
year: yearNumber,
|
|
1647
|
+
hasEvent: false,
|
|
1648
|
+
isSelected: isSelected,
|
|
1649
|
+
isToday: isToday,
|
|
1650
|
+
isDisabled: isDisabled,
|
|
1651
|
+
isEmpty: isEmpty,
|
|
1652
|
+
showDaysInNextAndPreviousMonths: false,
|
|
1653
|
+
};
|
|
1654
|
+
// Add week number cell at the beginning of each row
|
|
1655
|
+
if (r === 0 && options.showWeekNumbers) {
|
|
1656
|
+
const weekDate = new Date(yearNumber, monthNumber, dayNumber);
|
|
1657
|
+
const weekNum = getWeekNumber(weekDate, options.weekNumbering, options.firstDay);
|
|
1658
|
+
row.push(m('td.datepicker-week-number', {
|
|
1659
|
+
title: `Week ${weekNum}`
|
|
1660
|
+
}, weekNum));
|
|
1661
|
+
}
|
|
1662
|
+
row.push(m(Day, { opts: dayConfig, options }));
|
|
1663
|
+
if (++r === 7) {
|
|
1664
|
+
data.push(m('tr.datepicker-row', row));
|
|
1665
|
+
row = [];
|
|
1666
|
+
r = 0;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const weekdayHeaders = [];
|
|
1670
|
+
// Add week number header if enabled
|
|
1671
|
+
if (options.showWeekNumbers) {
|
|
1672
|
+
weekdayHeaders.push(m('th.datepicker-week-header', { scope: 'col', title: 'Week' }, 'Wk'));
|
|
1673
|
+
}
|
|
1674
|
+
for (let i = 0; i < 7; i++) {
|
|
1675
|
+
let day = i + options.firstDay;
|
|
1676
|
+
while (day >= 7) {
|
|
1677
|
+
day -= 7;
|
|
1678
|
+
}
|
|
1679
|
+
weekdayHeaders.push(m('th', { scope: 'col' }, [
|
|
1680
|
+
m('abbr', { title: options.i18n.weekdays[day] }, options.i18n.weekdaysAbbrev[day]),
|
|
1681
|
+
]));
|
|
1682
|
+
}
|
|
1683
|
+
return m('.datepicker-table-wrapper', [
|
|
1684
|
+
m('table.datepicker-table', {
|
|
1685
|
+
cellpadding: '0',
|
|
1686
|
+
cellspacing: '0',
|
|
1687
|
+
role: 'grid',
|
|
1688
|
+
'aria-labelledby': randId || 'datepicker-controls',
|
|
1689
|
+
class: options.showWeekNumbers ? 'with-week-numbers' : '',
|
|
1690
|
+
}, [m('thead', [m('tr', weekdayHeaders)]), m('tbody', data)]),
|
|
1691
|
+
]);
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
};
|
|
1695
|
+
const DateDisplay = () => {
|
|
1696
|
+
return {
|
|
1697
|
+
view: ({ attrs }) => {
|
|
1698
|
+
const { options } = attrs;
|
|
1699
|
+
const displayDate = isDate(state.date) ? state.date : new Date();
|
|
1700
|
+
const day = options.i18n.weekdaysShort[displayDate.getDay()];
|
|
1701
|
+
const month = options.i18n.monthsShort[displayDate.getMonth()];
|
|
1702
|
+
const date = displayDate.getDate();
|
|
1703
|
+
return m('.datepicker-date-display', [
|
|
1704
|
+
m('span.year-text', displayDate.getFullYear()),
|
|
1705
|
+
m('span.date-text', `${day}, ${month} ${date}`),
|
|
1706
|
+
]);
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
};
|
|
1710
|
+
const DateControls = () => {
|
|
1711
|
+
return {
|
|
1712
|
+
view: ({ attrs }) => {
|
|
1713
|
+
const { options, randId } = attrs;
|
|
1714
|
+
const calendar = state.calendars[0];
|
|
1715
|
+
const year = calendar.year;
|
|
1716
|
+
const month = calendar.month;
|
|
1717
|
+
// Year range calculation
|
|
1718
|
+
let yearStart, yearEnd;
|
|
1719
|
+
if (Array.isArray(options.yearRange)) {
|
|
1720
|
+
yearStart = options.yearRange[0];
|
|
1721
|
+
yearEnd = options.yearRange[1];
|
|
1722
|
+
}
|
|
1723
|
+
else {
|
|
1724
|
+
yearStart = year - options.yearRange;
|
|
1725
|
+
yearEnd = year + options.yearRange;
|
|
1726
|
+
}
|
|
1727
|
+
return m('.datepicker-controls', {
|
|
1728
|
+
id: randId,
|
|
1729
|
+
role: 'heading',
|
|
1730
|
+
'aria-live': 'assertive',
|
|
1731
|
+
}, [
|
|
1732
|
+
m('button.month-prev', {
|
|
1733
|
+
type: 'button',
|
|
1734
|
+
onclick: (e) => {
|
|
1735
|
+
e.preventDefault();
|
|
1736
|
+
prevMonth();
|
|
1737
|
+
},
|
|
1738
|
+
}, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
|
|
1739
|
+
m('path', { d: 'M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z' }),
|
|
1740
|
+
m('path', { d: 'M0-.5h24v24H0z', fill: 'none' }),
|
|
1741
|
+
])),
|
|
1742
|
+
m('.selects-container', [
|
|
1743
|
+
// Month select wrapper
|
|
1744
|
+
m('.select-wrapper.select-month', [
|
|
1745
|
+
m('input.select-dropdown.dropdown-trigger', {
|
|
1746
|
+
type: 'text',
|
|
1747
|
+
readonly: true,
|
|
1748
|
+
value: options.i18n.months[month],
|
|
1749
|
+
onclick: (e) => {
|
|
1750
|
+
e.preventDefault();
|
|
1751
|
+
state.monthDropdownOpen = !state.monthDropdownOpen;
|
|
1752
|
+
state.yearDropdownOpen = false; // Close year dropdown
|
|
1753
|
+
},
|
|
1754
|
+
}),
|
|
1755
|
+
// Custom dropdown menu
|
|
1756
|
+
state.monthDropdownOpen &&
|
|
1757
|
+
m('.dropdown-content', options.i18n.months.map((monthName, index) => m('.dropdown-item', {
|
|
1758
|
+
key: index,
|
|
1759
|
+
class: index === month ? 'selected' : '',
|
|
1760
|
+
onclick: (e) => {
|
|
1761
|
+
e.stopPropagation();
|
|
1762
|
+
gotoMonth(index);
|
|
1763
|
+
state.monthDropdownOpen = false;
|
|
1764
|
+
},
|
|
1765
|
+
}, monthName))),
|
|
1766
|
+
]),
|
|
1767
|
+
// Year select wrapper
|
|
1768
|
+
m('.select-wrapper.select-year', [
|
|
1769
|
+
m('input.select-dropdown.dropdown-trigger', {
|
|
1770
|
+
type: 'text',
|
|
1771
|
+
readonly: true,
|
|
1772
|
+
value: year.toString(),
|
|
1773
|
+
onclick: (e) => {
|
|
1774
|
+
e.preventDefault();
|
|
1775
|
+
state.yearDropdownOpen = !state.yearDropdownOpen;
|
|
1776
|
+
state.monthDropdownOpen = false; // Close month dropdown
|
|
1777
|
+
},
|
|
1778
|
+
}),
|
|
1779
|
+
// Custom dropdown menu
|
|
1780
|
+
state.yearDropdownOpen &&
|
|
1781
|
+
m('.dropdown-content', range(yearStart, yearEnd).map((i) => m('.dropdown-item', {
|
|
1782
|
+
key: i,
|
|
1783
|
+
class: i === year ? 'selected' : '',
|
|
1784
|
+
onclick: (e) => {
|
|
1785
|
+
e.stopPropagation();
|
|
1786
|
+
gotoYear(i);
|
|
1787
|
+
state.yearDropdownOpen = false;
|
|
1788
|
+
},
|
|
1789
|
+
}, i))),
|
|
1790
|
+
]),
|
|
1791
|
+
]),
|
|
1792
|
+
m('button.month-next', {
|
|
1793
|
+
type: 'button',
|
|
1794
|
+
onclick: (e) => {
|
|
1795
|
+
e.preventDefault();
|
|
1796
|
+
nextMonth();
|
|
1797
|
+
},
|
|
1798
|
+
}, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
|
|
1799
|
+
m('path', { d: 'M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z' }),
|
|
1800
|
+
m('path', { d: 'M0-.25h24v24H0z', fill: 'none' }),
|
|
1801
|
+
])),
|
|
1802
|
+
]);
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
};
|
|
1806
|
+
const gotoMonth = (month) => {
|
|
1807
|
+
if (!isNaN(month)) {
|
|
1808
|
+
state.calendars[0].month = month;
|
|
1809
|
+
adjustCalendars();
|
|
1810
|
+
// Update selected date if one exists
|
|
1811
|
+
if (state.date && isDate(state.date)) {
|
|
1812
|
+
const currentDay = state.date.getDate();
|
|
1813
|
+
const newYear = state.calendars[0].year;
|
|
1814
|
+
const daysInNewMonth = getDaysInMonth(newYear, month);
|
|
1815
|
+
// Adjust day if it doesn't exist in the new month (e.g., Jan 31 -> Feb 28)
|
|
1816
|
+
const adjustedDay = Math.min(currentDay, daysInNewMonth);
|
|
1817
|
+
const newDate = new Date(newYear, month, adjustedDay);
|
|
1818
|
+
state.date = newDate;
|
|
1819
|
+
setToStartOfDay(state.date);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
const gotoYear = (year) => {
|
|
1824
|
+
if (!isNaN(year)) {
|
|
1825
|
+
state.calendars[0].year = year;
|
|
1826
|
+
adjustCalendars();
|
|
1827
|
+
// Update selected date if one exists
|
|
1828
|
+
if (state.date && isDate(state.date)) {
|
|
1829
|
+
const currentMonth = state.date.getMonth();
|
|
1830
|
+
const currentDay = state.date.getDate();
|
|
1831
|
+
const daysInNewMonth = getDaysInMonth(year, currentMonth);
|
|
1832
|
+
// Adjust day if it doesn't exist in the new year/month (e.g., leap year changes)
|
|
1833
|
+
const adjustedDay = Math.min(currentDay, daysInNewMonth);
|
|
1834
|
+
const newDate = new Date(year, currentMonth, adjustedDay);
|
|
1835
|
+
state.date = newDate;
|
|
1836
|
+
setToStartOfDay(state.date);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
const handleDocumentClick = (e) => {
|
|
1841
|
+
const target = e.target;
|
|
1842
|
+
if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
|
|
1843
|
+
state.monthDropdownOpen = false;
|
|
1844
|
+
state.yearDropdownOpen = false;
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
return {
|
|
1848
|
+
oninit: (vnode) => {
|
|
1849
|
+
const attrs = vnode.attrs;
|
|
1850
|
+
const options = mergeOptions(attrs);
|
|
1851
|
+
state = {
|
|
1852
|
+
id: uniqueId(),
|
|
1853
|
+
isOpen: false,
|
|
1854
|
+
date: null,
|
|
1855
|
+
calendars: [{ month: 0, year: 0 }],
|
|
1856
|
+
monthDropdownOpen: false,
|
|
1857
|
+
yearDropdownOpen: false,
|
|
1858
|
+
formats: {
|
|
1859
|
+
d: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDate()) || 0; },
|
|
1860
|
+
dd: () => {
|
|
1861
|
+
var _a;
|
|
1862
|
+
const d = ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDate()) || 0;
|
|
1863
|
+
return (d < 10 ? '0' : '') + d;
|
|
1864
|
+
},
|
|
1865
|
+
ddd: () => { var _a; return options.i18n.weekdaysShort[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDay()) || 0]; },
|
|
1866
|
+
dddd: () => { var _a; return options.i18n.weekdays[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDay()) || 0]; },
|
|
1867
|
+
m: () => { var _a; return (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1; },
|
|
1868
|
+
mm: () => {
|
|
1869
|
+
var _a;
|
|
1870
|
+
const m = (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1;
|
|
1871
|
+
return (m < 10 ? '0' : '') + m;
|
|
1872
|
+
},
|
|
1873
|
+
mmm: () => { var _a; return options.i18n.monthsShort[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
|
|
1874
|
+
mmmm: () => { var _a; return options.i18n.months[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
|
|
1875
|
+
yy: () => { var _a; return ('' + (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0)).slice(2); },
|
|
1876
|
+
yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
|
|
1877
|
+
},
|
|
1878
|
+
};
|
|
1879
|
+
// Initialize date
|
|
1880
|
+
let defaultDate = attrs.defaultDate;
|
|
1881
|
+
if (!defaultDate && attrs.initialValue) {
|
|
1882
|
+
defaultDate = new Date(attrs.initialValue);
|
|
1883
|
+
}
|
|
1884
|
+
if (isDate(defaultDate)) {
|
|
1885
|
+
// Always set the date if we have initialValue or defaultDate
|
|
1886
|
+
setDate(defaultDate, true, options);
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
gotoDate(new Date());
|
|
1890
|
+
}
|
|
1891
|
+
// Add document click listener to close dropdowns
|
|
1892
|
+
document.addEventListener('click', handleDocumentClick);
|
|
1893
|
+
},
|
|
1894
|
+
onremove: () => {
|
|
1895
|
+
// Clean up event listener
|
|
1896
|
+
document.removeEventListener('click', handleDocumentClick);
|
|
1897
|
+
},
|
|
1898
|
+
view: (vnode) => {
|
|
1899
|
+
const attrs = vnode.attrs;
|
|
1900
|
+
const options = mergeOptions(attrs);
|
|
1901
|
+
const { id = state.id, label, dateLabel, placeholder, disabled, readonly, required, iconName, helperText, onchange, oninput, className: cn1, class: cn2, } = attrs;
|
|
1902
|
+
const className = cn1 || cn2 || 'col s12';
|
|
1903
|
+
// Calculate display value for the input
|
|
1904
|
+
let displayValue = '';
|
|
1905
|
+
if (state.date) {
|
|
1906
|
+
displayValue = toString(state.date, options.format);
|
|
1907
|
+
}
|
|
1908
|
+
// Custom date format handling
|
|
1909
|
+
if (attrs.displayFormat) {
|
|
1910
|
+
// const formatRegex = /(yyyy|mm|dd)/gi;
|
|
1911
|
+
let customDisplayValue = attrs.displayFormat;
|
|
1912
|
+
if (state.date) {
|
|
1913
|
+
customDisplayValue = customDisplayValue
|
|
1914
|
+
.replace(/yyyy/gi, state.date.getFullYear().toString())
|
|
1915
|
+
.replace(/mm/gi, (state.date.getMonth() + 1).toString().padStart(2, '0'))
|
|
1916
|
+
.replace(/dd/gi, state.date.getDate().toString().padStart(2, '0'));
|
|
1917
|
+
displayValue = customDisplayValue;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
return m('.input-field', {
|
|
1921
|
+
className,
|
|
1922
|
+
}, [
|
|
1923
|
+
// Icon prefix
|
|
1924
|
+
iconName && m('i.material-icons.prefix', iconName),
|
|
1925
|
+
// Date input field
|
|
1926
|
+
m('input.datepicker', {
|
|
1927
|
+
id,
|
|
1928
|
+
type: 'text',
|
|
1929
|
+
value: displayValue,
|
|
1930
|
+
placeholder,
|
|
1931
|
+
disabled,
|
|
1932
|
+
readonly,
|
|
1933
|
+
required,
|
|
1934
|
+
onclick: () => {
|
|
1935
|
+
if (!disabled && !readonly) {
|
|
1936
|
+
state.isOpen = true;
|
|
1937
|
+
if (options.onOpen)
|
|
1938
|
+
options.onOpen();
|
|
1939
|
+
}
|
|
1940
|
+
},
|
|
1941
|
+
oninput: (e) => {
|
|
1942
|
+
if (oninput) {
|
|
1943
|
+
const target = e.target;
|
|
1944
|
+
oninput(target.value);
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
onchange: (e) => {
|
|
1948
|
+
if (onchange) {
|
|
1949
|
+
const target = e.target;
|
|
1950
|
+
// Try to parse the input value
|
|
1951
|
+
const date = new Date(target.value);
|
|
1952
|
+
if (isDate(date)) {
|
|
1953
|
+
setDate(date, false, options);
|
|
1954
|
+
onchange(toString(date, 'yyyy-mm-dd')); // Always return ISO format
|
|
1955
|
+
}
|
|
1956
|
+
else {
|
|
1957
|
+
onchange(target.value);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
}),
|
|
1962
|
+
// Label
|
|
1963
|
+
(label || dateLabel) &&
|
|
1964
|
+
m('label', {
|
|
1965
|
+
for: id,
|
|
1966
|
+
class: displayValue || placeholder ? 'active' : '',
|
|
1967
|
+
}, label || dateLabel),
|
|
1968
|
+
// Helper text
|
|
1969
|
+
helperText && m('span.helper-text', helperText),
|
|
1970
|
+
// Modal datepicker
|
|
1971
|
+
state.isOpen && [
|
|
1972
|
+
m('.modal.datepicker-modal.open', {
|
|
1973
|
+
id: `modal-${state.id}`,
|
|
1974
|
+
tabindex: 0,
|
|
1975
|
+
style: {
|
|
1976
|
+
zIndex: 1003,
|
|
1977
|
+
display: 'block',
|
|
1978
|
+
opacity: 1,
|
|
1979
|
+
top: '10%',
|
|
1980
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
1981
|
+
},
|
|
1982
|
+
}, [
|
|
1983
|
+
m('.modal-content.datepicker-container', {
|
|
1984
|
+
onclick: (e) => {
|
|
1985
|
+
// Close dropdowns when clicking anywhere in the modal content
|
|
1986
|
+
const target = e.target;
|
|
1987
|
+
if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
|
|
1988
|
+
state.monthDropdownOpen = false;
|
|
1989
|
+
state.yearDropdownOpen = false;
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1992
|
+
}, [
|
|
1993
|
+
m(DateDisplay, { options }),
|
|
1994
|
+
m('.datepicker-calendar-container', [
|
|
1995
|
+
m('.datepicker-calendar', [
|
|
1996
|
+
m(DateControls, { options, randId: `datepicker-title-${Math.random().toString(36).slice(2)}` }),
|
|
1997
|
+
m(Calendar, { year: state.calendars[0].year, month: state.calendars[0].month, options }),
|
|
1998
|
+
]),
|
|
1999
|
+
m('.datepicker-footer', [
|
|
2000
|
+
options.showClearBtn &&
|
|
2001
|
+
m('button.btn-flat.datepicker-clear.waves-effect', {
|
|
2002
|
+
type: 'button',
|
|
2003
|
+
style: '',
|
|
2004
|
+
onclick: () => {
|
|
2005
|
+
setDate(null, false, options);
|
|
2006
|
+
state.isOpen = false;
|
|
2007
|
+
},
|
|
2008
|
+
}, options.i18n.clear),
|
|
2009
|
+
m('button.btn-flat.datepicker-cancel.waves-effect', {
|
|
2010
|
+
type: 'button',
|
|
2011
|
+
onclick: () => {
|
|
2012
|
+
state.isOpen = false;
|
|
2013
|
+
if (options.onClose)
|
|
2014
|
+
options.onClose();
|
|
2015
|
+
},
|
|
2016
|
+
}, options.i18n.cancel),
|
|
2017
|
+
m('button.btn-flat.datepicker-done.waves-effect', {
|
|
2018
|
+
type: 'button',
|
|
2019
|
+
onclick: () => {
|
|
2020
|
+
state.isOpen = false;
|
|
2021
|
+
if (state.date && onchange) {
|
|
2022
|
+
onchange(toString(state.date, 'yyyy-mm-dd')); // Always return ISO format
|
|
2023
|
+
}
|
|
2024
|
+
if (options.onClose)
|
|
2025
|
+
options.onClose();
|
|
2026
|
+
},
|
|
2027
|
+
}, options.i18n.done),
|
|
2028
|
+
]),
|
|
2029
|
+
]),
|
|
2030
|
+
]),
|
|
2031
|
+
]),
|
|
2032
|
+
// Modal overlay
|
|
2033
|
+
m('.modal-overlay', {
|
|
2034
|
+
style: {
|
|
2035
|
+
zIndex: 1002,
|
|
2036
|
+
display: 'block',
|
|
2037
|
+
opacity: 0.5,
|
|
2038
|
+
},
|
|
2039
|
+
onclick: () => {
|
|
2040
|
+
state.isOpen = false;
|
|
2041
|
+
if (options.onClose)
|
|
2042
|
+
options.onClose();
|
|
2043
|
+
},
|
|
2044
|
+
}),
|
|
2045
|
+
],
|
|
2046
|
+
]);
|
|
2047
|
+
},
|
|
2048
|
+
};
|
|
2049
|
+
};
|
|
2050
|
+
|
|
1347
2051
|
/** Pure TypeScript Dropdown component - no Materialize dependencies */
|
|
1348
2052
|
const Dropdown = () => {
|
|
1349
2053
|
const state = {
|
|
@@ -1500,6 +2204,7 @@
|
|
|
1500
2204
|
m(MaterialIcon, {
|
|
1501
2205
|
name: 'caret',
|
|
1502
2206
|
direction: 'down',
|
|
2207
|
+
class: 'caret',
|
|
1503
2208
|
}),
|
|
1504
2209
|
]),
|
|
1505
2210
|
]);
|
|
@@ -2570,698 +3275,624 @@
|
|
|
2570
3275
|
};
|
|
2571
3276
|
};
|
|
2572
3277
|
|
|
2573
|
-
const
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
'
|
|
2585
|
-
'
|
|
2586
|
-
'
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
};
|
|
2598
|
-
// Utility functions based on Materialize CSS implementation
|
|
2599
|
-
const isDate = (obj) => {
|
|
2600
|
-
return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
|
|
2601
|
-
};
|
|
2602
|
-
const isWeekend = (date) => {
|
|
2603
|
-
const day = date.getDay();
|
|
2604
|
-
return day === 0 || day === 6;
|
|
2605
|
-
};
|
|
2606
|
-
const setToStartOfDay = (date) => {
|
|
2607
|
-
if (isDate(date))
|
|
2608
|
-
date.setHours(0, 0, 0, 0);
|
|
2609
|
-
};
|
|
2610
|
-
const getDaysInMonth = (year, month) => {
|
|
2611
|
-
return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
2612
|
-
};
|
|
2613
|
-
const isLeapYear = (year) => {
|
|
2614
|
-
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
2615
|
-
};
|
|
2616
|
-
const compareDates = (a, b) => {
|
|
2617
|
-
return a.getTime() === b.getTime();
|
|
3278
|
+
const defaultOptions = {
|
|
3279
|
+
dialRadius: 135,
|
|
3280
|
+
outerRadius: 105,
|
|
3281
|
+
innerRadius: 70,
|
|
3282
|
+
tickRadius: 20,
|
|
3283
|
+
duration: 350,
|
|
3284
|
+
container: null,
|
|
3285
|
+
defaultTime: 'now',
|
|
3286
|
+
fromNow: 0,
|
|
3287
|
+
showClearBtn: false,
|
|
3288
|
+
i18n: {
|
|
3289
|
+
cancel: 'Cancel',
|
|
3290
|
+
clear: 'Clear',
|
|
3291
|
+
done: 'Ok',
|
|
3292
|
+
},
|
|
3293
|
+
autoClose: false,
|
|
3294
|
+
twelveHour: true,
|
|
3295
|
+
vibrate: true,
|
|
3296
|
+
onOpen: () => { },
|
|
3297
|
+
onOpenStart: () => { },
|
|
3298
|
+
onOpenEnd: () => { },
|
|
3299
|
+
onCloseStart: () => { },
|
|
3300
|
+
onCloseEnd: () => { },
|
|
3301
|
+
onSelect: () => { },
|
|
2618
3302
|
};
|
|
2619
3303
|
/**
|
|
2620
|
-
*
|
|
3304
|
+
* TimePicker component based on original Materialize CSS timepicker
|
|
2621
3305
|
*/
|
|
2622
|
-
const
|
|
3306
|
+
const TimePicker = () => {
|
|
2623
3307
|
let state;
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
if (attrs.yearrange) {
|
|
2628
|
-
const parts = attrs.yearrange.split(',');
|
|
2629
|
-
if (parts.length === 2) {
|
|
2630
|
-
yearRange = [parseInt(parts[0], 10), parseInt(parts[1], 10)];
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
else if (attrs.yearRange) {
|
|
2634
|
-
yearRange = attrs.yearRange;
|
|
2635
|
-
}
|
|
2636
|
-
// Handle format - priority: format attribute > displayFormat > default
|
|
2637
|
-
let finalFormat = 'mmm dd, yyyy';
|
|
2638
|
-
if (attrs.format) {
|
|
2639
|
-
finalFormat = attrs.format;
|
|
2640
|
-
}
|
|
2641
|
-
else if (attrs.displayFormat) {
|
|
2642
|
-
finalFormat = attrs.displayFormat;
|
|
2643
|
-
}
|
|
2644
|
-
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, i18n: defaultI18n, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
2645
|
-
// Merge i18n properly
|
|
2646
|
-
merged.i18n = Object.assign(Object.assign({}, defaultI18n), attrs.i18n);
|
|
2647
|
-
return merged;
|
|
3308
|
+
let options;
|
|
3309
|
+
const addLeadingZero = (num) => {
|
|
3310
|
+
return (num < 10 ? '0' : '') + num;
|
|
2648
3311
|
};
|
|
2649
|
-
const
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
}
|
|
2653
|
-
// Split format into tokens - match longer patterns first
|
|
2654
|
-
const formatTokens = /(dddd|ddd|dd|d|mmmm|mmm|mm|m|yyyy|yy)/g;
|
|
2655
|
-
let result = format;
|
|
2656
|
-
// Replace all format tokens with actual values
|
|
2657
|
-
result = result.replace(formatTokens, (match) => {
|
|
2658
|
-
if (state.formats[match]) {
|
|
2659
|
-
return String(state.formats[match]());
|
|
2660
|
-
}
|
|
2661
|
-
return match;
|
|
2662
|
-
});
|
|
2663
|
-
return result;
|
|
3312
|
+
const createSVGEl = (name) => {
|
|
3313
|
+
const svgNS = 'http://www.w3.org/2000/svg';
|
|
3314
|
+
return document.createElementNS(svgNS, name);
|
|
2664
3315
|
};
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
if (typeof date === 'string') {
|
|
2671
|
-
date = new Date(Date.parse(date));
|
|
2672
|
-
}
|
|
2673
|
-
if (!isDate(date)) {
|
|
2674
|
-
return;
|
|
2675
|
-
}
|
|
2676
|
-
const min = options.minDate;
|
|
2677
|
-
const max = options.maxDate;
|
|
2678
|
-
if (isDate(min) && date < min) {
|
|
2679
|
-
date = min;
|
|
3316
|
+
const getPos = (e) => {
|
|
3317
|
+
const touchEvent = e;
|
|
3318
|
+
const mouseEvent = e;
|
|
3319
|
+
if (touchEvent.targetTouches && touchEvent.targetTouches.length >= 1) {
|
|
3320
|
+
return { x: touchEvent.targetTouches[0].clientX, y: touchEvent.targetTouches[0].clientY };
|
|
2680
3321
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
3322
|
+
return { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
3323
|
+
};
|
|
3324
|
+
const vibrate = () => {
|
|
3325
|
+
if (state.vibrateTimer) {
|
|
3326
|
+
clearTimeout(state.vibrateTimer);
|
|
2683
3327
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
3328
|
+
if (options.vibrate && navigator.vibrate) {
|
|
3329
|
+
navigator.vibrate(10);
|
|
3330
|
+
state.vibrateTimer = window.setTimeout(() => {
|
|
3331
|
+
state.vibrateTimer = undefined;
|
|
3332
|
+
}, 100);
|
|
2689
3333
|
}
|
|
2690
3334
|
};
|
|
2691
|
-
const
|
|
2692
|
-
|
|
3335
|
+
const handleClockClickStart = (e) => {
|
|
3336
|
+
e.preventDefault();
|
|
3337
|
+
if (!state.plate)
|
|
2693
3338
|
return;
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
state.calendars[0].month--;
|
|
2708
|
-
adjustCalendars();
|
|
2709
|
-
};
|
|
2710
|
-
const adjustCalendars = () => {
|
|
2711
|
-
state.calendars[0] = adjustCalendar(state.calendars[0]);
|
|
3339
|
+
const clockPlateBR = state.plate.getBoundingClientRect();
|
|
3340
|
+
const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
|
|
3341
|
+
state.x0 = offset.x + options.dialRadius;
|
|
3342
|
+
state.y0 = offset.y + options.dialRadius;
|
|
3343
|
+
state.moved = false;
|
|
3344
|
+
const clickPos = getPos(e);
|
|
3345
|
+
state.dx = clickPos.x - state.x0;
|
|
3346
|
+
state.dy = clickPos.y - state.y0;
|
|
3347
|
+
setHand(state.dx, state.dy, false);
|
|
3348
|
+
document.addEventListener('mousemove', handleDocumentClickMove);
|
|
3349
|
+
document.addEventListener('touchmove', handleDocumentClickMove);
|
|
3350
|
+
document.addEventListener('mouseup', handleDocumentClickEnd);
|
|
3351
|
+
document.addEventListener('touchend', handleDocumentClickEnd);
|
|
2712
3352
|
};
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
calendar.month -= 12;
|
|
2721
|
-
}
|
|
2722
|
-
return calendar;
|
|
3353
|
+
const handleDocumentClickMove = (e) => {
|
|
3354
|
+
e.preventDefault();
|
|
3355
|
+
const clickPos = getPos(e);
|
|
3356
|
+
const x = clickPos.x - state.x0;
|
|
3357
|
+
const y = clickPos.y - state.y0;
|
|
3358
|
+
state.moved = true;
|
|
3359
|
+
setHand(x, y, false);
|
|
2723
3360
|
};
|
|
2724
|
-
const
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
3361
|
+
const handleDocumentClickEnd = (e) => {
|
|
3362
|
+
e.preventDefault();
|
|
3363
|
+
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
3364
|
+
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
3365
|
+
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
3366
|
+
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
3367
|
+
const clickPos = getPos(e);
|
|
3368
|
+
const x = clickPos.x - state.x0;
|
|
3369
|
+
const y = clickPos.y - state.y0;
|
|
3370
|
+
if (state.moved && x === state.dx && y === state.dy) {
|
|
3371
|
+
setHand(x, y);
|
|
3372
|
+
}
|
|
3373
|
+
if (state.currentView === 'hours') {
|
|
3374
|
+
showView('minutes', options.duration / 2);
|
|
3375
|
+
}
|
|
3376
|
+
else if (options.autoClose) {
|
|
3377
|
+
if (state.minutesView) {
|
|
3378
|
+
state.minutesView.classList.add('timepicker-dial-out');
|
|
2730
3379
|
}
|
|
3380
|
+
setTimeout(() => {
|
|
3381
|
+
done();
|
|
3382
|
+
}, options.duration / 2);
|
|
3383
|
+
}
|
|
3384
|
+
if (options.onSelect) {
|
|
3385
|
+
options.onSelect(state.hours, state.minutes);
|
|
2731
3386
|
}
|
|
2732
|
-
if (opts.isDisabled) {
|
|
2733
|
-
arr.push('is-disabled');
|
|
2734
|
-
}
|
|
2735
|
-
if (opts.isToday) {
|
|
2736
|
-
arr.push('is-today');
|
|
2737
|
-
}
|
|
2738
|
-
if (opts.isSelected) {
|
|
2739
|
-
arr.push('is-selected');
|
|
2740
|
-
ariaSelected = 'true';
|
|
2741
|
-
}
|
|
2742
|
-
return m('td', {
|
|
2743
|
-
'data-day': opts.day,
|
|
2744
|
-
class: arr.join(' '),
|
|
2745
|
-
'aria-selected': ariaSelected,
|
|
2746
|
-
}, [
|
|
2747
|
-
m('button.datepicker-day-button', {
|
|
2748
|
-
type: 'button',
|
|
2749
|
-
'data-year': opts.year,
|
|
2750
|
-
'data-month': opts.month,
|
|
2751
|
-
'data-day': opts.day,
|
|
2752
|
-
onclick: (e) => {
|
|
2753
|
-
const target = e.target;
|
|
2754
|
-
if (!opts.isDisabled) {
|
|
2755
|
-
const year = parseInt(target.getAttribute('data-year') || '0', 10);
|
|
2756
|
-
const month = parseInt(target.getAttribute('data-month') || '0', 10);
|
|
2757
|
-
const day = parseInt(target.getAttribute('data-day') || '0', 10);
|
|
2758
|
-
const selectedDate = new Date(year, month, day);
|
|
2759
|
-
setDate(selectedDate, false, options);
|
|
2760
|
-
if (options.autoClose) {
|
|
2761
|
-
state.isOpen = false;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
},
|
|
2765
|
-
}, opts.day),
|
|
2766
|
-
]);
|
|
2767
3387
|
};
|
|
2768
|
-
const
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
before -= options.firstDay;
|
|
2777
|
-
if (before < 0) {
|
|
2778
|
-
before += 7;
|
|
3388
|
+
const updateTimeFromInput = (inputValue) => {
|
|
3389
|
+
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
3390
|
+
if (options.twelveHour && value.length > 1) {
|
|
3391
|
+
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
3392
|
+
state.amOrPm = 'AM';
|
|
3393
|
+
}
|
|
3394
|
+
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
3395
|
+
state.amOrPm = 'PM';
|
|
2779
3396
|
}
|
|
3397
|
+
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
2780
3398
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
let cells = days + before;
|
|
2787
|
-
let after = cells;
|
|
2788
|
-
while (after > 7) {
|
|
2789
|
-
after -= 7;
|
|
2790
|
-
}
|
|
2791
|
-
cells += 7 - after;
|
|
2792
|
-
for (let i = 0, r = 0; i < cells; i++) {
|
|
2793
|
-
const day = new Date(year, month, 1 + (i - before));
|
|
2794
|
-
const isSelected = isDate(state.date) ? compareDates(day, state.date) : false;
|
|
2795
|
-
const isToday = compareDates(day, now);
|
|
2796
|
-
const isEmpty = i < before || i >= days + before;
|
|
2797
|
-
let dayNumber = 1 + (i - before);
|
|
2798
|
-
let monthNumber = month;
|
|
2799
|
-
let yearNumber = year;
|
|
2800
|
-
if (isEmpty) {
|
|
2801
|
-
if (i < before) {
|
|
2802
|
-
dayNumber = daysInPreviousMonth + dayNumber;
|
|
2803
|
-
monthNumber = previousMonth;
|
|
2804
|
-
yearNumber = yearOfPreviousMonth;
|
|
2805
|
-
}
|
|
2806
|
-
else {
|
|
2807
|
-
dayNumber = dayNumber - days;
|
|
2808
|
-
monthNumber = nextMonth;
|
|
2809
|
-
yearNumber = yearOfNextMonth;
|
|
2810
|
-
}
|
|
3399
|
+
if (value[0] === 'now') {
|
|
3400
|
+
const now = new Date(+new Date() + options.fromNow);
|
|
3401
|
+
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
3402
|
+
if (options.twelveHour) {
|
|
3403
|
+
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
2811
3404
|
}
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
isSelected: isSelected,
|
|
2821
|
-
isToday: isToday,
|
|
2822
|
-
isDisabled: isDisabled,
|
|
2823
|
-
isEmpty: isEmpty};
|
|
2824
|
-
row.push(renderDay(dayConfig, options));
|
|
2825
|
-
if (++r === 7) {
|
|
2826
|
-
data.push(m('tr.datepicker-row', row));
|
|
2827
|
-
row = [];
|
|
2828
|
-
r = 0;
|
|
3405
|
+
}
|
|
3406
|
+
let hours = +value[0] || 0;
|
|
3407
|
+
let minutes = +value[1] || 0;
|
|
3408
|
+
// Handle 24-hour to 12-hour conversion if needed
|
|
3409
|
+
if (options.twelveHour && hours >= 12) {
|
|
3410
|
+
state.amOrPm = 'PM';
|
|
3411
|
+
if (hours > 12) {
|
|
3412
|
+
hours = hours - 12;
|
|
2829
3413
|
}
|
|
2830
3414
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
day -= 7;
|
|
3415
|
+
else if (options.twelveHour && hours < 12) {
|
|
3416
|
+
state.amOrPm = 'AM';
|
|
3417
|
+
if (hours === 0) {
|
|
3418
|
+
hours = 12;
|
|
2836
3419
|
}
|
|
2837
|
-
weekdayHeaders.push(m('th', { scope: 'col' }, [
|
|
2838
|
-
m('abbr', { title: options.i18n.weekdays[day] }, options.i18n.weekdaysAbbrev[day]),
|
|
2839
|
-
]));
|
|
2840
3420
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
role: 'grid',
|
|
2846
|
-
'aria-labelledby': 'datepicker-controls',
|
|
2847
|
-
}, [m('thead', [m('tr', weekdayHeaders)]), m('tbody', data)]),
|
|
2848
|
-
]);
|
|
2849
|
-
};
|
|
2850
|
-
const renderDateDisplay = (options) => {
|
|
2851
|
-
const displayDate = isDate(state.date) ? state.date : new Date();
|
|
2852
|
-
const day = options.i18n.weekdaysShort[displayDate.getDay()];
|
|
2853
|
-
const month = options.i18n.monthsShort[displayDate.getMonth()];
|
|
2854
|
-
const date = displayDate.getDate();
|
|
2855
|
-
return m('.datepicker-date-display', [
|
|
2856
|
-
m('span.year-text', displayDate.getFullYear()),
|
|
2857
|
-
m('span.date-text', `${day}, ${month} ${date}`),
|
|
2858
|
-
]);
|
|
2859
|
-
};
|
|
2860
|
-
const renderControls = (options, randId) => {
|
|
2861
|
-
const calendar = state.calendars[0];
|
|
2862
|
-
const year = calendar.year;
|
|
2863
|
-
const month = calendar.month;
|
|
2864
|
-
// Month options
|
|
2865
|
-
const monthOptions = [];
|
|
2866
|
-
for (let i = 0; i < 12; i++) {
|
|
2867
|
-
monthOptions.push(m('option', {
|
|
2868
|
-
value: i,
|
|
2869
|
-
selected: i === month ? 'selected' : undefined,
|
|
2870
|
-
}, options.i18n.months[i]));
|
|
2871
|
-
}
|
|
2872
|
-
// Year options
|
|
2873
|
-
const yearOptions = [];
|
|
2874
|
-
let yearStart, yearEnd;
|
|
2875
|
-
if (Array.isArray(options.yearRange)) {
|
|
2876
|
-
yearStart = options.yearRange[0];
|
|
2877
|
-
yearEnd = options.yearRange[1];
|
|
3421
|
+
state.hours = hours;
|
|
3422
|
+
state.minutes = minutes;
|
|
3423
|
+
if (state.spanHours) {
|
|
3424
|
+
state.spanHours.innerHTML = state.hours.toString();
|
|
2878
3425
|
}
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
for (let i = yearStart; i <= yearEnd; i++) {
|
|
2884
|
-
yearOptions.push(m('option', {
|
|
2885
|
-
value: i,
|
|
2886
|
-
selected: i === year ? 'selected' : undefined,
|
|
2887
|
-
}, i));
|
|
2888
|
-
}
|
|
2889
|
-
return m('.datepicker-controls', {
|
|
2890
|
-
id: randId,
|
|
2891
|
-
role: 'heading',
|
|
2892
|
-
'aria-live': 'assertive',
|
|
2893
|
-
}, [
|
|
2894
|
-
m('button.month-prev', {
|
|
2895
|
-
type: 'button',
|
|
2896
|
-
onclick: (e) => {
|
|
2897
|
-
e.preventDefault();
|
|
2898
|
-
prevMonth();
|
|
2899
|
-
},
|
|
2900
|
-
}, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
|
|
2901
|
-
m('path', { d: 'M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z' }),
|
|
2902
|
-
m('path', { d: 'M0-.5h24v24H0z', fill: 'none' }),
|
|
2903
|
-
])),
|
|
2904
|
-
m('.selects-container', [
|
|
2905
|
-
// Month select wrapper
|
|
2906
|
-
m('.select-wrapper.select-month', [
|
|
2907
|
-
m('input.select-dropdown.dropdown-trigger', {
|
|
2908
|
-
type: 'text',
|
|
2909
|
-
readonly: true,
|
|
2910
|
-
value: options.i18n.months[month],
|
|
2911
|
-
onclick: (e) => {
|
|
2912
|
-
e.preventDefault();
|
|
2913
|
-
state.monthDropdownOpen = !state.monthDropdownOpen;
|
|
2914
|
-
state.yearDropdownOpen = false; // Close year dropdown
|
|
2915
|
-
},
|
|
2916
|
-
}),
|
|
2917
|
-
// Custom dropdown menu
|
|
2918
|
-
state.monthDropdownOpen &&
|
|
2919
|
-
m('.dropdown-content', options.i18n.months.map((monthName, index) => m('.dropdown-item', {
|
|
2920
|
-
key: index,
|
|
2921
|
-
class: index === month ? 'selected' : '',
|
|
2922
|
-
onclick: (e) => {
|
|
2923
|
-
e.stopPropagation();
|
|
2924
|
-
gotoMonth(index);
|
|
2925
|
-
state.monthDropdownOpen = false;
|
|
2926
|
-
},
|
|
2927
|
-
}, monthName))),
|
|
2928
|
-
]),
|
|
2929
|
-
// Year select wrapper
|
|
2930
|
-
m('.select-wrapper.select-year', [
|
|
2931
|
-
m('input.select-dropdown.dropdown-trigger', {
|
|
2932
|
-
type: 'text',
|
|
2933
|
-
readonly: true,
|
|
2934
|
-
value: year.toString(),
|
|
2935
|
-
onclick: (e) => {
|
|
2936
|
-
e.preventDefault();
|
|
2937
|
-
state.yearDropdownOpen = !state.yearDropdownOpen;
|
|
2938
|
-
state.monthDropdownOpen = false; // Close month dropdown
|
|
2939
|
-
},
|
|
2940
|
-
}),
|
|
2941
|
-
// Custom dropdown menu
|
|
2942
|
-
state.yearDropdownOpen &&
|
|
2943
|
-
m('.dropdown-content', range(yearStart, yearEnd).map((i) => m('.dropdown-item', {
|
|
2944
|
-
key: i,
|
|
2945
|
-
class: i === year ? 'selected' : '',
|
|
2946
|
-
onclick: (e) => {
|
|
2947
|
-
e.stopPropagation();
|
|
2948
|
-
gotoYear(i);
|
|
2949
|
-
state.yearDropdownOpen = false;
|
|
2950
|
-
},
|
|
2951
|
-
}, i))),
|
|
2952
|
-
]),
|
|
2953
|
-
]),
|
|
2954
|
-
m('button.month-next', {
|
|
2955
|
-
type: 'button',
|
|
2956
|
-
onclick: (e) => {
|
|
2957
|
-
e.preventDefault();
|
|
2958
|
-
nextMonth();
|
|
2959
|
-
},
|
|
2960
|
-
}, m('svg', { fill: '#000000', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' }, [
|
|
2961
|
-
m('path', { d: 'M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z' }),
|
|
2962
|
-
m('path', { d: 'M0-.25h24v24H0z', fill: 'none' }),
|
|
2963
|
-
])),
|
|
2964
|
-
]);
|
|
3426
|
+
if (state.spanMinutes) {
|
|
3427
|
+
state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
|
|
3428
|
+
}
|
|
3429
|
+
updateAmPmView();
|
|
2965
3430
|
};
|
|
2966
|
-
const
|
|
2967
|
-
if (
|
|
2968
|
-
state.
|
|
2969
|
-
|
|
3431
|
+
const updateAmPmView = () => {
|
|
3432
|
+
if (options.twelveHour && state.amBtn && state.pmBtn) {
|
|
3433
|
+
state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
|
|
3434
|
+
state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
|
|
2970
3435
|
}
|
|
2971
3436
|
};
|
|
2972
|
-
const
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
3437
|
+
const showView = (view, delay) => {
|
|
3438
|
+
const isHours = view === 'hours';
|
|
3439
|
+
const nextView = isHours ? state.hoursView : state.minutesView;
|
|
3440
|
+
const hideView = isHours ? state.minutesView : state.hoursView;
|
|
3441
|
+
state.currentView = view;
|
|
3442
|
+
if (state.spanHours) {
|
|
3443
|
+
state.spanHours.classList.toggle('text-primary', isHours);
|
|
2976
3444
|
}
|
|
3445
|
+
if (state.spanMinutes) {
|
|
3446
|
+
state.spanMinutes.classList.toggle('text-primary', !isHours);
|
|
3447
|
+
}
|
|
3448
|
+
if (hideView) {
|
|
3449
|
+
hideView.classList.add('timepicker-dial-out');
|
|
3450
|
+
}
|
|
3451
|
+
if (nextView) {
|
|
3452
|
+
nextView.style.visibility = 'visible';
|
|
3453
|
+
nextView.classList.remove('timepicker-dial-out');
|
|
3454
|
+
}
|
|
3455
|
+
resetClock(delay);
|
|
3456
|
+
if (state.toggleViewTimer) {
|
|
3457
|
+
clearTimeout(state.toggleViewTimer);
|
|
3458
|
+
}
|
|
3459
|
+
state.toggleViewTimer = window.setTimeout(() => {
|
|
3460
|
+
if (hideView) {
|
|
3461
|
+
hideView.style.visibility = 'hidden';
|
|
3462
|
+
}
|
|
3463
|
+
}, options.duration);
|
|
2977
3464
|
};
|
|
2978
|
-
const
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3465
|
+
const resetClock = (delay) => {
|
|
3466
|
+
const view = state.currentView;
|
|
3467
|
+
const value = state[view];
|
|
3468
|
+
const isHours = view === 'hours';
|
|
3469
|
+
const unit = Math.PI / (isHours ? 6 : 30);
|
|
3470
|
+
const radian = value * unit;
|
|
3471
|
+
const radius = isHours && value > 0 && value < 13 ? options.innerRadius : options.outerRadius;
|
|
3472
|
+
const x = Math.sin(radian) * radius;
|
|
3473
|
+
const y = -Math.cos(radian) * radius;
|
|
3474
|
+
if (delay && state.canvas) {
|
|
3475
|
+
state.canvas.classList.add('timepicker-canvas-out');
|
|
3476
|
+
setTimeout(() => {
|
|
3477
|
+
if (state.canvas) {
|
|
3478
|
+
state.canvas.classList.remove('timepicker-canvas-out');
|
|
3479
|
+
}
|
|
3480
|
+
setHand(x, y);
|
|
3481
|
+
}, delay);
|
|
3482
|
+
}
|
|
3483
|
+
else {
|
|
3484
|
+
setHand(x, y);
|
|
2983
3485
|
}
|
|
2984
3486
|
};
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
dddd: () => { var _a; return options.i18n.weekdays[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDay()) || 0]; },
|
|
3005
|
-
m: () => { var _a; return (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1; },
|
|
3006
|
-
mm: () => {
|
|
3007
|
-
var _a;
|
|
3008
|
-
const m = (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0) + 1;
|
|
3009
|
-
return (m < 10 ? '0' : '') + m;
|
|
3010
|
-
},
|
|
3011
|
-
mmm: () => { var _a; return options.i18n.monthsShort[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
|
|
3012
|
-
mmmm: () => { var _a; return options.i18n.months[((_a = state.date) === null || _a === void 0 ? void 0 : _a.getMonth()) || 0]; },
|
|
3013
|
-
yy: () => { var _a; return ('' + (((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0)).slice(2); },
|
|
3014
|
-
yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
|
|
3015
|
-
},
|
|
3016
|
-
};
|
|
3017
|
-
// Initialize date
|
|
3018
|
-
let defaultDate = attrs.defaultDate;
|
|
3019
|
-
if (!defaultDate && attrs.initialValue) {
|
|
3020
|
-
defaultDate = new Date(attrs.initialValue);
|
|
3021
|
-
}
|
|
3022
|
-
if (isDate(defaultDate)) {
|
|
3023
|
-
// Always set the date if we have initialValue or defaultDate
|
|
3024
|
-
setDate(defaultDate, true, options);
|
|
3487
|
+
const setHand = (x, y, roundBy5, _dragging) => {
|
|
3488
|
+
let radian = Math.atan2(x, -y);
|
|
3489
|
+
const isHours = state.currentView === 'hours';
|
|
3490
|
+
const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
|
|
3491
|
+
const z = Math.sqrt(x * x + y * y);
|
|
3492
|
+
const inner = isHours && z < (options.outerRadius + options.innerRadius) / 2;
|
|
3493
|
+
let radius = inner ? options.innerRadius : options.outerRadius;
|
|
3494
|
+
if (options.twelveHour) {
|
|
3495
|
+
radius = options.outerRadius;
|
|
3496
|
+
}
|
|
3497
|
+
if (radian < 0) {
|
|
3498
|
+
radian = Math.PI * 2 + radian;
|
|
3499
|
+
}
|
|
3500
|
+
let value = Math.round(radian / unit);
|
|
3501
|
+
radian = value * unit;
|
|
3502
|
+
if (options.twelveHour) {
|
|
3503
|
+
if (isHours) {
|
|
3504
|
+
if (value === 0)
|
|
3505
|
+
value = 12;
|
|
3025
3506
|
}
|
|
3026
3507
|
else {
|
|
3027
|
-
|
|
3508
|
+
if (roundBy5)
|
|
3509
|
+
value *= 5;
|
|
3510
|
+
if (value === 60)
|
|
3511
|
+
value = 0;
|
|
3028
3512
|
}
|
|
3029
|
-
// Add document click listener to close dropdowns
|
|
3030
|
-
document.addEventListener('click', handleDocumentClick);
|
|
3031
|
-
},
|
|
3032
|
-
onremove: () => {
|
|
3033
|
-
// Clean up event listener
|
|
3034
|
-
document.removeEventListener('click', handleDocumentClick);
|
|
3035
|
-
},
|
|
3036
|
-
view: (vnode) => {
|
|
3037
|
-
const attrs = vnode.attrs;
|
|
3038
|
-
const options = mergeOptions(attrs);
|
|
3039
|
-
const { id = state.id, label, dateLabel, placeholder, disabled, required, className, style, helperText, iconName, newRow, } = attrs;
|
|
3040
|
-
// Use dateLabel if label is not provided (backward compatibility)
|
|
3041
|
-
const finalLabel = label || dateLabel;
|
|
3042
|
-
const finalClassName = newRow ? `${className || ''} clear` : className;
|
|
3043
|
-
return m('.input-field', {
|
|
3044
|
-
className: finalClassName,
|
|
3045
|
-
style,
|
|
3046
|
-
}, [
|
|
3047
|
-
// Icon prefix
|
|
3048
|
-
iconName && m('i.material-icons.prefix', iconName),
|
|
3049
|
-
// Main date input
|
|
3050
|
-
m('input.datepicker', {
|
|
3051
|
-
id,
|
|
3052
|
-
type: 'text',
|
|
3053
|
-
value: toString(state.date, options.format),
|
|
3054
|
-
placeholder,
|
|
3055
|
-
disabled,
|
|
3056
|
-
required,
|
|
3057
|
-
readonly: true,
|
|
3058
|
-
format: attrs.format,
|
|
3059
|
-
yearrange: attrs.yearrange,
|
|
3060
|
-
tabindex: '0',
|
|
3061
|
-
onclick: () => {
|
|
3062
|
-
if (!disabled) {
|
|
3063
|
-
state.isOpen = true;
|
|
3064
|
-
if (options.onOpen)
|
|
3065
|
-
options.onOpen();
|
|
3066
|
-
}
|
|
3067
|
-
},
|
|
3068
|
-
}),
|
|
3069
|
-
// Label
|
|
3070
|
-
finalLabel &&
|
|
3071
|
-
m('label', {
|
|
3072
|
-
for: id,
|
|
3073
|
-
class: state.date || placeholder ? 'active' : '',
|
|
3074
|
-
}, finalLabel),
|
|
3075
|
-
// Helper text
|
|
3076
|
-
helperText && m('span.helper-text', helperText),
|
|
3077
|
-
// Modal datepicker
|
|
3078
|
-
state.isOpen && [
|
|
3079
|
-
m('.modal.datepicker-modal.open', {
|
|
3080
|
-
id: `modal-${state.id}`,
|
|
3081
|
-
tabindex: 0,
|
|
3082
|
-
style: {
|
|
3083
|
-
zIndex: 1003,
|
|
3084
|
-
display: 'block',
|
|
3085
|
-
opacity: 1,
|
|
3086
|
-
top: '10%',
|
|
3087
|
-
transform: 'scaleX(1) scaleY(1)',
|
|
3088
|
-
},
|
|
3089
|
-
}, [
|
|
3090
|
-
m('.modal-content.datepicker-container', {
|
|
3091
|
-
onclick: (e) => {
|
|
3092
|
-
// Close dropdowns when clicking anywhere in the modal content
|
|
3093
|
-
const target = e.target;
|
|
3094
|
-
if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
|
|
3095
|
-
state.monthDropdownOpen = false;
|
|
3096
|
-
state.yearDropdownOpen = false;
|
|
3097
|
-
}
|
|
3098
|
-
},
|
|
3099
|
-
}, [
|
|
3100
|
-
renderDateDisplay(options),
|
|
3101
|
-
m('.datepicker-calendar-container', [
|
|
3102
|
-
m('.datepicker-calendar', [
|
|
3103
|
-
renderControls(options, `datepicker-title-${Math.random().toString(36).slice(2)}`),
|
|
3104
|
-
renderCalendar(state.calendars[0].year, state.calendars[0].month, options),
|
|
3105
|
-
]),
|
|
3106
|
-
m('.datepicker-footer', [
|
|
3107
|
-
options.showClearBtn &&
|
|
3108
|
-
m('button.btn-flat.datepicker-clear.waves-effect', {
|
|
3109
|
-
type: 'button',
|
|
3110
|
-
style: '',
|
|
3111
|
-
onclick: () => {
|
|
3112
|
-
setDate(null, false, options);
|
|
3113
|
-
state.isOpen = false;
|
|
3114
|
-
},
|
|
3115
|
-
}, options.i18n.clear),
|
|
3116
|
-
m('.confirmation-btns', [
|
|
3117
|
-
m('button.btn-flat.datepicker-cancel.waves-effect', {
|
|
3118
|
-
type: 'button',
|
|
3119
|
-
onclick: () => {
|
|
3120
|
-
state.isOpen = false;
|
|
3121
|
-
if (options.onClose)
|
|
3122
|
-
options.onClose();
|
|
3123
|
-
},
|
|
3124
|
-
}, options.i18n.cancel),
|
|
3125
|
-
m('button.btn-flat.datepicker-done.waves-effect', {
|
|
3126
|
-
type: 'button',
|
|
3127
|
-
onclick: () => {
|
|
3128
|
-
if (attrs.onchange && state.date) {
|
|
3129
|
-
attrs.onchange(state.date.toISOString().split('T')[0]);
|
|
3130
|
-
}
|
|
3131
|
-
state.isOpen = false;
|
|
3132
|
-
if (options.onClose)
|
|
3133
|
-
options.onClose();
|
|
3134
|
-
},
|
|
3135
|
-
}, options.i18n.done),
|
|
3136
|
-
]),
|
|
3137
|
-
]),
|
|
3138
|
-
]),
|
|
3139
|
-
]),
|
|
3140
|
-
]),
|
|
3141
|
-
// Modal overlay
|
|
3142
|
-
m('.modal-overlay', {
|
|
3143
|
-
style: {
|
|
3144
|
-
zIndex: 1002,
|
|
3145
|
-
display: 'block',
|
|
3146
|
-
opacity: 0.5,
|
|
3147
|
-
},
|
|
3148
|
-
onclick: () => {
|
|
3149
|
-
state.isOpen = false;
|
|
3150
|
-
if (options.onClose)
|
|
3151
|
-
options.onClose();
|
|
3152
|
-
},
|
|
3153
|
-
}),
|
|
3154
|
-
],
|
|
3155
|
-
]);
|
|
3156
|
-
},
|
|
3157
|
-
};
|
|
3158
|
-
};
|
|
3159
|
-
/**
|
|
3160
|
-
* Enhanced TimePicker component with i18n support and improved functionality.
|
|
3161
|
-
*
|
|
3162
|
-
* Usage:
|
|
3163
|
-
* - Use `initialValue` to set the current/initial time value (24h format: "HH:MM")
|
|
3164
|
-
* - Use `defaultTime` only if you need a fallback when the field is cleared
|
|
3165
|
-
* - The component accepts and outputs 24-hour format strings ("HH:MM")
|
|
3166
|
-
* - Display format (12h/24h) is controlled by the `twelveHour` property
|
|
3167
|
-
*/
|
|
3168
|
-
const TimePicker = () => {
|
|
3169
|
-
const state = {
|
|
3170
|
-
id: uniqueId(),
|
|
3171
|
-
isOpen: false,
|
|
3172
|
-
hours: 12,
|
|
3173
|
-
minutes: 0,
|
|
3174
|
-
ampm: 'AM',
|
|
3175
|
-
use12Hour: false,
|
|
3176
|
-
time: ''};
|
|
3177
|
-
const parseTime = (timeString) => {
|
|
3178
|
-
if (!timeString)
|
|
3179
|
-
return { hours: 12, minutes: 0, ampm: 'AM' };
|
|
3180
|
-
const [time, ampm] = timeString.split(' ');
|
|
3181
|
-
const [hoursStr, minutesStr] = time.split(':');
|
|
3182
|
-
let hours = parseInt(hoursStr, 10) || 0;
|
|
3183
|
-
const minutes = parseInt(minutesStr, 10) || 0;
|
|
3184
|
-
if (ampm) {
|
|
3185
|
-
// 12-hour format
|
|
3186
|
-
if (ampm.toUpperCase() === 'PM' && hours !== 12)
|
|
3187
|
-
hours += 12;
|
|
3188
|
-
if (ampm.toUpperCase() === 'AM' && hours === 12)
|
|
3189
|
-
hours = 0;
|
|
3190
|
-
return { hours, minutes, ampm: ampm.toUpperCase() };
|
|
3191
3513
|
}
|
|
3192
3514
|
else {
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3515
|
+
if (isHours) {
|
|
3516
|
+
if (value === 12)
|
|
3517
|
+
value = 0;
|
|
3518
|
+
value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
|
|
3519
|
+
}
|
|
3520
|
+
else {
|
|
3521
|
+
if (roundBy5)
|
|
3522
|
+
value *= 5;
|
|
3523
|
+
if (value === 60)
|
|
3524
|
+
value = 0;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
if (state[state.currentView] !== value) {
|
|
3528
|
+
vibrate();
|
|
3196
3529
|
}
|
|
3530
|
+
state[state.currentView] = value;
|
|
3531
|
+
if (isHours && state.spanHours) {
|
|
3532
|
+
state.spanHours.innerHTML = value.toString();
|
|
3533
|
+
}
|
|
3534
|
+
else if (!isHours && state.spanMinutes) {
|
|
3535
|
+
state.spanMinutes.innerHTML = addLeadingZero(value);
|
|
3536
|
+
}
|
|
3537
|
+
// Set clock hand position
|
|
3538
|
+
if (state.hand && state.bg) {
|
|
3539
|
+
const cx1 = Math.sin(radian) * (radius - options.tickRadius);
|
|
3540
|
+
const cy1 = -Math.cos(radian) * (radius - options.tickRadius);
|
|
3541
|
+
const cx2 = Math.sin(radian) * radius;
|
|
3542
|
+
const cy2 = -Math.cos(radian) * radius;
|
|
3543
|
+
state.hand.setAttribute('x2', cx1.toString());
|
|
3544
|
+
state.hand.setAttribute('y2', cy1.toString());
|
|
3545
|
+
state.bg.setAttribute('cx', cx2.toString());
|
|
3546
|
+
state.bg.setAttribute('cy', cy2.toString());
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
const buildSVGClock = () => {
|
|
3550
|
+
if (!state.canvas)
|
|
3551
|
+
return;
|
|
3552
|
+
const dialRadius = options.dialRadius;
|
|
3553
|
+
const tickRadius = options.tickRadius;
|
|
3554
|
+
const diameter = dialRadius * 2;
|
|
3555
|
+
const svg = createSVGEl('svg');
|
|
3556
|
+
svg.setAttribute('class', 'timepicker-svg');
|
|
3557
|
+
svg.setAttribute('width', diameter.toString());
|
|
3558
|
+
svg.setAttribute('height', diameter.toString());
|
|
3559
|
+
const g = createSVGEl('g');
|
|
3560
|
+
g.setAttribute('transform', `translate(${dialRadius},${dialRadius})`);
|
|
3561
|
+
const bearing = createSVGEl('circle');
|
|
3562
|
+
bearing.setAttribute('class', 'timepicker-canvas-bearing');
|
|
3563
|
+
bearing.setAttribute('cx', '0');
|
|
3564
|
+
bearing.setAttribute('cy', '0');
|
|
3565
|
+
bearing.setAttribute('r', '4');
|
|
3566
|
+
const hand = createSVGEl('line');
|
|
3567
|
+
hand.setAttribute('x1', '0');
|
|
3568
|
+
hand.setAttribute('y1', '0');
|
|
3569
|
+
const bg = createSVGEl('circle');
|
|
3570
|
+
bg.setAttribute('class', 'timepicker-canvas-bg');
|
|
3571
|
+
bg.setAttribute('r', tickRadius.toString());
|
|
3572
|
+
g.appendChild(hand);
|
|
3573
|
+
g.appendChild(bg);
|
|
3574
|
+
g.appendChild(bearing);
|
|
3575
|
+
svg.appendChild(g);
|
|
3576
|
+
state.canvas.appendChild(svg);
|
|
3577
|
+
state.hand = hand;
|
|
3578
|
+
state.bg = bg;
|
|
3579
|
+
state.bearing = bearing;
|
|
3580
|
+
state.g = g;
|
|
3197
3581
|
};
|
|
3198
|
-
const
|
|
3199
|
-
if (
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3582
|
+
const buildHoursView = () => {
|
|
3583
|
+
if (!state.hoursView)
|
|
3584
|
+
return;
|
|
3585
|
+
if (options.twelveHour) {
|
|
3586
|
+
for (let i = 1; i < 13; i++) {
|
|
3587
|
+
const tick = document.createElement('div');
|
|
3588
|
+
tick.className = 'timepicker-tick';
|
|
3589
|
+
const radian = (i / 6) * Math.PI;
|
|
3590
|
+
const radius = options.outerRadius;
|
|
3591
|
+
tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
|
|
3592
|
+
tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
|
|
3593
|
+
tick.innerHTML = i === 0 ? '00' : i.toString();
|
|
3594
|
+
state.hoursView.appendChild(tick);
|
|
3595
|
+
}
|
|
3203
3596
|
}
|
|
3204
3597
|
else {
|
|
3205
|
-
|
|
3598
|
+
for (let i = 0; i < 24; i++) {
|
|
3599
|
+
const tick = document.createElement('div');
|
|
3600
|
+
tick.className = 'timepicker-tick';
|
|
3601
|
+
const radian = (i / 6) * Math.PI;
|
|
3602
|
+
const inner = i > 0 && i < 13;
|
|
3603
|
+
const radius = inner ? options.innerRadius : options.outerRadius;
|
|
3604
|
+
tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
|
|
3605
|
+
tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
|
|
3606
|
+
tick.innerHTML = i === 0 ? '00' : i.toString();
|
|
3607
|
+
state.hoursView.appendChild(tick);
|
|
3608
|
+
}
|
|
3206
3609
|
}
|
|
3207
3610
|
};
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
if (attrs.oninput) {
|
|
3220
|
-
attrs.oninput(timeString);
|
|
3611
|
+
const buildMinutesView = () => {
|
|
3612
|
+
if (!state.minutesView)
|
|
3613
|
+
return;
|
|
3614
|
+
for (let i = 0; i < 60; i += 5) {
|
|
3615
|
+
const tick = document.createElement('div');
|
|
3616
|
+
tick.className = 'timepicker-tick';
|
|
3617
|
+
const radian = (i / 30) * Math.PI;
|
|
3618
|
+
tick.style.left = options.dialRadius + Math.sin(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
3619
|
+
tick.style.top = options.dialRadius - Math.cos(radian) * options.outerRadius - options.tickRadius + 'px';
|
|
3620
|
+
tick.innerHTML = addLeadingZero(i);
|
|
3621
|
+
state.minutesView.appendChild(tick);
|
|
3221
3622
|
}
|
|
3222
|
-
|
|
3223
|
-
|
|
3623
|
+
};
|
|
3624
|
+
const handleAmPmClick = (ampm) => {
|
|
3625
|
+
state.amOrPm = ampm;
|
|
3626
|
+
updateAmPmView();
|
|
3627
|
+
};
|
|
3628
|
+
const open = (inputValue) => {
|
|
3629
|
+
if (state.isOpen)
|
|
3630
|
+
return;
|
|
3631
|
+
state.isOpen = true;
|
|
3632
|
+
updateTimeFromInput(inputValue);
|
|
3633
|
+
showView('hours');
|
|
3634
|
+
if (options.onOpen)
|
|
3635
|
+
options.onOpen();
|
|
3636
|
+
if (options.onOpenStart)
|
|
3637
|
+
options.onOpenStart();
|
|
3638
|
+
if (options.onOpenEnd)
|
|
3639
|
+
options.onOpenEnd();
|
|
3640
|
+
};
|
|
3641
|
+
const close = () => {
|
|
3642
|
+
if (!state.isOpen)
|
|
3643
|
+
return;
|
|
3644
|
+
state.isOpen = false;
|
|
3645
|
+
if (options.onCloseStart)
|
|
3646
|
+
options.onCloseStart();
|
|
3647
|
+
if (options.onCloseEnd)
|
|
3648
|
+
options.onCloseEnd();
|
|
3649
|
+
};
|
|
3650
|
+
const done = (clearValue) => {
|
|
3651
|
+
// const last = ''; // We'll get this from the actual input
|
|
3652
|
+
let value = clearValue ? '' : addLeadingZero(state.hours) + ':' + addLeadingZero(state.minutes);
|
|
3653
|
+
if (!clearValue && options.twelveHour) {
|
|
3654
|
+
value = `${value} ${state.amOrPm}`;
|
|
3224
3655
|
}
|
|
3656
|
+
close();
|
|
3657
|
+
return value;
|
|
3658
|
+
};
|
|
3659
|
+
const clear = () => {
|
|
3660
|
+
return done(true);
|
|
3661
|
+
};
|
|
3662
|
+
const TimepickerModal = () => {
|
|
3663
|
+
return {
|
|
3664
|
+
view: ({ attrs }) => {
|
|
3665
|
+
const { showClearBtn, clearLabel, closeLabel, doneLabel } = attrs;
|
|
3666
|
+
return [
|
|
3667
|
+
m('.modal-content.timepicker-container', [
|
|
3668
|
+
m('.timepicker-digital-display', [
|
|
3669
|
+
m('.timepicker-text-container', [
|
|
3670
|
+
m('.timepicker-display-column', [
|
|
3671
|
+
m('span.timepicker-span-hours', {
|
|
3672
|
+
class: state.currentView === 'hours' ? 'text-primary' : '',
|
|
3673
|
+
onclick: () => showView('hours'),
|
|
3674
|
+
oncreate: (vnode) => {
|
|
3675
|
+
state.spanHours = vnode.dom;
|
|
3676
|
+
},
|
|
3677
|
+
}, state.hours.toString()),
|
|
3678
|
+
':',
|
|
3679
|
+
m('span.timepicker-span-minutes', {
|
|
3680
|
+
class: state.currentView === 'minutes' ? 'text-primary' : '',
|
|
3681
|
+
onclick: () => showView('minutes'),
|
|
3682
|
+
oncreate: (vnode) => {
|
|
3683
|
+
state.spanMinutes = vnode.dom;
|
|
3684
|
+
},
|
|
3685
|
+
}, addLeadingZero(state.minutes)),
|
|
3686
|
+
]),
|
|
3687
|
+
options.twelveHour &&
|
|
3688
|
+
m('.timepicker-display-column.timepicker-display-am-pm', [
|
|
3689
|
+
m('.timepicker-span-am-pm', {
|
|
3690
|
+
oncreate: (vnode) => {
|
|
3691
|
+
state.spanAmPm = vnode.dom;
|
|
3692
|
+
},
|
|
3693
|
+
}, [
|
|
3694
|
+
m('.am-btn', {
|
|
3695
|
+
class: state.amOrPm === 'AM' ? 'text-primary' : '',
|
|
3696
|
+
onclick: () => handleAmPmClick('AM'),
|
|
3697
|
+
oncreate: (vnode) => {
|
|
3698
|
+
state.amBtn = vnode.dom;
|
|
3699
|
+
},
|
|
3700
|
+
}, 'AM'),
|
|
3701
|
+
m('.pm-btn', {
|
|
3702
|
+
class: state.amOrPm === 'PM' ? 'text-primary' : '',
|
|
3703
|
+
onclick: () => handleAmPmClick('PM'),
|
|
3704
|
+
oncreate: (vnode) => {
|
|
3705
|
+
state.pmBtn = vnode.dom;
|
|
3706
|
+
},
|
|
3707
|
+
}, 'PM'),
|
|
3708
|
+
]),
|
|
3709
|
+
]),
|
|
3710
|
+
]),
|
|
3711
|
+
]),
|
|
3712
|
+
m('.timepicker-analog-display', [
|
|
3713
|
+
m('.timepicker-plate', {
|
|
3714
|
+
oncreate: (vnode) => {
|
|
3715
|
+
state.plate = vnode.dom;
|
|
3716
|
+
state.plate.addEventListener('mousedown', handleClockClickStart);
|
|
3717
|
+
state.plate.addEventListener('touchstart', handleClockClickStart);
|
|
3718
|
+
},
|
|
3719
|
+
onremove: () => {
|
|
3720
|
+
if (state.plate) {
|
|
3721
|
+
state.plate.removeEventListener('mousedown', handleClockClickStart);
|
|
3722
|
+
state.plate.removeEventListener('touchstart', handleClockClickStart);
|
|
3723
|
+
}
|
|
3724
|
+
},
|
|
3725
|
+
}, [
|
|
3726
|
+
m('.timepicker-canvas', {
|
|
3727
|
+
oncreate: (vnode) => {
|
|
3728
|
+
state.canvas = vnode.dom;
|
|
3729
|
+
buildSVGClock();
|
|
3730
|
+
// Position the hand after SVG is built
|
|
3731
|
+
setTimeout(() => resetClock(), 10);
|
|
3732
|
+
},
|
|
3733
|
+
}),
|
|
3734
|
+
m('.timepicker-dial.timepicker-hours', {
|
|
3735
|
+
oncreate: (vnode) => {
|
|
3736
|
+
state.hoursView = vnode.dom;
|
|
3737
|
+
buildHoursView();
|
|
3738
|
+
},
|
|
3739
|
+
}),
|
|
3740
|
+
m('.timepicker-dial.timepicker-minutes.timepicker-dial-out', {
|
|
3741
|
+
oncreate: (vnode) => {
|
|
3742
|
+
state.minutesView = vnode.dom;
|
|
3743
|
+
buildMinutesView();
|
|
3744
|
+
},
|
|
3745
|
+
}),
|
|
3746
|
+
]),
|
|
3747
|
+
m('.timepicker-footer', {
|
|
3748
|
+
oncreate: (vnode) => {
|
|
3749
|
+
state.footer = vnode.dom;
|
|
3750
|
+
},
|
|
3751
|
+
}, [
|
|
3752
|
+
m('button.btn-flat.timepicker-clear.waves-effect', {
|
|
3753
|
+
type: 'button',
|
|
3754
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
3755
|
+
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
3756
|
+
onclick: () => clear(),
|
|
3757
|
+
}, clearLabel),
|
|
3758
|
+
m('.confirmation-btns', [
|
|
3759
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
3760
|
+
type: 'button',
|
|
3761
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
3762
|
+
onclick: () => close(),
|
|
3763
|
+
}, closeLabel),
|
|
3764
|
+
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
3765
|
+
type: 'button',
|
|
3766
|
+
tabindex: options.twelveHour ? '3' : '1',
|
|
3767
|
+
onclick: () => done(),
|
|
3768
|
+
}, doneLabel),
|
|
3769
|
+
]),
|
|
3770
|
+
]),
|
|
3771
|
+
]),
|
|
3772
|
+
]),
|
|
3773
|
+
];
|
|
3774
|
+
},
|
|
3775
|
+
};
|
|
3225
3776
|
};
|
|
3226
3777
|
return {
|
|
3227
3778
|
oninit: (vnode) => {
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3779
|
+
const attrs = vnode.attrs;
|
|
3780
|
+
options = Object.assign(Object.assign({}, defaultOptions), attrs);
|
|
3781
|
+
state = {
|
|
3782
|
+
id: uniqueId(),
|
|
3783
|
+
isOpen: false,
|
|
3784
|
+
hours: 12,
|
|
3785
|
+
minutes: 0,
|
|
3786
|
+
amOrPm: 'AM',
|
|
3787
|
+
currentView: 'hours',
|
|
3788
|
+
moved: false,
|
|
3789
|
+
x0: 0,
|
|
3790
|
+
y0: 0,
|
|
3791
|
+
dx: 0,
|
|
3792
|
+
dy: 0,
|
|
3793
|
+
};
|
|
3794
|
+
// Handle initial value after options are set
|
|
3795
|
+
if (attrs.initialValue) {
|
|
3796
|
+
updateTimeFromInput(attrs.initialValue);
|
|
3237
3797
|
}
|
|
3238
3798
|
},
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3799
|
+
onremove: () => {
|
|
3800
|
+
// Cleanup
|
|
3801
|
+
if (state.toggleViewTimer) {
|
|
3802
|
+
clearTimeout(state.toggleViewTimer);
|
|
3803
|
+
}
|
|
3804
|
+
if (state.vibrateTimer) {
|
|
3805
|
+
clearTimeout(state.vibrateTimer);
|
|
3806
|
+
}
|
|
3807
|
+
document.removeEventListener('mousemove', handleDocumentClickMove);
|
|
3808
|
+
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
3809
|
+
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
3810
|
+
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
3811
|
+
},
|
|
3812
|
+
view: ({ attrs }) => {
|
|
3813
|
+
const { id = state.id, label, placeholder, disabled, readonly, required, iconName, helperText, onchange, oninput, useModal = true, showClearBtn = false, clearLabel = 'Clear', closeLabel = 'Cancel', twelveHour, className: cn1, class: cn2, } = attrs;
|
|
3814
|
+
const className = cn1 || cn2 || 'col s12';
|
|
3815
|
+
// Format time value for display
|
|
3816
|
+
const formatTime = (hours, minutes, use12Hour) => {
|
|
3817
|
+
if (use12Hour) {
|
|
3818
|
+
const displayHours = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
|
3819
|
+
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
3820
|
+
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${ampm}`;
|
|
3821
|
+
}
|
|
3822
|
+
else {
|
|
3823
|
+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3826
|
+
const setTime = (timeValue) => {
|
|
3827
|
+
if (onchange) {
|
|
3828
|
+
onchange(timeValue);
|
|
3829
|
+
}
|
|
3830
|
+
};
|
|
3831
|
+
// Calculate display hours based on format
|
|
3832
|
+
let hoursForDisplay = state.hours;
|
|
3833
|
+
if (twelveHour) {
|
|
3834
|
+
// For 12-hour display format, use the original 24-hour value so formatTime can properly determine AM/PM
|
|
3835
|
+
if (options.twelveHour) {
|
|
3836
|
+
// Convert from internal 12-hour back to 24-hour for proper AM/PM calculation
|
|
3837
|
+
if (state.amOrPm === 'PM' && state.hours !== 12) {
|
|
3838
|
+
hoursForDisplay = state.hours + 12;
|
|
3839
|
+
}
|
|
3840
|
+
else if (state.amOrPm === 'AM' && state.hours === 12) {
|
|
3841
|
+
hoursForDisplay = 0;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
else {
|
|
3846
|
+
// For 24-hour display format
|
|
3847
|
+
if (options.twelveHour) {
|
|
3848
|
+
// Convert from internal 12-hour to 24-hour for display
|
|
3849
|
+
if (state.amOrPm === 'PM' && state.hours !== 12) {
|
|
3850
|
+
hoursForDisplay = state.hours + 12;
|
|
3851
|
+
}
|
|
3852
|
+
else if (state.amOrPm === 'AM' && state.hours === 12) {
|
|
3853
|
+
hoursForDisplay = 0;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
const displayValue = state.hours !== undefined && state.minutes !== undefined
|
|
3858
|
+
? formatTime(hoursForDisplay, state.minutes, twelveHour || false)
|
|
3859
|
+
: '';
|
|
3860
|
+
return m('.input-field', { className }, [
|
|
3249
3861
|
// Icon prefix
|
|
3250
3862
|
iconName && m('i.material-icons.prefix', iconName),
|
|
3251
|
-
// Time input field
|
|
3863
|
+
// Time input field - use HTML5 time input for inline mode
|
|
3252
3864
|
m('input.timepicker', {
|
|
3253
3865
|
id,
|
|
3254
|
-
type: 'text',
|
|
3255
|
-
value:
|
|
3256
|
-
|
|
3866
|
+
type: useModal ? 'text' : 'time',
|
|
3867
|
+
value: useModal
|
|
3868
|
+
? displayValue
|
|
3869
|
+
: state.hours !== undefined && state.minutes !== undefined
|
|
3870
|
+
? `${state.hours.toString().padStart(2, '0')}:${state.minutes.toString().padStart(2, '0')}`
|
|
3871
|
+
: '',
|
|
3872
|
+
placeholder: useModal ? placeholder : undefined,
|
|
3257
3873
|
disabled,
|
|
3258
3874
|
readonly,
|
|
3259
3875
|
required,
|
|
3260
3876
|
onclick: () => {
|
|
3261
3877
|
if (!disabled && !readonly && useModal) {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3878
|
+
open(displayValue);
|
|
3879
|
+
}
|
|
3880
|
+
},
|
|
3881
|
+
onchange: (e) => {
|
|
3882
|
+
if (!useModal) {
|
|
3883
|
+
// For inline mode, handle HTML5 time input changes directly
|
|
3884
|
+
const target = e.target;
|
|
3885
|
+
const timeValue = target.value; // Already in HH:MM format
|
|
3886
|
+
const [hours, minutes] = timeValue.split(':').map(Number);
|
|
3887
|
+
state.hours = hours;
|
|
3888
|
+
state.minutes = minutes;
|
|
3889
|
+
setTime(timeValue);
|
|
3890
|
+
}
|
|
3891
|
+
},
|
|
3892
|
+
oninput: (e) => {
|
|
3893
|
+
if (!useModal && oninput) {
|
|
3894
|
+
const target = e.target;
|
|
3895
|
+
oninput(target.value);
|
|
3265
3896
|
}
|
|
3266
3897
|
},
|
|
3267
3898
|
}),
|
|
@@ -3273,150 +3904,161 @@
|
|
|
3273
3904
|
}, label),
|
|
3274
3905
|
// Helper text
|
|
3275
3906
|
helperText && m('span.helper-text', helperText),
|
|
3276
|
-
//
|
|
3907
|
+
// Modal timepicker
|
|
3277
3908
|
useModal &&
|
|
3278
|
-
state.isOpen &&
|
|
3279
|
-
|
|
3909
|
+
state.isOpen && [
|
|
3910
|
+
// Modal overlay
|
|
3911
|
+
m('.modal-overlay', {
|
|
3280
3912
|
style: {
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
transform: 'translate(-50%, -50%)',
|
|
3285
|
-
backgroundColor: 'white',
|
|
3286
|
-
padding: '20px',
|
|
3287
|
-
borderRadius: '4px',
|
|
3288
|
-
boxShadow: '0 4px 20px rgba(0,0,0,0.3)',
|
|
3289
|
-
zIndex: 1000,
|
|
3913
|
+
zIndex: 1002,
|
|
3914
|
+
display: 'block',
|
|
3915
|
+
opacity: 0.5,
|
|
3290
3916
|
},
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
}, [
|
|
3296
|
-
// Hours input
|
|
3297
|
-
m('input', {
|
|
3298
|
-
type: 'number',
|
|
3299
|
-
min: state.use12Hour ? 1 : 0,
|
|
3300
|
-
max: state.use12Hour ? 12 : 23,
|
|
3301
|
-
value: state.use12Hour
|
|
3302
|
-
? state.hours === 0
|
|
3303
|
-
? 12
|
|
3304
|
-
: state.hours > 12
|
|
3305
|
-
? state.hours - 12
|
|
3306
|
-
: state.hours
|
|
3307
|
-
: state.hours,
|
|
3308
|
-
style: { width: '60px', textAlign: 'center', padding: '8px' },
|
|
3309
|
-
onchange: (e) => {
|
|
3310
|
-
const target = e.target;
|
|
3311
|
-
let hours = parseInt(target.value) || 0;
|
|
3312
|
-
if (state.use12Hour) {
|
|
3313
|
-
if (state.ampm === 'PM' && hours !== 12)
|
|
3314
|
-
hours += 12;
|
|
3315
|
-
if (state.ampm === 'AM' && hours === 12)
|
|
3316
|
-
hours = 0;
|
|
3317
|
-
}
|
|
3318
|
-
state.hours = hours;
|
|
3319
|
-
state.time = formatTime(state.hours, state.minutes, state.use12Hour);
|
|
3320
|
-
},
|
|
3321
|
-
}),
|
|
3322
|
-
m('span', ':'),
|
|
3323
|
-
// Minutes input
|
|
3324
|
-
m('input', {
|
|
3325
|
-
type: 'number',
|
|
3326
|
-
min: 0,
|
|
3327
|
-
max: 59,
|
|
3328
|
-
value: state.minutes,
|
|
3329
|
-
style: { width: '60px', textAlign: 'center', padding: '8px' },
|
|
3330
|
-
onchange: (e) => {
|
|
3331
|
-
const target = e.target;
|
|
3332
|
-
state.minutes = parseInt(target.value) || 0;
|
|
3333
|
-
state.time = formatTime(state.hours, state.minutes, state.use12Hour);
|
|
3334
|
-
},
|
|
3335
|
-
}),
|
|
3336
|
-
// AM/PM toggle for 12-hour format
|
|
3337
|
-
state.use12Hour &&
|
|
3338
|
-
m('select', {
|
|
3339
|
-
value: state.ampm,
|
|
3340
|
-
style: { padding: '8px' },
|
|
3341
|
-
onchange: (e) => {
|
|
3342
|
-
const target = e.target;
|
|
3343
|
-
const oldAmpm = state.ampm;
|
|
3344
|
-
state.ampm = target.value;
|
|
3345
|
-
// Adjust hours when switching AM/PM
|
|
3346
|
-
if (oldAmpm !== state.ampm) {
|
|
3347
|
-
if (state.ampm === 'PM' && state.hours < 12) {
|
|
3348
|
-
state.hours += 12;
|
|
3349
|
-
}
|
|
3350
|
-
else if (state.ampm === 'AM' && state.hours >= 12) {
|
|
3351
|
-
state.hours -= 12;
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
state.time = formatTime(state.hours, state.minutes, state.use12Hour);
|
|
3355
|
-
},
|
|
3356
|
-
}, [m('option', { value: 'AM' }, amLabel), m('option', { value: 'PM' }, pmLabel)]),
|
|
3357
|
-
]),
|
|
3358
|
-
// Action buttons
|
|
3359
|
-
m('.timepicker-actions', {
|
|
3360
|
-
style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' },
|
|
3361
|
-
}, [
|
|
3362
|
-
showClearBtn &&
|
|
3363
|
-
m('button.btn-flat', {
|
|
3364
|
-
onclick: () => {
|
|
3365
|
-
setTime('', attrs);
|
|
3366
|
-
state.isOpen = false;
|
|
3367
|
-
},
|
|
3368
|
-
}, clearLabel),
|
|
3369
|
-
showNowBtn &&
|
|
3370
|
-
m('button.btn-flat', {
|
|
3371
|
-
onclick: () => {
|
|
3372
|
-
const now = new Date();
|
|
3373
|
-
state.hours = now.getHours();
|
|
3374
|
-
state.minutes = now.getMinutes();
|
|
3375
|
-
state.ampm = state.hours >= 12 ? 'PM' : 'AM';
|
|
3376
|
-
state.time = formatTime(state.hours, state.minutes, state.use12Hour);
|
|
3377
|
-
},
|
|
3378
|
-
}, nowLabel),
|
|
3379
|
-
m('button.btn-flat', {
|
|
3380
|
-
onclick: () => {
|
|
3381
|
-
state.isOpen = false;
|
|
3382
|
-
if (attrs.onClose)
|
|
3383
|
-
attrs.onClose();
|
|
3384
|
-
},
|
|
3385
|
-
}, closeLabel),
|
|
3386
|
-
m('button.btn-flat', {
|
|
3387
|
-
onclick: () => {
|
|
3388
|
-
setTime(state.time, attrs);
|
|
3389
|
-
state.isOpen = false;
|
|
3390
|
-
if (attrs.onClose)
|
|
3391
|
-
attrs.onClose();
|
|
3392
|
-
},
|
|
3393
|
-
}, 'OK'),
|
|
3394
|
-
]),
|
|
3395
|
-
]),
|
|
3396
|
-
// Modal backdrop
|
|
3397
|
-
useModal &&
|
|
3398
|
-
state.isOpen &&
|
|
3399
|
-
m('.modal-backdrop', {
|
|
3917
|
+
onclick: () => close(),
|
|
3918
|
+
}),
|
|
3919
|
+
// Modal content
|
|
3920
|
+
m('.modal.timepicker-modal.open', {
|
|
3400
3921
|
style: {
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
3407
|
-
zIndex: 999,
|
|
3408
|
-
},
|
|
3409
|
-
onclick: () => {
|
|
3410
|
-
state.isOpen = false;
|
|
3411
|
-
if (attrs.onClose)
|
|
3412
|
-
attrs.onClose();
|
|
3922
|
+
zIndex: 1003,
|
|
3923
|
+
display: 'block',
|
|
3924
|
+
opacity: 1,
|
|
3925
|
+
top: '10%',
|
|
3926
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
3413
3927
|
},
|
|
3414
|
-
}),
|
|
3928
|
+
}, m(TimepickerModal, { showClearBtn, clearLabel, closeLabel, doneLabel: 'OK' })),
|
|
3929
|
+
],
|
|
3415
3930
|
]);
|
|
3416
3931
|
},
|
|
3417
3932
|
};
|
|
3418
3933
|
};
|
|
3419
3934
|
|
|
3935
|
+
class Pushpin {
|
|
3936
|
+
constructor(el, options = {}) {
|
|
3937
|
+
this.el = el;
|
|
3938
|
+
this.options = Object.assign(Object.assign({}, Pushpin.defaults), options);
|
|
3939
|
+
this.state = {
|
|
3940
|
+
originalOffset: this.el.getBoundingClientRect().top + window.pageYOffset,
|
|
3941
|
+
};
|
|
3942
|
+
this.el.M_Pushpin = this;
|
|
3943
|
+
this._setupEventHandlers();
|
|
3944
|
+
this._updateElementPosition();
|
|
3945
|
+
}
|
|
3946
|
+
static getInstance(el) {
|
|
3947
|
+
return el.M_Pushpin;
|
|
3948
|
+
}
|
|
3949
|
+
destroy() {
|
|
3950
|
+
this.el.style.position = '';
|
|
3951
|
+
this.el.style.top = '';
|
|
3952
|
+
this.el.style.left = '';
|
|
3953
|
+
this._removeEventHandlers();
|
|
3954
|
+
this.el.M_Pushpin = undefined;
|
|
3955
|
+
}
|
|
3956
|
+
_setupEventHandlers() {
|
|
3957
|
+
this._updateElementPositionBound = this._updateElementPosition.bind(this);
|
|
3958
|
+
window.addEventListener('scroll', this._updateElementPositionBound);
|
|
3959
|
+
window.addEventListener('resize', this._updateElementPositionBound);
|
|
3960
|
+
}
|
|
3961
|
+
_removeEventHandlers() {
|
|
3962
|
+
window.removeEventListener('scroll', this._updateElementPositionBound);
|
|
3963
|
+
window.removeEventListener('resize', this._updateElementPositionBound);
|
|
3964
|
+
}
|
|
3965
|
+
_updateElementPosition() {
|
|
3966
|
+
const scrolled = window.pageYOffset;
|
|
3967
|
+
const elementTop = this.state.originalOffset - this.options.offset;
|
|
3968
|
+
// const elementBottom = elementTop + this.el.offsetHeight;
|
|
3969
|
+
// Check if element should be pinned
|
|
3970
|
+
if (scrolled > elementTop) {
|
|
3971
|
+
// Check if element is past bottom
|
|
3972
|
+
if (this.options.bottom !== Infinity && scrolled > this.options.bottom) {
|
|
3973
|
+
this._removePinClasses();
|
|
3974
|
+
this.el.classList.add('pin-bottom');
|
|
3975
|
+
this.el.style.position = 'absolute';
|
|
3976
|
+
this.el.style.top = this.options.bottom - this.el.offsetHeight + 'px';
|
|
3977
|
+
this.el.style.left = '';
|
|
3978
|
+
if (this.options.onPositionChange) {
|
|
3979
|
+
this.options.onPositionChange('pin-bottom');
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
else {
|
|
3983
|
+
// Pin element
|
|
3984
|
+
this._removePinClasses();
|
|
3985
|
+
this.el.classList.add('pinned');
|
|
3986
|
+
this.el.style.position = 'fixed';
|
|
3987
|
+
this.el.style.top = this.options.top + 'px';
|
|
3988
|
+
this.el.style.left = this.el.getBoundingClientRect().left + 'px';
|
|
3989
|
+
if (this.options.onPositionChange) {
|
|
3990
|
+
this.options.onPositionChange('pinned');
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
// Unpin element
|
|
3996
|
+
this._removePinClasses();
|
|
3997
|
+
this.el.classList.add('pin-top');
|
|
3998
|
+
this.el.style.position = '';
|
|
3999
|
+
this.el.style.top = '';
|
|
4000
|
+
this.el.style.left = '';
|
|
4001
|
+
if (this.options.onPositionChange) {
|
|
4002
|
+
this.options.onPositionChange('pin-top');
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
_removePinClasses() {
|
|
4007
|
+
this.el.classList.remove('pin-top');
|
|
4008
|
+
this.el.classList.remove('pinned');
|
|
4009
|
+
this.el.classList.remove('pin-bottom');
|
|
4010
|
+
}
|
|
4011
|
+
_updatePosition() {
|
|
4012
|
+
// Recalculate original offset in case element moved
|
|
4013
|
+
this.state.originalOffset = this.el.getBoundingClientRect().top + window.pageYOffset;
|
|
4014
|
+
this._updateElementPosition();
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
Pushpin.defaults = {
|
|
4018
|
+
top: 0,
|
|
4019
|
+
bottom: Infinity,
|
|
4020
|
+
offset: 0,
|
|
4021
|
+
onPositionChange: undefined,
|
|
4022
|
+
};
|
|
4023
|
+
const PushpinComponent = () => {
|
|
4024
|
+
let pushpinInstance = null;
|
|
4025
|
+
return {
|
|
4026
|
+
oncreate: ({ attrs }) => {
|
|
4027
|
+
if (attrs.targetSelector) {
|
|
4028
|
+
const targetEl = document.querySelector(attrs.targetSelector);
|
|
4029
|
+
if (targetEl) {
|
|
4030
|
+
pushpinInstance = new Pushpin(targetEl, attrs);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
},
|
|
4034
|
+
onupdate: ({ attrs }) => {
|
|
4035
|
+
if (pushpinInstance) {
|
|
4036
|
+
// Update options and recalculate position
|
|
4037
|
+
pushpinInstance.options = Object.assign(Object.assign({}, pushpinInstance.options), attrs);
|
|
4038
|
+
pushpinInstance._updatePosition();
|
|
4039
|
+
}
|
|
4040
|
+
},
|
|
4041
|
+
onremove: () => {
|
|
4042
|
+
if (pushpinInstance) {
|
|
4043
|
+
pushpinInstance.destroy();
|
|
4044
|
+
pushpinInstance = null;
|
|
4045
|
+
}
|
|
4046
|
+
},
|
|
4047
|
+
view: () => null, // This component doesn't render anything itself
|
|
4048
|
+
};
|
|
4049
|
+
};
|
|
4050
|
+
// Helper function to initialize pushpins on elements
|
|
4051
|
+
const initPushpins = (selector = '.pushpin', options = {}) => {
|
|
4052
|
+
const elements = document.querySelectorAll(selector);
|
|
4053
|
+
const pushpins = [];
|
|
4054
|
+
elements.forEach((el) => {
|
|
4055
|
+
if (!el.M_Pushpin) {
|
|
4056
|
+
pushpins.push(new Pushpin(el, options));
|
|
4057
|
+
}
|
|
4058
|
+
});
|
|
4059
|
+
return pushpins;
|
|
4060
|
+
};
|
|
4061
|
+
|
|
3420
4062
|
const RadioButton = () => ({
|
|
3421
4063
|
view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId } }) => {
|
|
3422
4064
|
const radioId = inputId || `${groupId}-${id}`;
|
|
@@ -3699,6 +4341,7 @@
|
|
|
3699
4341
|
m(MaterialIcon, {
|
|
3700
4342
|
name: 'caret',
|
|
3701
4343
|
direction: 'down',
|
|
4344
|
+
class: 'caret',
|
|
3702
4345
|
}),
|
|
3703
4346
|
]),
|
|
3704
4347
|
// Label
|
|
@@ -4106,6 +4749,7 @@
|
|
|
4106
4749
|
m(MaterialIcon, {
|
|
4107
4750
|
name: 'caret',
|
|
4108
4751
|
direction: state.isOpen ? 'up' : 'down',
|
|
4752
|
+
class: 'caret',
|
|
4109
4753
|
style: { marginLeft: 'auto', cursor: 'pointer' },
|
|
4110
4754
|
}),
|
|
4111
4755
|
]),
|
|
@@ -4212,6 +4856,493 @@
|
|
|
4212
4856
|
};
|
|
4213
4857
|
};
|
|
4214
4858
|
|
|
4859
|
+
class Toast {
|
|
4860
|
+
constructor(options = {}) {
|
|
4861
|
+
this.options = Object.assign(Object.assign({}, Toast.defaults), options);
|
|
4862
|
+
this.state = {
|
|
4863
|
+
panning: false,
|
|
4864
|
+
timeRemaining: this.options.displayLength,
|
|
4865
|
+
startingXPos: 0,
|
|
4866
|
+
xPos: 0,
|
|
4867
|
+
velocityX: 0,
|
|
4868
|
+
time: 0,
|
|
4869
|
+
deltaX: 0,
|
|
4870
|
+
wasSwiped: false,
|
|
4871
|
+
};
|
|
4872
|
+
if (Toast._toasts.length === 0) {
|
|
4873
|
+
Toast._createContainer();
|
|
4874
|
+
}
|
|
4875
|
+
// Create new toast
|
|
4876
|
+
Toast._toasts.push(this);
|
|
4877
|
+
this.el = this._createToast();
|
|
4878
|
+
this._animateIn();
|
|
4879
|
+
this._setTimer();
|
|
4880
|
+
}
|
|
4881
|
+
static getInstance(el) {
|
|
4882
|
+
return el.M_Toast;
|
|
4883
|
+
}
|
|
4884
|
+
static _createContainer() {
|
|
4885
|
+
const container = document.createElement('div');
|
|
4886
|
+
container.setAttribute('id', 'toast-container');
|
|
4887
|
+
// Add event handlers
|
|
4888
|
+
container.addEventListener('touchstart', Toast._onDragStart);
|
|
4889
|
+
container.addEventListener('touchmove', Toast._onDragMove);
|
|
4890
|
+
container.addEventListener('touchend', Toast._onDragEnd);
|
|
4891
|
+
container.addEventListener('mousedown', Toast._onDragStart);
|
|
4892
|
+
document.addEventListener('mousemove', Toast._onDragMove);
|
|
4893
|
+
document.addEventListener('mouseup', Toast._onDragEnd);
|
|
4894
|
+
document.body.appendChild(container);
|
|
4895
|
+
Toast._container = container;
|
|
4896
|
+
}
|
|
4897
|
+
static _removeContainer() {
|
|
4898
|
+
document.removeEventListener('mousemove', Toast._onDragMove);
|
|
4899
|
+
document.removeEventListener('mouseup', Toast._onDragEnd);
|
|
4900
|
+
if (Toast._container) {
|
|
4901
|
+
Toast._container.remove();
|
|
4902
|
+
Toast._container = null;
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
static _xPos(e) {
|
|
4906
|
+
const touchEvent = e;
|
|
4907
|
+
const mouseEvent = e;
|
|
4908
|
+
if (touchEvent.targetTouches && touchEvent.targetTouches.length >= 1) {
|
|
4909
|
+
return touchEvent.targetTouches[0].clientX;
|
|
4910
|
+
}
|
|
4911
|
+
return mouseEvent.clientX;
|
|
4912
|
+
}
|
|
4913
|
+
static dismissAll() {
|
|
4914
|
+
Toast._toasts.forEach((toast) => toast.dismiss());
|
|
4915
|
+
}
|
|
4916
|
+
_createToast() {
|
|
4917
|
+
const toast = document.createElement('div');
|
|
4918
|
+
toast.classList.add('toast');
|
|
4919
|
+
// Add custom classes
|
|
4920
|
+
if (this.options.classes) {
|
|
4921
|
+
toast.classList.add(...this.options.classes.split(' '));
|
|
4922
|
+
}
|
|
4923
|
+
// Set content
|
|
4924
|
+
const message = this.options.html;
|
|
4925
|
+
if (typeof message === 'object' && message && 'nodeType' in message) {
|
|
4926
|
+
toast.appendChild(message);
|
|
4927
|
+
}
|
|
4928
|
+
else {
|
|
4929
|
+
toast.innerHTML = message;
|
|
4930
|
+
}
|
|
4931
|
+
// Store reference
|
|
4932
|
+
toast.M_Toast = this;
|
|
4933
|
+
// Append to container
|
|
4934
|
+
Toast._container.appendChild(toast);
|
|
4935
|
+
return toast;
|
|
4936
|
+
}
|
|
4937
|
+
_animateIn() {
|
|
4938
|
+
// Simple CSS animation since we don't have anime.js
|
|
4939
|
+
this.el.style.cssText = `
|
|
4940
|
+
transform: translateY(35px);
|
|
4941
|
+
opacity: 0;
|
|
4942
|
+
transition: transform ${this.options.inDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1),
|
|
4943
|
+
opacity ${this.options.inDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
4944
|
+
`;
|
|
4945
|
+
// Trigger animation
|
|
4946
|
+
setTimeout(() => {
|
|
4947
|
+
this.el.style.transform = 'translateY(0)';
|
|
4948
|
+
this.el.style.opacity = '1';
|
|
4949
|
+
}, 10);
|
|
4950
|
+
}
|
|
4951
|
+
_setTimer() {
|
|
4952
|
+
if (this.state.timeRemaining !== Infinity) {
|
|
4953
|
+
this.state.counterInterval = window.setInterval(() => {
|
|
4954
|
+
if (!this.state.panning) {
|
|
4955
|
+
this.state.timeRemaining -= 20;
|
|
4956
|
+
}
|
|
4957
|
+
if (this.state.timeRemaining <= 0) {
|
|
4958
|
+
this.dismiss();
|
|
4959
|
+
}
|
|
4960
|
+
}, 20);
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
dismiss() {
|
|
4964
|
+
if (this.state.counterInterval) {
|
|
4965
|
+
window.clearInterval(this.state.counterInterval);
|
|
4966
|
+
}
|
|
4967
|
+
const activationDistance = this.el.offsetWidth * this.options.activationPercent;
|
|
4968
|
+
if (this.state.wasSwiped) {
|
|
4969
|
+
this.el.style.transition = 'transform .05s, opacity .05s';
|
|
4970
|
+
this.el.style.transform = `translateX(${activationDistance}px)`;
|
|
4971
|
+
this.el.style.opacity = '0';
|
|
4972
|
+
}
|
|
4973
|
+
// Animate out
|
|
4974
|
+
this.el.style.cssText += `
|
|
4975
|
+
transition: opacity ${this.options.outDuration}ms cubic-bezier(0.165, 0.84, 0.44, 1),
|
|
4976
|
+
margin-top ${this.options.outDuration}ms cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
4977
|
+
opacity: 0;
|
|
4978
|
+
margin-top: -40px;
|
|
4979
|
+
`;
|
|
4980
|
+
setTimeout(() => {
|
|
4981
|
+
// Call completion callback
|
|
4982
|
+
if (this.options.completeCallback) {
|
|
4983
|
+
this.options.completeCallback();
|
|
4984
|
+
}
|
|
4985
|
+
// Remove toast from DOM
|
|
4986
|
+
this.el.remove();
|
|
4987
|
+
// Remove from toasts array
|
|
4988
|
+
const index = Toast._toasts.indexOf(this);
|
|
4989
|
+
if (index > -1) {
|
|
4990
|
+
Toast._toasts.splice(index, 1);
|
|
4991
|
+
}
|
|
4992
|
+
// Remove container if no more toasts
|
|
4993
|
+
if (Toast._toasts.length === 0) {
|
|
4994
|
+
Toast._removeContainer();
|
|
4995
|
+
}
|
|
4996
|
+
}, this.options.outDuration);
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
Toast._toasts = [];
|
|
5000
|
+
Toast._container = null;
|
|
5001
|
+
Toast._draggedToast = null;
|
|
5002
|
+
Toast.defaults = {
|
|
5003
|
+
html: '',
|
|
5004
|
+
displayLength: 4000,
|
|
5005
|
+
inDuration: 300,
|
|
5006
|
+
outDuration: 375,
|
|
5007
|
+
classes: '',
|
|
5008
|
+
completeCallback: undefined,
|
|
5009
|
+
activationPercent: 0.8,
|
|
5010
|
+
};
|
|
5011
|
+
Toast._onDragStart = (e) => {
|
|
5012
|
+
const target = e.target;
|
|
5013
|
+
const toastEl = target.closest('.toast');
|
|
5014
|
+
if (toastEl) {
|
|
5015
|
+
const toast = toastEl.M_Toast;
|
|
5016
|
+
if (toast) {
|
|
5017
|
+
toast.state.panning = true;
|
|
5018
|
+
Toast._draggedToast = toast;
|
|
5019
|
+
toast.el.classList.add('panning');
|
|
5020
|
+
toast.el.style.transition = '';
|
|
5021
|
+
toast.state.startingXPos = Toast._xPos(e);
|
|
5022
|
+
toast.state.time = Date.now();
|
|
5023
|
+
toast.state.xPos = Toast._xPos(e);
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
};
|
|
5027
|
+
Toast._onDragMove = (e) => {
|
|
5028
|
+
if (Toast._draggedToast) {
|
|
5029
|
+
e.preventDefault();
|
|
5030
|
+
const toast = Toast._draggedToast;
|
|
5031
|
+
toast.state.deltaX = Math.abs(toast.state.xPos - Toast._xPos(e));
|
|
5032
|
+
toast.state.xPos = Toast._xPos(e);
|
|
5033
|
+
toast.state.velocityX = toast.state.deltaX / (Date.now() - toast.state.time);
|
|
5034
|
+
toast.state.time = Date.now();
|
|
5035
|
+
const totalDeltaX = toast.state.xPos - toast.state.startingXPos;
|
|
5036
|
+
const activationDistance = toast.el.offsetWidth * toast.options.activationPercent;
|
|
5037
|
+
toast.el.style.transform = `translateX(${totalDeltaX}px)`;
|
|
5038
|
+
toast.el.style.opacity = String(1 - Math.abs(totalDeltaX / activationDistance));
|
|
5039
|
+
}
|
|
5040
|
+
};
|
|
5041
|
+
Toast._onDragEnd = () => {
|
|
5042
|
+
if (Toast._draggedToast) {
|
|
5043
|
+
const toast = Toast._draggedToast;
|
|
5044
|
+
toast.state.panning = false;
|
|
5045
|
+
toast.el.classList.remove('panning');
|
|
5046
|
+
const totalDeltaX = toast.state.xPos - toast.state.startingXPos;
|
|
5047
|
+
const activationDistance = toast.el.offsetWidth * toast.options.activationPercent;
|
|
5048
|
+
const shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || toast.state.velocityX > 1;
|
|
5049
|
+
if (shouldBeDismissed) {
|
|
5050
|
+
toast.state.wasSwiped = true;
|
|
5051
|
+
toast.dismiss();
|
|
5052
|
+
}
|
|
5053
|
+
else {
|
|
5054
|
+
toast.el.style.transition = 'transform .2s, opacity .2s';
|
|
5055
|
+
toast.el.style.transform = '';
|
|
5056
|
+
toast.el.style.opacity = '';
|
|
5057
|
+
}
|
|
5058
|
+
Toast._draggedToast = null;
|
|
5059
|
+
}
|
|
5060
|
+
};
|
|
5061
|
+
// Factory function for creating toasts
|
|
5062
|
+
const toast = (options) => {
|
|
5063
|
+
return new Toast(options);
|
|
5064
|
+
};
|
|
5065
|
+
const ToastComponent = () => {
|
|
5066
|
+
let toastInstance = null;
|
|
5067
|
+
return {
|
|
5068
|
+
view: ({ attrs }) => {
|
|
5069
|
+
if (attrs.show && !toastInstance) {
|
|
5070
|
+
toastInstance = new Toast(attrs);
|
|
5071
|
+
}
|
|
5072
|
+
else if (!attrs.show && toastInstance) {
|
|
5073
|
+
toastInstance.dismiss();
|
|
5074
|
+
toastInstance = null;
|
|
5075
|
+
}
|
|
5076
|
+
return null; // This component doesn't render anything itself
|
|
5077
|
+
},
|
|
5078
|
+
onremove: () => {
|
|
5079
|
+
if (toastInstance) {
|
|
5080
|
+
toastInstance.dismiss();
|
|
5081
|
+
toastInstance = null;
|
|
5082
|
+
}
|
|
5083
|
+
},
|
|
5084
|
+
};
|
|
5085
|
+
};
|
|
5086
|
+
|
|
5087
|
+
class Tooltip {
|
|
5088
|
+
constructor(el, options = {}) {
|
|
5089
|
+
this.el = el;
|
|
5090
|
+
this.options = Object.assign(Object.assign({}, Tooltip.defaults), options);
|
|
5091
|
+
this.state = {
|
|
5092
|
+
isOpen: false,
|
|
5093
|
+
isHovered: false,
|
|
5094
|
+
isFocused: false,
|
|
5095
|
+
xMovement: 0,
|
|
5096
|
+
yMovement: 0,
|
|
5097
|
+
};
|
|
5098
|
+
this.el.M_Tooltip = this;
|
|
5099
|
+
this._appendTooltipEl();
|
|
5100
|
+
this._setupEventHandlers();
|
|
5101
|
+
}
|
|
5102
|
+
static getInstance(el) {
|
|
5103
|
+
return el.M_Tooltip;
|
|
5104
|
+
}
|
|
5105
|
+
destroy() {
|
|
5106
|
+
this.tooltipEl.remove();
|
|
5107
|
+
this._removeEventHandlers();
|
|
5108
|
+
this.el.M_Tooltip = undefined;
|
|
5109
|
+
}
|
|
5110
|
+
_appendTooltipEl() {
|
|
5111
|
+
const tooltipEl = document.createElement('div');
|
|
5112
|
+
tooltipEl.classList.add('material-tooltip');
|
|
5113
|
+
this.tooltipEl = tooltipEl;
|
|
5114
|
+
const tooltipContentEl = document.createElement('div');
|
|
5115
|
+
tooltipContentEl.classList.add('tooltip-content');
|
|
5116
|
+
tooltipContentEl.innerHTML = this.options.html || '';
|
|
5117
|
+
tooltipEl.appendChild(tooltipContentEl);
|
|
5118
|
+
document.body.appendChild(tooltipEl);
|
|
5119
|
+
}
|
|
5120
|
+
_updateTooltipContent() {
|
|
5121
|
+
const contentEl = this.tooltipEl.querySelector('.tooltip-content');
|
|
5122
|
+
if (contentEl) {
|
|
5123
|
+
contentEl.innerHTML = this.options.html || '';
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
_setupEventHandlers() {
|
|
5127
|
+
this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
|
|
5128
|
+
this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
|
|
5129
|
+
this._handleFocusBound = this._handleFocus.bind(this);
|
|
5130
|
+
this._handleBlurBound = this._handleBlur.bind(this);
|
|
5131
|
+
this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
|
|
5132
|
+
this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
|
|
5133
|
+
this.el.addEventListener('focus', this._handleFocusBound, true);
|
|
5134
|
+
this.el.addEventListener('blur', this._handleBlurBound, true);
|
|
5135
|
+
}
|
|
5136
|
+
_removeEventHandlers() {
|
|
5137
|
+
this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
|
|
5138
|
+
this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
|
|
5139
|
+
this.el.removeEventListener('focus', this._handleFocusBound, true);
|
|
5140
|
+
this.el.removeEventListener('blur', this._handleBlurBound, true);
|
|
5141
|
+
}
|
|
5142
|
+
open(isManual = true) {
|
|
5143
|
+
if (this.state.isOpen) {
|
|
5144
|
+
return;
|
|
5145
|
+
}
|
|
5146
|
+
this.state.isOpen = true;
|
|
5147
|
+
// Update tooltip content with data attributes
|
|
5148
|
+
this.options = Object.assign(Object.assign({}, this.options), this._getAttributeOptions());
|
|
5149
|
+
this._updateTooltipContent();
|
|
5150
|
+
this._setEnterDelayTimeout(isManual);
|
|
5151
|
+
}
|
|
5152
|
+
close() {
|
|
5153
|
+
if (!this.state.isOpen) {
|
|
5154
|
+
return;
|
|
5155
|
+
}
|
|
5156
|
+
this.state.isHovered = false;
|
|
5157
|
+
this.state.isFocused = false;
|
|
5158
|
+
this.state.isOpen = false;
|
|
5159
|
+
this._setExitDelayTimeout();
|
|
5160
|
+
}
|
|
5161
|
+
_setExitDelayTimeout() {
|
|
5162
|
+
if (this.state.exitDelayTimeout) {
|
|
5163
|
+
clearTimeout(this.state.exitDelayTimeout);
|
|
5164
|
+
}
|
|
5165
|
+
this.state.exitDelayTimeout = window.setTimeout(() => {
|
|
5166
|
+
if (this.state.isHovered || this.state.isFocused) {
|
|
5167
|
+
return;
|
|
5168
|
+
}
|
|
5169
|
+
this._animateOut();
|
|
5170
|
+
}, this.options.exitDelay);
|
|
5171
|
+
}
|
|
5172
|
+
_setEnterDelayTimeout(isManual) {
|
|
5173
|
+
if (this.state.enterDelayTimeout) {
|
|
5174
|
+
clearTimeout(this.state.enterDelayTimeout);
|
|
5175
|
+
}
|
|
5176
|
+
this.state.enterDelayTimeout = window.setTimeout(() => {
|
|
5177
|
+
if (!this.state.isHovered && !this.state.isFocused && !isManual) {
|
|
5178
|
+
return;
|
|
5179
|
+
}
|
|
5180
|
+
this._animateIn();
|
|
5181
|
+
}, this.options.enterDelay);
|
|
5182
|
+
}
|
|
5183
|
+
_positionTooltip() {
|
|
5184
|
+
const origin = this.el;
|
|
5185
|
+
const tooltip = this.tooltipEl;
|
|
5186
|
+
const originHeight = origin.offsetHeight;
|
|
5187
|
+
const originWidth = origin.offsetWidth;
|
|
5188
|
+
const tooltipHeight = tooltip.offsetHeight;
|
|
5189
|
+
const tooltipWidth = tooltip.offsetWidth;
|
|
5190
|
+
const margin = this.options.margin;
|
|
5191
|
+
this.state.xMovement = 0;
|
|
5192
|
+
this.state.yMovement = 0;
|
|
5193
|
+
const originRect = origin.getBoundingClientRect();
|
|
5194
|
+
let targetTop = originRect.top + window.pageYOffset;
|
|
5195
|
+
let targetLeft = originRect.left + window.pageXOffset;
|
|
5196
|
+
switch (this.options.position) {
|
|
5197
|
+
case 'top':
|
|
5198
|
+
targetTop += -tooltipHeight - margin;
|
|
5199
|
+
targetLeft += originWidth / 2 - tooltipWidth / 2;
|
|
5200
|
+
this.state.yMovement = -this.options.transitionMovement;
|
|
5201
|
+
break;
|
|
5202
|
+
case 'right':
|
|
5203
|
+
targetTop += originHeight / 2 - tooltipHeight / 2;
|
|
5204
|
+
targetLeft += originWidth + margin;
|
|
5205
|
+
this.state.xMovement = this.options.transitionMovement;
|
|
5206
|
+
break;
|
|
5207
|
+
case 'left':
|
|
5208
|
+
targetTop += originHeight / 2 - tooltipHeight / 2;
|
|
5209
|
+
targetLeft += -tooltipWidth - margin;
|
|
5210
|
+
this.state.xMovement = -this.options.transitionMovement;
|
|
5211
|
+
break;
|
|
5212
|
+
case 'bottom':
|
|
5213
|
+
default:
|
|
5214
|
+
targetTop += originHeight + margin;
|
|
5215
|
+
targetLeft += originWidth / 2 - tooltipWidth / 2;
|
|
5216
|
+
this.state.yMovement = this.options.transitionMovement;
|
|
5217
|
+
break;
|
|
5218
|
+
}
|
|
5219
|
+
const repositioned = this._repositionWithinScreen(targetLeft, targetTop, tooltipWidth, tooltipHeight);
|
|
5220
|
+
this.tooltipEl.style.top = repositioned.y + 'px';
|
|
5221
|
+
this.tooltipEl.style.left = repositioned.x + 'px';
|
|
5222
|
+
}
|
|
5223
|
+
_repositionWithinScreen(x, y, width, height) {
|
|
5224
|
+
const scrollLeft = window.pageXOffset;
|
|
5225
|
+
const scrollTop = window.pageYOffset;
|
|
5226
|
+
let newX = x - scrollLeft;
|
|
5227
|
+
let newY = y - scrollTop;
|
|
5228
|
+
const offset = this.options.margin + this.options.transitionMovement;
|
|
5229
|
+
// Check boundaries
|
|
5230
|
+
if (newX < offset) {
|
|
5231
|
+
newX = offset;
|
|
5232
|
+
}
|
|
5233
|
+
else if (newX + width > window.innerWidth - offset) {
|
|
5234
|
+
newX = window.innerWidth - width - offset;
|
|
5235
|
+
}
|
|
5236
|
+
if (newY < offset) {
|
|
5237
|
+
newY = offset;
|
|
5238
|
+
}
|
|
5239
|
+
else if (newY + height > window.innerHeight - offset) {
|
|
5240
|
+
newY = window.innerHeight - height - offset;
|
|
5241
|
+
}
|
|
5242
|
+
return {
|
|
5243
|
+
x: newX + scrollLeft,
|
|
5244
|
+
y: newY + scrollTop,
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
5247
|
+
_animateIn() {
|
|
5248
|
+
this._positionTooltip();
|
|
5249
|
+
this.tooltipEl.style.visibility = 'visible';
|
|
5250
|
+
// CSS animation
|
|
5251
|
+
this.tooltipEl.style.cssText += `
|
|
5252
|
+
opacity: 0;
|
|
5253
|
+
transform: translate(0, 0);
|
|
5254
|
+
transition: opacity ${this.options.inDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1),
|
|
5255
|
+
transform ${this.options.inDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
5256
|
+
`;
|
|
5257
|
+
setTimeout(() => {
|
|
5258
|
+
this.tooltipEl.style.opacity = '1';
|
|
5259
|
+
this.tooltipEl.style.transform = `translate(${this.state.xMovement}px, ${this.state.yMovement}px)`;
|
|
5260
|
+
}, 10);
|
|
5261
|
+
}
|
|
5262
|
+
_animateOut() {
|
|
5263
|
+
this.tooltipEl.style.cssText += `
|
|
5264
|
+
transition: opacity ${this.options.outDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1),
|
|
5265
|
+
transform ${this.options.outDuration}ms cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
5266
|
+
opacity: 0;
|
|
5267
|
+
transform: translate(0, 0);
|
|
5268
|
+
`;
|
|
5269
|
+
setTimeout(() => {
|
|
5270
|
+
this.tooltipEl.style.visibility = 'hidden';
|
|
5271
|
+
}, this.options.outDuration);
|
|
5272
|
+
}
|
|
5273
|
+
_handleMouseEnter() {
|
|
5274
|
+
this.state.isHovered = true;
|
|
5275
|
+
this.state.isFocused = false;
|
|
5276
|
+
this.open(false);
|
|
5277
|
+
}
|
|
5278
|
+
_handleMouseLeave() {
|
|
5279
|
+
this.state.isHovered = false;
|
|
5280
|
+
this.state.isFocused = false;
|
|
5281
|
+
this.close();
|
|
5282
|
+
}
|
|
5283
|
+
_handleFocus() {
|
|
5284
|
+
this.state.isFocused = true;
|
|
5285
|
+
this.open(false);
|
|
5286
|
+
}
|
|
5287
|
+
_handleBlur() {
|
|
5288
|
+
this.state.isFocused = false;
|
|
5289
|
+
this.close();
|
|
5290
|
+
}
|
|
5291
|
+
_getAttributeOptions() {
|
|
5292
|
+
const attributeOptions = {};
|
|
5293
|
+
const tooltipText = this.el.getAttribute('data-tooltip');
|
|
5294
|
+
const position = this.el.getAttribute('data-position');
|
|
5295
|
+
if (tooltipText) {
|
|
5296
|
+
attributeOptions.html = tooltipText;
|
|
5297
|
+
}
|
|
5298
|
+
if (position && ['top', 'bottom', 'left', 'right'].includes(position)) {
|
|
5299
|
+
attributeOptions.position = position;
|
|
5300
|
+
}
|
|
5301
|
+
return attributeOptions;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
Tooltip.defaults = {
|
|
5305
|
+
exitDelay: 200,
|
|
5306
|
+
enterDelay: 0,
|
|
5307
|
+
html: null,
|
|
5308
|
+
margin: 5,
|
|
5309
|
+
inDuration: 250,
|
|
5310
|
+
outDuration: 200,
|
|
5311
|
+
position: 'bottom',
|
|
5312
|
+
transitionMovement: 10,
|
|
5313
|
+
};
|
|
5314
|
+
const TooltipComponent = () => {
|
|
5315
|
+
let tooltipInstance = null;
|
|
5316
|
+
return {
|
|
5317
|
+
oncreate: ({ attrs }) => {
|
|
5318
|
+
if (attrs.targetSelector) {
|
|
5319
|
+
const targetEl = document.querySelector(attrs.targetSelector);
|
|
5320
|
+
if (targetEl) {
|
|
5321
|
+
tooltipInstance = new Tooltip(targetEl, attrs);
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
},
|
|
5325
|
+
onremove: () => {
|
|
5326
|
+
if (tooltipInstance) {
|
|
5327
|
+
tooltipInstance.destroy();
|
|
5328
|
+
tooltipInstance = null;
|
|
5329
|
+
}
|
|
5330
|
+
},
|
|
5331
|
+
view: () => null, // This component doesn't render anything itself
|
|
5332
|
+
};
|
|
5333
|
+
};
|
|
5334
|
+
// Helper function to initialize tooltips on elements
|
|
5335
|
+
const initTooltips = (selector = '[data-tooltip]', options = {}) => {
|
|
5336
|
+
const elements = document.querySelectorAll(selector);
|
|
5337
|
+
const tooltips = [];
|
|
5338
|
+
elements.forEach((el) => {
|
|
5339
|
+
if (!el.M_Tooltip) {
|
|
5340
|
+
tooltips.push(new Tooltip(el, options));
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
5343
|
+
return tooltips;
|
|
5344
|
+
};
|
|
5345
|
+
|
|
4215
5346
|
exports.AnchorItem = AnchorItem;
|
|
4216
5347
|
exports.Autocomplete = Autocomplete;
|
|
4217
5348
|
exports.Button = Button;
|
|
@@ -4244,6 +5375,8 @@
|
|
|
4244
5375
|
exports.Pagination = Pagination;
|
|
4245
5376
|
exports.Parallax = Parallax;
|
|
4246
5377
|
exports.PasswordInput = PasswordInput;
|
|
5378
|
+
exports.Pushpin = Pushpin;
|
|
5379
|
+
exports.PushpinComponent = PushpinComponent;
|
|
4247
5380
|
exports.RadioButton = RadioButton;
|
|
4248
5381
|
exports.RadioButtons = RadioButtons;
|
|
4249
5382
|
exports.RangeInput = RangeInput;
|
|
@@ -4258,11 +5391,18 @@
|
|
|
4258
5391
|
exports.TextArea = TextArea;
|
|
4259
5392
|
exports.TextInput = TextInput;
|
|
4260
5393
|
exports.TimePicker = TimePicker;
|
|
5394
|
+
exports.Toast = Toast;
|
|
5395
|
+
exports.ToastComponent = ToastComponent;
|
|
5396
|
+
exports.Tooltip = Tooltip;
|
|
5397
|
+
exports.TooltipComponent = TooltipComponent;
|
|
4261
5398
|
exports.UrlInput = UrlInput;
|
|
4262
5399
|
exports.getDropdownStyles = getDropdownStyles;
|
|
5400
|
+
exports.initPushpins = initPushpins;
|
|
5401
|
+
exports.initTooltips = initTooltips;
|
|
4263
5402
|
exports.isNumeric = isNumeric;
|
|
4264
5403
|
exports.padLeft = padLeft;
|
|
4265
5404
|
exports.range = range;
|
|
5405
|
+
exports.toast = toast;
|
|
4266
5406
|
exports.uniqueId = uniqueId;
|
|
4267
5407
|
exports.uuid4 = uuid4;
|
|
4268
5408
|
|