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