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