mithril-materialized 2.0.0-beta.3 → 2.0.0-beta.5

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