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

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