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