mithril-materialized 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1608,7 +1608,7 @@ const Collection = () => {
1608
1608
  };
1609
1609
  };
1610
1610
 
1611
- const defaultI18n$3 = {
1611
+ const defaultI18n$4 = {
1612
1612
  cancel: 'Cancel',
1613
1613
  clear: 'Clear',
1614
1614
  done: 'Ok',
@@ -1716,9 +1716,9 @@ const DatePicker = () => {
1716
1716
  else if (attrs.displayFormat) {
1717
1717
  finalFormat = attrs.displayFormat;
1718
1718
  }
1719
- 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$3, onSelect: null, onOpen: null, onClose: null }, attrs);
1719
+ 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$4, onSelect: null, onOpen: null, onClose: null }, attrs);
1720
1720
  // Merge i18n properly
1721
- merged.i18n = Object.assign(Object.assign({}, defaultI18n$3), attrs.i18n);
1721
+ merged.i18n = Object.assign(Object.assign({}, defaultI18n$4), attrs.i18n);
1722
1722
  return merged;
1723
1723
  };
1724
1724
  const toString = (date, format) => {
@@ -4519,7 +4519,7 @@ const Dropdown = () => {
4519
4519
  opacity: 1,
4520
4520
  };
4521
4521
  };
4522
- const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
4522
+ const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
4523
4523
  if (!state.isInsideModal)
4524
4524
  return;
4525
4525
  // Clean up existing portal
@@ -4563,7 +4563,7 @@ const Dropdown = () => {
4563
4563
  // Create dropdown with proper positioning
4564
4564
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
4565
4565
  tabindex: 0,
4566
- style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
4566
+ style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
4567
4567
  oncreate: ({ dom }) => {
4568
4568
  state.dropdownRef = dom;
4569
4569
  },
@@ -4627,7 +4627,7 @@ const Dropdown = () => {
4627
4627
  state.focusedIndex = -1;
4628
4628
  handleSelection(item.id);
4629
4629
  }
4630
- });
4630
+ }, attrs.maxHeight);
4631
4631
  }
4632
4632
  return m('.dropdown-wrapper.input-field', { className, key, style }, [
4633
4633
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
@@ -5296,45 +5296,417 @@ const Parallax = () => {
5296
5296
  };
5297
5297
  };
5298
5298
 
5299
- const defaultI18n$2 = {
5300
- cancel: 'Cancel',
5301
- clear: 'Clear',
5302
- done: 'Ok',
5299
+ /**
5300
+ * Shared utility functions for TimePicker and TimeRangePicker components
5301
+ */
5302
+ const addLeadingZero = (num) => {
5303
+ return (num < 10 ? '0' : '') + num;
5303
5304
  };
5304
- const defaultOptions = {
5305
- dialRadius: 135,
5306
- outerRadius: 105,
5307
- innerRadius: 70,
5308
- tickRadius: 20,
5309
- duration: 350,
5310
- container: null,
5311
- defaultTime: 'now',
5312
- fromNow: 0,
5313
- showClearBtn: false,
5314
- i18n: defaultI18n$2,
5315
- autoClose: false,
5316
- twelveHour: true,
5317
- vibrate: true,
5318
- roundBy5: false,
5319
- onOpen: () => { },
5320
- onOpenStart: () => { },
5321
- onOpenEnd: () => { },
5322
- onCloseStart: () => { },
5323
- onCloseEnd: () => { },
5324
- onSelect: () => { },
5305
+ const parseTime = (timeStr, twelveHour) => {
5306
+ if (!timeStr) {
5307
+ return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
5308
+ }
5309
+ const parts = timeStr.trim().split(/[:\s]+/);
5310
+ if (parts.length < 2) {
5311
+ return { hours: twelveHour ? 12 : 0, minutes: 0, amOrPm: 'AM' };
5312
+ }
5313
+ let hours = parseInt(parts[0]) || 0;
5314
+ const minutes = parseInt(parts[1]) || 0;
5315
+ let amOrPm = 'AM';
5316
+ const upperStr = timeStr.toUpperCase();
5317
+ if (upperStr.includes('PM')) {
5318
+ amOrPm = 'PM';
5319
+ }
5320
+ else if (upperStr.includes('AM')) {
5321
+ amOrPm = 'AM';
5322
+ }
5323
+ if (twelveHour) {
5324
+ if (hours >= 12) {
5325
+ amOrPm = 'PM';
5326
+ if (hours > 12)
5327
+ hours -= 12;
5328
+ }
5329
+ else if (hours === 0) {
5330
+ hours = 12;
5331
+ }
5332
+ }
5333
+ return { hours, minutes, amOrPm };
5334
+ };
5335
+ const formatTime = (time, twelveHour) => {
5336
+ if (!time)
5337
+ return '';
5338
+ const { hours, minutes, amOrPm } = time;
5339
+ const hoursStr = addLeadingZero(hours);
5340
+ const minutesStr = addLeadingZero(minutes);
5341
+ if (twelveHour) {
5342
+ return `${hoursStr}:${minutesStr} ${amOrPm}`;
5343
+ }
5344
+ let hours24 = hours;
5345
+ if (amOrPm === 'PM' && hours !== 12) {
5346
+ hours24 = hours + 12;
5347
+ }
5348
+ else if (amOrPm === 'AM' && hours === 12) {
5349
+ hours24 = 0;
5350
+ }
5351
+ return `${addLeadingZero(hours24)}:${minutesStr}`;
5352
+ };
5353
+ const timeToMinutes = (time, twelveHour) => {
5354
+ let h = time.hours;
5355
+ if (twelveHour) {
5356
+ if (time.amOrPm === 'PM' && h !== 12)
5357
+ h += 12;
5358
+ if (time.amOrPm === 'AM' && h === 12)
5359
+ h = 0;
5360
+ }
5361
+ return h * 60 + time.minutes;
5362
+ };
5363
+ const generateHourOptions = (twelveHour, hourStep) => {
5364
+ const start = twelveHour ? 1 : 0;
5365
+ const end = twelveHour ? 12 : 23;
5366
+ const hours = [];
5367
+ for (let i = start; i <= end; i += hourStep) {
5368
+ hours.push(i);
5369
+ }
5370
+ // Special case for 12-hour: include 12 first
5371
+ if (twelveHour && hourStep === 1) {
5372
+ return [12, ...hours.filter((h) => h !== 12)];
5373
+ }
5374
+ return hours;
5375
+ };
5376
+ const generateMinuteOptions = (minuteStep) => {
5377
+ const minutes = [];
5378
+ for (let i = 0; i < 60; i += minuteStep) {
5379
+ minutes.push(i);
5380
+ }
5381
+ return minutes;
5382
+ };
5383
+ const isTimeDisabled = (hours, minutes, amOrPm, minTime, maxTime, twelveHour) => {
5384
+ if (!minTime && !maxTime)
5385
+ return false;
5386
+ const currentMins = timeToMinutes({ hours, minutes, amOrPm }, twelveHour || false);
5387
+ if (minTime) {
5388
+ const minParsed = parseTime(minTime, twelveHour || false);
5389
+ const minMins = timeToMinutes(minParsed, twelveHour || false);
5390
+ if (currentMins < minMins)
5391
+ return true;
5392
+ }
5393
+ if (maxTime) {
5394
+ const maxParsed = parseTime(maxTime, twelveHour || false);
5395
+ const maxMins = timeToMinutes(maxParsed, twelveHour || false);
5396
+ if (currentMins > maxMins)
5397
+ return true;
5398
+ }
5399
+ return false;
5400
+ };
5401
+ const scrollToValue = (container, index, itemHeight, animated = true) => {
5402
+ const scrollTop = index * itemHeight - (container.clientHeight / 2 - itemHeight / 2);
5403
+ if (animated) {
5404
+ container.scrollTo({ top: scrollTop, behavior: 'smooth' });
5405
+ }
5406
+ else {
5407
+ container.scrollTop = scrollTop;
5408
+ }
5409
+ };
5410
+ const snapToNearestItem = (container, itemHeight, onSnap) => {
5411
+ const scrollTop = container.scrollTop;
5412
+ const centerOffset = container.clientHeight / 2;
5413
+ const nearestIndex = Math.round((scrollTop + centerOffset - itemHeight / 2) / itemHeight);
5414
+ scrollToValue(container, nearestIndex, itemHeight, true);
5415
+ onSnap(nearestIndex);
5325
5416
  };
5417
+
5326
5418
  /**
5327
- * TimePicker component based on original Materialize CSS timepicker
5419
+ * DigitalClock component - A scrollable digital time picker
5420
+ *
5421
+ * @example
5422
+ * ```typescript
5423
+ * m(DigitalClock, {
5424
+ * hours: 10,
5425
+ * minutes: 30,
5426
+ * amOrPm: 'AM',
5427
+ * twelveHour: true,
5428
+ * minuteStep: 5,
5429
+ * onTimeChange: (hours, minutes, amOrPm) => {
5430
+ * console.log(`Time changed to ${hours}:${minutes} ${amOrPm}`);
5431
+ * }
5432
+ * })
5433
+ * ```
5328
5434
  */
5329
- const TimePicker = () => {
5330
- let state;
5331
- let options;
5332
- const addLeadingZero = (num) => {
5333
- return (num < 10 ? '0' : '') + num;
5435
+ const DigitalClock = () => {
5436
+ const ITEM_HEIGHT = 48;
5437
+ const state = {};
5438
+ return {
5439
+ view: ({ attrs }) => {
5440
+ const { hours, minutes, amOrPm, twelveHour, minuteStep = 5, hourStep = 1, minTime, maxTime, onTimeChange, spanHours, spanMinutes, spanAmPm, } = attrs;
5441
+ const hourOptions = generateHourOptions(twelveHour, hourStep);
5442
+ const minuteOptions = generateMinuteOptions(minuteStep);
5443
+ return m('.timepicker-digital-clock', [
5444
+ // Hours column
5445
+ m('.digital-clock-column', {
5446
+ oncreate: (vnode) => {
5447
+ state.hourScrollContainer = vnode.dom;
5448
+ const currentIndex = hourOptions.indexOf(hours);
5449
+ if (currentIndex >= 0) {
5450
+ scrollToValue(state.hourScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
5451
+ }
5452
+ },
5453
+ onwheel: (e) => {
5454
+ e.preventDefault();
5455
+ if (!state.hourScrollContainer)
5456
+ return;
5457
+ const delta = Math.sign(e.deltaY);
5458
+ const currentIndex = hourOptions.indexOf(hours);
5459
+ const newIndex = Math.max(0, Math.min(hourOptions.length - 1, currentIndex + delta));
5460
+ const newHour = hourOptions[newIndex];
5461
+ if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
5462
+ onTimeChange(newHour, minutes, amOrPm);
5463
+ if (spanHours) {
5464
+ spanHours.innerHTML = addLeadingZero(newHour);
5465
+ }
5466
+ scrollToValue(state.hourScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
5467
+ m.redraw();
5468
+ }
5469
+ },
5470
+ onscroll: () => {
5471
+ if (state.hourScrollTimeout) {
5472
+ clearTimeout(state.hourScrollTimeout);
5473
+ }
5474
+ state.hourScrollTimeout = window.setTimeout(() => {
5475
+ if (!state.hourScrollContainer)
5476
+ return;
5477
+ snapToNearestItem(state.hourScrollContainer, ITEM_HEIGHT, (index) => {
5478
+ const actualIndex = index - 2; // Account for padding
5479
+ if (actualIndex >= 0 && actualIndex < hourOptions.length) {
5480
+ const newHour = hourOptions[actualIndex];
5481
+ if (!isTimeDisabled(newHour, minutes, amOrPm, minTime, maxTime, twelveHour)) {
5482
+ onTimeChange(newHour, minutes, amOrPm);
5483
+ if (spanHours) {
5484
+ spanHours.innerHTML = addLeadingZero(newHour);
5485
+ }
5486
+ m.redraw();
5487
+ }
5488
+ }
5489
+ });
5490
+ }, 150);
5491
+ },
5492
+ }, [
5493
+ // Padding items for centering
5494
+ m('.digital-clock-item.padding'),
5495
+ m('.digital-clock-item.padding'),
5496
+ // Hour items
5497
+ ...hourOptions.map((hour) => {
5498
+ const disabled = isTimeDisabled(hour, minutes, amOrPm, minTime, maxTime, twelveHour);
5499
+ return m('.digital-clock-item', {
5500
+ class: `${hour === hours ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
5501
+ onclick: () => {
5502
+ if (disabled)
5503
+ return;
5504
+ onTimeChange(hour, minutes, amOrPm);
5505
+ if (spanHours) {
5506
+ spanHours.innerHTML = addLeadingZero(hour);
5507
+ }
5508
+ if (state.hourScrollContainer) {
5509
+ const index = hourOptions.indexOf(hour);
5510
+ scrollToValue(state.hourScrollContainer, index + 2, ITEM_HEIGHT, true);
5511
+ }
5512
+ m.redraw();
5513
+ },
5514
+ }, addLeadingZero(hour));
5515
+ }),
5516
+ // Padding items for centering
5517
+ m('.digital-clock-item.padding'),
5518
+ m('.digital-clock-item.padding'),
5519
+ ]),
5520
+ // Separator
5521
+ m('.digital-clock-separator', ':'),
5522
+ // Minutes column
5523
+ m('.digital-clock-column', {
5524
+ oncreate: (vnode) => {
5525
+ state.minuteScrollContainer = vnode.dom;
5526
+ const currentIndex = minuteOptions.indexOf(minutes);
5527
+ if (currentIndex >= 0) {
5528
+ scrollToValue(state.minuteScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
5529
+ }
5530
+ },
5531
+ onwheel: (e) => {
5532
+ e.preventDefault();
5533
+ if (!state.minuteScrollContainer)
5534
+ return;
5535
+ const delta = Math.sign(e.deltaY);
5536
+ const currentIndex = minuteOptions.indexOf(minutes);
5537
+ const newIndex = Math.max(0, Math.min(minuteOptions.length - 1, currentIndex + delta));
5538
+ const newMinute = minuteOptions[newIndex];
5539
+ if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
5540
+ onTimeChange(hours, newMinute, amOrPm);
5541
+ if (spanMinutes) {
5542
+ spanMinutes.innerHTML = addLeadingZero(newMinute);
5543
+ }
5544
+ scrollToValue(state.minuteScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
5545
+ m.redraw();
5546
+ }
5547
+ },
5548
+ onscroll: () => {
5549
+ if (state.minuteScrollTimeout) {
5550
+ clearTimeout(state.minuteScrollTimeout);
5551
+ }
5552
+ state.minuteScrollTimeout = window.setTimeout(() => {
5553
+ if (!state.minuteScrollContainer)
5554
+ return;
5555
+ snapToNearestItem(state.minuteScrollContainer, ITEM_HEIGHT, (index) => {
5556
+ const actualIndex = index - 2; // Account for padding
5557
+ if (actualIndex >= 0 && actualIndex < minuteOptions.length) {
5558
+ const newMinute = minuteOptions[actualIndex];
5559
+ if (!isTimeDisabled(hours, newMinute, amOrPm, minTime, maxTime, twelveHour)) {
5560
+ onTimeChange(hours, newMinute, amOrPm);
5561
+ if (spanMinutes) {
5562
+ spanMinutes.innerHTML = addLeadingZero(newMinute);
5563
+ }
5564
+ m.redraw();
5565
+ }
5566
+ }
5567
+ });
5568
+ }, 150);
5569
+ },
5570
+ }, [
5571
+ // Padding items for centering
5572
+ m('.digital-clock-item.padding'),
5573
+ m('.digital-clock-item.padding'),
5574
+ // Minute items
5575
+ ...minuteOptions.map((minute) => {
5576
+ const disabled = isTimeDisabled(hours, minute, amOrPm, minTime, maxTime, twelveHour);
5577
+ return m('.digital-clock-item', {
5578
+ class: `${minute === minutes ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
5579
+ onclick: () => {
5580
+ if (disabled)
5581
+ return;
5582
+ onTimeChange(hours, minute, amOrPm);
5583
+ if (spanMinutes) {
5584
+ spanMinutes.innerHTML = addLeadingZero(minute);
5585
+ }
5586
+ if (state.minuteScrollContainer) {
5587
+ const index = minuteOptions.indexOf(minute);
5588
+ scrollToValue(state.minuteScrollContainer, index + 2, ITEM_HEIGHT, true);
5589
+ }
5590
+ m.redraw();
5591
+ },
5592
+ }, addLeadingZero(minute));
5593
+ }),
5594
+ // Padding items for centering
5595
+ m('.digital-clock-item.padding'),
5596
+ m('.digital-clock-item.padding'),
5597
+ ]),
5598
+ // AM/PM column (if twelveHour)
5599
+ twelveHour &&
5600
+ m('.digital-clock-column.ampm-column', {
5601
+ oncreate: (vnode) => {
5602
+ state.amPmScrollContainer = vnode.dom;
5603
+ const amPmOptions = ['AM', 'PM'];
5604
+ const currentIndex = amPmOptions.indexOf(amOrPm);
5605
+ if (currentIndex >= 0) {
5606
+ scrollToValue(state.amPmScrollContainer, currentIndex + 2, ITEM_HEIGHT, false);
5607
+ }
5608
+ },
5609
+ onwheel: (e) => {
5610
+ e.preventDefault();
5611
+ const delta = Math.sign(e.deltaY);
5612
+ const newAmPm = delta > 0 ? 'PM' : 'AM';
5613
+ if (newAmPm !== amOrPm && !isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
5614
+ onTimeChange(hours, minutes, newAmPm);
5615
+ if (spanAmPm) {
5616
+ spanAmPm.innerHTML = newAmPm;
5617
+ }
5618
+ const amPmOptions = ['AM', 'PM'];
5619
+ const newIndex = amPmOptions.indexOf(newAmPm);
5620
+ if (state.amPmScrollContainer) {
5621
+ scrollToValue(state.amPmScrollContainer, newIndex + 2, ITEM_HEIGHT, true);
5622
+ }
5623
+ m.redraw();
5624
+ }
5625
+ },
5626
+ onscroll: () => {
5627
+ if (state.amPmScrollTimeout) {
5628
+ clearTimeout(state.amPmScrollTimeout);
5629
+ }
5630
+ state.amPmScrollTimeout = window.setTimeout(() => {
5631
+ if (!state.amPmScrollContainer)
5632
+ return;
5633
+ snapToNearestItem(state.amPmScrollContainer, ITEM_HEIGHT, (index) => {
5634
+ const actualIndex = index - 2;
5635
+ const amPmOptions = ['AM', 'PM'];
5636
+ if (actualIndex >= 0 && actualIndex < amPmOptions.length) {
5637
+ const newAmPm = amPmOptions[actualIndex];
5638
+ if (!isTimeDisabled(hours, minutes, newAmPm, minTime, maxTime, twelveHour)) {
5639
+ onTimeChange(hours, minutes, newAmPm);
5640
+ if (spanAmPm) {
5641
+ spanAmPm.innerHTML = newAmPm;
5642
+ }
5643
+ m.redraw();
5644
+ }
5645
+ }
5646
+ });
5647
+ }, 150);
5648
+ },
5649
+ }, [
5650
+ // Padding items
5651
+ m('.digital-clock-item.padding'),
5652
+ m('.digital-clock-item.padding'),
5653
+ // AM/PM items
5654
+ ['AM', 'PM'].map((ampm) => {
5655
+ const disabled = isTimeDisabled(hours, minutes, ampm, minTime, maxTime, twelveHour);
5656
+ return m('.digital-clock-item', {
5657
+ class: `${ampm === amOrPm ? 'selected' : ''} ${disabled ? 'disabled' : ''}`,
5658
+ onclick: () => {
5659
+ if (disabled)
5660
+ return;
5661
+ onTimeChange(hours, minutes, ampm);
5662
+ if (spanAmPm) {
5663
+ spanAmPm.innerHTML = ampm;
5664
+ }
5665
+ if (state.amPmScrollContainer) {
5666
+ const amPmOptions = ['AM', 'PM'];
5667
+ const index = amPmOptions.indexOf(ampm);
5668
+ scrollToValue(state.amPmScrollContainer, index + 2, ITEM_HEIGHT, true);
5669
+ }
5670
+ m.redraw();
5671
+ },
5672
+ }, ampm);
5673
+ }),
5674
+ // Padding items
5675
+ m('.digital-clock-item.padding'),
5676
+ m('.digital-clock-item.padding'),
5677
+ ]),
5678
+ ]);
5679
+ },
5334
5680
  };
5335
- const createSVGEl = (name) => {
5336
- const svgNS = 'http://www.w3.org/2000/svg';
5337
- return document.createElementNS(svgNS, name);
5681
+ };
5682
+
5683
+ /**
5684
+ * AnalogClock component - A draggable analog clock face for time selection
5685
+ *
5686
+ * @example
5687
+ * ```typescript
5688
+ * m(AnalogClock, {
5689
+ * hours: 10,
5690
+ * minutes: 30,
5691
+ * amOrPm: 'AM',
5692
+ * currentView: 'hours',
5693
+ * twelveHour: true,
5694
+ * onTimeChange: (hours, minutes) => {
5695
+ * console.log(`Time changed to ${hours}:${minutes}`);
5696
+ * },
5697
+ * onViewChange: (view) => {
5698
+ * console.log(`View changed to ${view}`);
5699
+ * }
5700
+ * })
5701
+ * ```
5702
+ */
5703
+ const AnalogClock = () => {
5704
+ const state = {
5705
+ moved: false,
5706
+ x0: 0,
5707
+ y0: 0,
5708
+ dx: 0,
5709
+ dy: 0,
5338
5710
  };
5339
5711
  const getPos = (e) => {
5340
5712
  const touchEvent = e;
@@ -5344,202 +5716,36 @@ const TimePicker = () => {
5344
5716
  }
5345
5717
  return { x: mouseEvent.clientX, y: mouseEvent.clientY };
5346
5718
  };
5347
- const vibrate = () => {
5719
+ const vibrate = (attrs) => {
5348
5720
  if (state.vibrateTimer) {
5349
5721
  clearTimeout(state.vibrateTimer);
5350
5722
  }
5351
- if (options.vibrate && navigator.vibrate) {
5723
+ if (attrs.vibrate && navigator.vibrate) {
5352
5724
  navigator.vibrate(10);
5353
5725
  state.vibrateTimer = window.setTimeout(() => {
5354
5726
  state.vibrateTimer = undefined;
5355
5727
  }, 100);
5356
5728
  }
5357
5729
  };
5358
- const handleClockClickStart = (e) => {
5359
- e.preventDefault();
5360
- if (!state.plate)
5361
- return;
5362
- const clockPlateBR = state.plate.getBoundingClientRect();
5363
- const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
5364
- state.x0 = offset.x + options.dialRadius;
5365
- state.y0 = offset.y + options.dialRadius;
5366
- state.moved = false;
5367
- const clickPos = getPos(e);
5368
- state.dx = clickPos.x - state.x0;
5369
- state.dy = clickPos.y - state.y0;
5370
- setHand(state.dx, state.dy, options.roundBy5);
5371
- document.addEventListener('mousemove', handleDocumentClickMove);
5372
- document.addEventListener('touchmove', handleDocumentClickMove);
5373
- document.addEventListener('mouseup', handleDocumentClickEnd);
5374
- document.addEventListener('touchend', handleDocumentClickEnd);
5375
- };
5376
- const handleDocumentClickMove = (e) => {
5377
- e.preventDefault();
5378
- const clickPos = getPos(e);
5379
- const x = clickPos.x - state.x0;
5380
- const y = clickPos.y - state.y0;
5381
- state.moved = true;
5382
- setHand(x, y, options.roundBy5);
5383
- m.redraw();
5384
- };
5385
- const handleDocumentClickEnd = (e) => {
5386
- e.preventDefault();
5387
- document.removeEventListener('mouseup', handleDocumentClickEnd);
5388
- document.removeEventListener('touchend', handleDocumentClickEnd);
5389
- document.removeEventListener('mousemove', handleDocumentClickMove);
5390
- document.removeEventListener('touchmove', handleDocumentClickMove);
5391
- const clickPos = getPos(e);
5392
- const x = clickPos.x - state.x0;
5393
- const y = clickPos.y - state.y0;
5394
- if (state.moved && x === state.dx && y === state.dy) {
5395
- setHand(x, y);
5396
- }
5397
- if (state.currentView === 'hours') {
5398
- showView('minutes', options.duration / 2);
5399
- }
5400
- else if (options.autoClose) {
5401
- if (state.minutesView) {
5402
- state.minutesView.classList.add('timepicker-dial-out');
5403
- }
5404
- setTimeout(() => {
5405
- done();
5406
- }, options.duration / 2);
5407
- }
5408
- if (options.onSelect) {
5409
- options.onSelect(state.hours, state.minutes);
5410
- }
5411
- m.redraw();
5412
- };
5413
- const updateTimeFromInput = (inputValue) => {
5414
- let value = ((inputValue || options.defaultTime || '') + '').split(':');
5415
- let amPmWasProvided = false;
5416
- if (options.twelveHour && value.length > 1) {
5417
- if (value[1].toUpperCase().indexOf('AM') > -1) {
5418
- state.amOrPm = 'AM';
5419
- amPmWasProvided = true;
5420
- }
5421
- else if (value[1].toUpperCase().indexOf('PM') > -1) {
5422
- state.amOrPm = 'PM';
5423
- amPmWasProvided = true;
5424
- }
5425
- value[1] = value[1].replace('AM', '').replace('PM', '').trim();
5426
- }
5427
- if (value[0] === 'now') {
5428
- const now = new Date(+new Date() + options.fromNow);
5429
- value = [now.getHours().toString(), now.getMinutes().toString()];
5430
- if (options.twelveHour) {
5431
- state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
5432
- amPmWasProvided = false; // For 'now', we need to do conversion
5433
- }
5434
- }
5435
- let hours = +value[0] || 0;
5436
- let minutes = +value[1] || 0;
5437
- if (options.twelveHour) {
5438
- if (!amPmWasProvided) {
5439
- // No AM/PM was provided, assume this is 24-hour format input - convert it
5440
- if (hours >= 12) {
5441
- state.amOrPm = 'PM';
5442
- if (hours > 12) {
5443
- hours = hours - 12;
5444
- }
5445
- }
5446
- else {
5447
- state.amOrPm = 'AM';
5448
- if (hours === 0) {
5449
- hours = 12;
5450
- }
5451
- }
5452
- }
5453
- else {
5454
- // AM/PM was provided, hours are already in 12-hour format
5455
- // Just handle midnight/noon edge cases
5456
- if (hours === 0 && state.amOrPm === 'AM') {
5457
- hours = 12;
5458
- }
5459
- }
5460
- }
5461
- state.hours = hours;
5462
- state.minutes = minutes;
5463
- if (state.spanHours) {
5464
- state.spanHours.innerHTML = addLeadingZero(state.hours);
5465
- }
5466
- if (state.spanMinutes) {
5467
- state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
5468
- }
5469
- updateAmPmView();
5470
- };
5471
- const updateAmPmView = () => {
5472
- if (options.twelveHour && state.amBtn && state.pmBtn) {
5473
- state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
5474
- state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
5475
- }
5476
- };
5477
- const showView = (view, delay) => {
5478
- const isHours = view === 'hours';
5479
- const nextView = isHours ? state.hoursView : state.minutesView;
5480
- const hideView = isHours ? state.minutesView : state.hoursView;
5481
- state.currentView = view;
5482
- if (state.spanHours) {
5483
- state.spanHours.classList.toggle('text-primary', isHours);
5484
- }
5485
- if (state.spanMinutes) {
5486
- state.spanMinutes.classList.toggle('text-primary', !isHours);
5487
- }
5488
- if (hideView) {
5489
- hideView.classList.add('timepicker-dial-out');
5490
- }
5491
- if (nextView) {
5492
- nextView.style.visibility = 'visible';
5493
- nextView.classList.remove('timepicker-dial-out');
5494
- }
5495
- resetClock(delay);
5496
- if (state.toggleViewTimer) {
5497
- clearTimeout(state.toggleViewTimer);
5498
- }
5499
- state.toggleViewTimer = window.setTimeout(() => {
5500
- if (hideView) {
5501
- hideView.style.visibility = 'hidden';
5502
- }
5503
- }, options.duration);
5504
- };
5505
- const resetClock = (delay) => {
5506
- const view = state.currentView;
5507
- const value = state[view];
5508
- const isHours = view === 'hours';
5509
- const unit = Math.PI / (isHours ? 6 : 30);
5510
- const radian = value * unit;
5511
- const radius = isHours && value > 0 && value < 13 ? options.innerRadius : options.outerRadius;
5512
- const x = Math.sin(radian) * radius;
5513
- const y = -Math.cos(radian) * radius;
5514
- if (delay && state.canvas) {
5515
- state.canvas.classList.add('timepicker-canvas-out');
5516
- setTimeout(() => {
5517
- if (state.canvas) {
5518
- state.canvas.classList.remove('timepicker-canvas-out');
5519
- }
5520
- setHand(x, y);
5521
- }, delay);
5522
- }
5523
- else {
5524
- setHand(x, y);
5525
- }
5526
- };
5527
- const setHand = (x, y, roundBy5, _dragging) => {
5730
+ const setHand = (x, y, attrs, roundBy5, _dragging) => {
5731
+ const outerRadius = attrs.outerRadius || 105;
5732
+ const innerRadius = attrs.innerRadius || 70;
5733
+ const tickRadius = attrs.tickRadius || 20;
5528
5734
  let radian = Math.atan2(x, -y);
5529
- const isHours = state.currentView === 'hours';
5735
+ const isHours = attrs.currentView === 'hours';
5530
5736
  const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
5531
5737
  const z = Math.sqrt(x * x + y * y);
5532
- const inner = isHours && z < (options.outerRadius + options.innerRadius) / 2;
5533
- let radius = inner ? options.innerRadius : options.outerRadius;
5534
- if (options.twelveHour) {
5535
- radius = options.outerRadius;
5738
+ const inner = isHours && z < (outerRadius + innerRadius) / 2;
5739
+ let radius = inner ? innerRadius : outerRadius;
5740
+ if (attrs.twelveHour) {
5741
+ radius = outerRadius;
5536
5742
  }
5537
5743
  if (radian < 0) {
5538
5744
  radian = Math.PI * 2 + radian;
5539
5745
  }
5540
5746
  let value = Math.round(radian / unit);
5541
5747
  radian = value * unit;
5542
- if (options.twelveHour) {
5748
+ if (attrs.twelveHour) {
5543
5749
  if (isHours) {
5544
5750
  if (value === 0)
5545
5751
  value = 12;
@@ -5564,20 +5770,27 @@ const TimePicker = () => {
5564
5770
  value = 0;
5565
5771
  }
5566
5772
  }
5567
- if (state[state.currentView] !== value) {
5568
- vibrate();
5773
+ const currentValue = isHours ? attrs.hours : attrs.minutes;
5774
+ if (currentValue !== value) {
5775
+ vibrate(attrs);
5569
5776
  }
5570
- state[state.currentView] = value;
5571
- if (isHours && state.spanHours) {
5572
- state.spanHours.innerHTML = addLeadingZero(value);
5573
- }
5574
- else if (!isHours && state.spanMinutes) {
5575
- state.spanMinutes.innerHTML = addLeadingZero(value);
5777
+ // Update the value
5778
+ if (isHours) {
5779
+ attrs.onTimeChange(value, attrs.minutes);
5780
+ if (attrs.spanHours) {
5781
+ attrs.spanHours.innerHTML = addLeadingZero(value);
5782
+ }
5783
+ }
5784
+ else {
5785
+ attrs.onTimeChange(attrs.hours, value);
5786
+ if (attrs.spanMinutes) {
5787
+ attrs.spanMinutes.innerHTML = addLeadingZero(value);
5788
+ }
5576
5789
  }
5577
5790
  // Set clock hand position
5578
5791
  if (state.hand && state.bg) {
5579
- const cx1 = Math.sin(radian) * (radius - options.tickRadius);
5580
- const cy1 = -Math.cos(radian) * (radius - options.tickRadius);
5792
+ const cx1 = Math.sin(radian) * (radius - tickRadius);
5793
+ const cy1 = -Math.cos(radian) * (radius - tickRadius);
5581
5794
  const cx2 = Math.sin(radian) * radius;
5582
5795
  const cy2 = -Math.cos(radian) * radius;
5583
5796
  state.hand.setAttribute('x2', cx1.toString());
@@ -5586,79 +5799,339 @@ const TimePicker = () => {
5586
5799
  state.bg.setAttribute('cy', cy2.toString());
5587
5800
  }
5588
5801
  };
5589
- const buildSVGClock = () => {
5590
- if (!state.canvas)
5591
- return;
5592
- const dialRadius = options.dialRadius;
5593
- const tickRadius = options.tickRadius;
5594
- const diameter = dialRadius * 2;
5595
- const svg = createSVGEl('svg');
5596
- svg.setAttribute('class', 'timepicker-svg');
5597
- svg.setAttribute('width', diameter.toString());
5598
- svg.setAttribute('height', diameter.toString());
5599
- const g = createSVGEl('g');
5600
- g.setAttribute('transform', `translate(${dialRadius},${dialRadius})`);
5601
- const bearing = createSVGEl('circle');
5602
- bearing.setAttribute('class', 'timepicker-canvas-bearing');
5603
- bearing.setAttribute('cx', '0');
5604
- bearing.setAttribute('cy', '0');
5605
- bearing.setAttribute('r', '4');
5606
- const hand = createSVGEl('line');
5607
- hand.setAttribute('x1', '0');
5608
- hand.setAttribute('y1', '0');
5609
- const bg = createSVGEl('circle');
5610
- bg.setAttribute('class', 'timepicker-canvas-bg');
5611
- bg.setAttribute('r', tickRadius.toString());
5612
- g.appendChild(hand);
5613
- g.appendChild(bg);
5614
- g.appendChild(bearing);
5615
- svg.appendChild(g);
5616
- state.canvas.appendChild(svg);
5617
- state.hand = hand;
5618
- state.bg = bg;
5619
- state.bearing = bearing;
5620
- state.g = g;
5621
- };
5622
- const buildHoursView = () => {
5623
- if (!state.hoursView)
5802
+ const resetClock = (attrs) => {
5803
+ const view = attrs.currentView;
5804
+ const value = view === 'hours' ? attrs.hours : attrs.minutes;
5805
+ const isHours = view === 'hours';
5806
+ const unit = Math.PI / (isHours ? 6 : 30);
5807
+ const radian = value * unit;
5808
+ const outerRadius = attrs.outerRadius || 105;
5809
+ const innerRadius = attrs.innerRadius || 70;
5810
+ // In 12-hour mode, always use outer radius
5811
+ // In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
5812
+ let radius = outerRadius;
5813
+ if (!attrs.twelveHour && isHours && value > 0 && value < 13) {
5814
+ radius = innerRadius;
5815
+ }
5816
+ const x = Math.sin(radian) * radius;
5817
+ const y = -Math.cos(radian) * radius;
5818
+ setHand(x, y, attrs);
5819
+ };
5820
+ const handleClockClickStart = (e, attrs) => {
5821
+ e.preventDefault();
5822
+ if (!state.plate)
5624
5823
  return;
5625
- if (options.twelveHour) {
5626
- for (let i = 1; i < 13; i++) {
5627
- const tick = document.createElement('div');
5628
- tick.className = 'timepicker-tick';
5629
- const radian = (i / 6) * Math.PI;
5630
- const radius = options.outerRadius;
5631
- tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
5632
- tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
5633
- tick.innerHTML = i === 0 ? '00' : i.toString();
5634
- state.hoursView.appendChild(tick);
5824
+ const _dialRadius = attrs.dialRadius || 135;
5825
+ const clockPlateBR = state.plate.getBoundingClientRect();
5826
+ const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
5827
+ state.x0 = offset.x + _dialRadius;
5828
+ state.y0 = offset.y + _dialRadius;
5829
+ state.moved = false;
5830
+ const clickPos = getPos(e);
5831
+ state.dx = clickPos.x - state.x0;
5832
+ state.dy = clickPos.y - state.y0;
5833
+ const startX = clickPos.x;
5834
+ const startY = clickPos.y;
5835
+ setHand(state.dx, state.dy, attrs, attrs.roundBy5);
5836
+ m.redraw();
5837
+ // Add document-level listeners to track dragging
5838
+ const moveHandler = (e) => {
5839
+ e.preventDefault();
5840
+ const clickPos = getPos(e);
5841
+ const x = clickPos.x - state.x0;
5842
+ const y = clickPos.y - state.y0;
5843
+ // Only consider it "moved" if dragged more than 5 pixels
5844
+ const distance = Math.sqrt(Math.pow(clickPos.x - startX, 2) + Math.pow(clickPos.y - startY, 2));
5845
+ if (distance > 5) {
5846
+ state.moved = true;
5847
+ }
5848
+ setHand(x, y, attrs, attrs.roundBy5);
5849
+ };
5850
+ const endHandler = () => {
5851
+ document.removeEventListener('mousemove', moveHandler);
5852
+ document.removeEventListener('touchmove', moveHandler);
5853
+ // After setting hour (either by click or drag), switch to minutes view
5854
+ if (attrs.currentView === 'hours' && attrs.onViewChange) {
5855
+ attrs.onViewChange('minutes');
5856
+ }
5857
+ state.moved = false;
5858
+ m.redraw();
5859
+ };
5860
+ document.addEventListener('mousemove', moveHandler);
5861
+ document.addEventListener('touchmove', moveHandler);
5862
+ document.addEventListener('mouseup', endHandler, { once: true });
5863
+ document.addEventListener('touchend', endHandler, { once: true });
5864
+ };
5865
+ const HourTicks = () => {
5866
+ return {
5867
+ view: ({ attrs }) => {
5868
+ const dialRadius = attrs.dialRadius || 135;
5869
+ const outerRadius = attrs.outerRadius || 105;
5870
+ const innerRadius = attrs.innerRadius || 70;
5871
+ const tickRadius = attrs.tickRadius || 20;
5872
+ const ticks = [];
5873
+ if (attrs.twelveHour) {
5874
+ for (let i = 1; i < 13; i++) {
5875
+ const radian = (i / 6) * Math.PI;
5876
+ const radius = outerRadius;
5877
+ const left = dialRadius + Math.sin(radian) * radius - tickRadius;
5878
+ const top = dialRadius - Math.cos(radian) * radius - tickRadius;
5879
+ ticks.push(m('.timepicker-tick', {
5880
+ style: {
5881
+ left: `${left}px`,
5882
+ top: `${top}px`,
5883
+ },
5884
+ }, i === 0 ? '00' : i.toString()));
5885
+ }
5886
+ }
5887
+ else {
5888
+ for (let i = 0; i < 24; i++) {
5889
+ const radian = (i / 6) * Math.PI;
5890
+ const inner = i > 0 && i < 13;
5891
+ const radius = inner ? innerRadius : outerRadius;
5892
+ const left = dialRadius + Math.sin(radian) * radius - tickRadius;
5893
+ const top = dialRadius - Math.cos(radian) * radius - tickRadius;
5894
+ ticks.push(m('.timepicker-tick', {
5895
+ style: {
5896
+ left: `${left}px`,
5897
+ top: `${top}px`,
5898
+ },
5899
+ }, i === 0 ? '00' : i.toString()));
5900
+ }
5901
+ }
5902
+ return ticks;
5903
+ },
5904
+ };
5905
+ };
5906
+ const MinuteTicks = () => {
5907
+ return {
5908
+ view: ({ attrs }) => {
5909
+ const dialRadius = attrs.dialRadius || 135;
5910
+ const outerRadius = attrs.outerRadius || 105;
5911
+ const tickRadius = attrs.tickRadius || 20;
5912
+ const ticks = [];
5913
+ for (let i = 0; i < 60; i += 5) {
5914
+ const radian = (i / 30) * Math.PI;
5915
+ const left = dialRadius + Math.sin(radian) * outerRadius - tickRadius;
5916
+ const top = dialRadius - Math.cos(radian) * outerRadius - tickRadius;
5917
+ ticks.push(m('.timepicker-tick', {
5918
+ style: {
5919
+ left: `${left}px`,
5920
+ top: `${top}px`,
5921
+ },
5922
+ }, addLeadingZero(i)));
5923
+ }
5924
+ return ticks;
5925
+ },
5926
+ };
5927
+ };
5928
+ return {
5929
+ oncreate: ({ attrs }) => {
5930
+ resetClock(attrs);
5931
+ },
5932
+ view: ({ attrs }) => {
5933
+ // Handle view transitions
5934
+ const isHours = attrs.currentView === 'hours';
5935
+ const dialRadius = attrs.dialRadius || 135;
5936
+ const tickRadius = attrs.tickRadius || 20;
5937
+ const diameter = dialRadius * 2;
5938
+ // Calculate hand and background positions
5939
+ const view = attrs.currentView;
5940
+ const value = view === 'hours' ? attrs.hours : attrs.minutes;
5941
+ const unit = Math.PI / (view === 'hours' ? 6 : 30);
5942
+ const radian = value * unit;
5943
+ const outerRadius = attrs.outerRadius || 105;
5944
+ const innerRadius = attrs.innerRadius || 70;
5945
+ // In 12-hour mode, always use outer radius
5946
+ // In 24-hour mode, use inner radius for hours 1-12, outer for 0 and 13-23
5947
+ let radius = outerRadius;
5948
+ if (!attrs.twelveHour && view === 'hours' && value > 0 && value < 13) {
5949
+ radius = innerRadius;
5950
+ }
5951
+ const cx1 = Math.sin(radian) * (radius - tickRadius);
5952
+ const cy1 = -Math.cos(radian) * (radius - tickRadius);
5953
+ const cx2 = Math.sin(radian) * radius;
5954
+ const cy2 = -Math.cos(radian) * radius;
5955
+ return [
5956
+ m('.timepicker-canvas', {
5957
+ oncreate: (vnode) => {
5958
+ state.canvas = vnode.dom;
5959
+ state.plate = vnode.dom.parentElement;
5960
+ },
5961
+ onmousedown: (e) => handleClockClickStart(e, attrs),
5962
+ ontouchstart: (e) => handleClockClickStart(e, attrs),
5963
+ }, [
5964
+ m('svg.timepicker-svg', {
5965
+ width: diameter,
5966
+ height: diameter,
5967
+ xmlns: 'http://www.w3.org/2000/svg',
5968
+ }, [
5969
+ m('g', {
5970
+ transform: `translate(${dialRadius},${dialRadius})`,
5971
+ oncreate: (vnode) => {
5972
+ state.g = vnode.dom;
5973
+ },
5974
+ }, [
5975
+ m('line', {
5976
+ x1: '0',
5977
+ y1: '0',
5978
+ x2: cx1,
5979
+ y2: cy1,
5980
+ oncreate: (vnode) => {
5981
+ state.hand = vnode.dom;
5982
+ },
5983
+ }),
5984
+ m('circle.timepicker-canvas-bg', {
5985
+ cx: cx2,
5986
+ cy: cy2,
5987
+ r: tickRadius,
5988
+ oncreate: (vnode) => {
5989
+ state.bg = vnode.dom;
5990
+ },
5991
+ }),
5992
+ m('circle.timepicker-canvas-bearing', {
5993
+ cx: '0',
5994
+ cy: '0',
5995
+ r: '4',
5996
+ oncreate: (vnode) => {
5997
+ state.bearing = vnode.dom;
5998
+ },
5999
+ }),
6000
+ ]),
6001
+ ]),
6002
+ ]),
6003
+ m(`.timepicker-dial.timepicker-hours${isHours ? '' : '.timepicker-dial-out'}`, {
6004
+ oncreate: (vnode) => {
6005
+ state.hoursView = vnode.dom;
6006
+ },
6007
+ style: {
6008
+ visibility: isHours ? 'visible' : 'hidden',
6009
+ },
6010
+ }, m(HourTicks, attrs)),
6011
+ m(`.timepicker-dial.timepicker-minutes${!isHours ? '' : '.timepicker-dial-out'}`, {
6012
+ oncreate: (vnode) => {
6013
+ state.minutesView = vnode.dom;
6014
+ },
6015
+ style: {
6016
+ visibility: !isHours ? 'visible' : 'hidden',
6017
+ },
6018
+ }, m(MinuteTicks, attrs)),
6019
+ ];
6020
+ },
6021
+ onupdate: ({ attrs }) => {
6022
+ // Update clock hand when time or view changes
6023
+ resetClock(attrs);
6024
+ },
6025
+ };
6026
+ };
6027
+
6028
+ const defaultI18n$3 = {
6029
+ cancel: 'Cancel',
6030
+ clear: 'Clear',
6031
+ done: 'Ok',
6032
+ next: 'Next',
6033
+ };
6034
+ const defaultOptions = {
6035
+ dialRadius: 135,
6036
+ outerRadius: 105,
6037
+ innerRadius: 70,
6038
+ tickRadius: 20,
6039
+ duration: 350,
6040
+ container: null,
6041
+ defaultTime: 'now',
6042
+ fromNow: 0,
6043
+ showClearBtn: false,
6044
+ i18n: defaultI18n$3,
6045
+ autoClose: false,
6046
+ twelveHour: true,
6047
+ vibrate: true,
6048
+ roundBy5: false,
6049
+ displayMode: 'analog',
6050
+ minuteStep: 5,
6051
+ hourStep: 1,
6052
+ minTime: undefined,
6053
+ maxTime: undefined,
6054
+ onOpen: () => { },
6055
+ onOpenStart: () => { },
6056
+ onOpenEnd: () => { },
6057
+ onCloseStart: () => { },
6058
+ onCloseEnd: () => { },
6059
+ onSelect: () => { },
6060
+ };
6061
+ /**
6062
+ * TimePicker component based on original Materialize CSS timepicker
6063
+ */
6064
+ const TimePicker = () => {
6065
+ let state;
6066
+ let options;
6067
+ // Use shared utilities from time-utils
6068
+ // const addLeadingZero = sharedAddLeadingZero;
6069
+ // const generateHourOptions = sharedGenerateHourOptions;
6070
+ // const generateMinuteOptions = sharedGenerateMinuteOptions;
6071
+ // const scrollToValue = sharedScrollToValue;
6072
+ // const snapToNearestItem = sharedSnapToNearestItem;
6073
+ const updateTimeFromInput = (inputValue) => {
6074
+ let value = ((inputValue || options.defaultTime || '') + '').split(':');
6075
+ let amPmWasProvided = false;
6076
+ if (options.twelveHour && value.length > 1) {
6077
+ if (value[1].toUpperCase().indexOf('AM') > -1) {
6078
+ state.amOrPm = 'AM';
6079
+ amPmWasProvided = true;
6080
+ }
6081
+ else if (value[1].toUpperCase().indexOf('PM') > -1) {
6082
+ state.amOrPm = 'PM';
6083
+ amPmWasProvided = true;
5635
6084
  }
6085
+ value[1] = value[1].replace('AM', '').replace('PM', '').trim();
5636
6086
  }
5637
- else {
5638
- for (let i = 0; i < 24; i++) {
5639
- const tick = document.createElement('div');
5640
- tick.className = 'timepicker-tick';
5641
- const radian = (i / 6) * Math.PI;
5642
- const inner = i > 0 && i < 13;
5643
- const radius = inner ? options.innerRadius : options.outerRadius;
5644
- tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
5645
- tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
5646
- tick.innerHTML = i === 0 ? '00' : i.toString();
5647
- state.hoursView.appendChild(tick);
6087
+ if (value[0] === 'now') {
6088
+ const now = new Date(+new Date() + options.fromNow);
6089
+ value = [now.getHours().toString(), now.getMinutes().toString()];
6090
+ if (options.twelveHour) {
6091
+ state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
6092
+ amPmWasProvided = false; // For 'now', we need to do conversion
6093
+ }
6094
+ }
6095
+ let hours = +value[0] || 0;
6096
+ let minutes = +value[1] || 0;
6097
+ if (options.twelveHour) {
6098
+ if (!amPmWasProvided) {
6099
+ // No AM/PM was provided, assume this is 24-hour format input - convert it
6100
+ if (hours >= 12) {
6101
+ state.amOrPm = 'PM';
6102
+ if (hours > 12) {
6103
+ hours = hours - 12;
6104
+ }
6105
+ }
6106
+ else {
6107
+ state.amOrPm = 'AM';
6108
+ if (hours === 0) {
6109
+ hours = 12;
6110
+ }
6111
+ }
6112
+ }
6113
+ else {
6114
+ // AM/PM was provided, hours are already in 12-hour format
6115
+ // Just handle midnight/noon edge cases
6116
+ if (hours === 0 && state.amOrPm === 'AM') {
6117
+ hours = 12;
6118
+ }
5648
6119
  }
5649
6120
  }
6121
+ state.hours = hours;
6122
+ state.minutes = minutes;
6123
+ if (state.spanHours) {
6124
+ state.spanHours.innerHTML = addLeadingZero(state.hours);
6125
+ }
6126
+ if (state.spanMinutes) {
6127
+ state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
6128
+ }
6129
+ updateAmPmView();
5650
6130
  };
5651
- const buildMinutesView = () => {
5652
- if (!state.minutesView)
5653
- return;
5654
- for (let i = 0; i < 60; i += 5) {
5655
- const tick = document.createElement('div');
5656
- tick.className = 'timepicker-tick';
5657
- const radian = (i / 30) * Math.PI;
5658
- tick.style.left = options.dialRadius + Math.sin(radian) * options.outerRadius - options.tickRadius + 'px';
5659
- tick.style.top = options.dialRadius - Math.cos(radian) * options.outerRadius - options.tickRadius + 'px';
5660
- tick.innerHTML = addLeadingZero(i);
5661
- state.minutesView.appendChild(tick);
6131
+ const updateAmPmView = () => {
6132
+ if (options.twelveHour && state.amBtn && state.pmBtn) {
6133
+ state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
6134
+ state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
5662
6135
  }
5663
6136
  };
5664
6137
  const handleAmPmClick = (ampm) => {
@@ -5670,7 +6143,7 @@ const TimePicker = () => {
5670
6143
  return;
5671
6144
  state.isOpen = true;
5672
6145
  updateTimeFromInput(inputValue);
5673
- showView('hours');
6146
+ state.currentView = 'hours';
5674
6147
  if (options.onOpen)
5675
6148
  options.onOpen();
5676
6149
  if (options.onOpenStart)
@@ -5704,6 +6177,7 @@ const TimePicker = () => {
5704
6177
  return {
5705
6178
  view: ({ attrs }) => {
5706
6179
  const { i18n, showClearBtn } = attrs;
6180
+ const isDigitalMode = options.displayMode === 'digital';
5707
6181
  return [
5708
6182
  m('.modal-content.timepicker-container', [
5709
6183
  m('.timepicker-digital-display', [
@@ -5711,7 +6185,12 @@ const TimePicker = () => {
5711
6185
  m('.timepicker-display-column', [
5712
6186
  m('span.timepicker-span-hours', {
5713
6187
  class: state.currentView === 'hours' ? 'text-primary' : '',
5714
- onclick: () => showView('hours'),
6188
+ onclick: () => {
6189
+ if (!isDigitalMode) {
6190
+ state.currentView = 'hours';
6191
+ m.redraw();
6192
+ }
6193
+ },
5715
6194
  oncreate: (vnode) => {
5716
6195
  state.spanHours = vnode.dom;
5717
6196
  },
@@ -5719,7 +6198,12 @@ const TimePicker = () => {
5719
6198
  ':',
5720
6199
  m('span.timepicker-span-minutes', {
5721
6200
  class: state.currentView === 'minutes' ? 'text-primary' : '',
5722
- onclick: () => showView('minutes'),
6201
+ onclick: () => {
6202
+ if (!isDigitalMode) {
6203
+ state.currentView = 'minutes';
6204
+ m.redraw();
6205
+ }
6206
+ },
5723
6207
  oncreate: (vnode) => {
5724
6208
  state.spanMinutes = vnode.dom;
5725
6209
  },
@@ -5750,66 +6234,108 @@ const TimePicker = () => {
5750
6234
  ]),
5751
6235
  ]),
5752
6236
  ]),
5753
- m('.timepicker-analog-display', [
5754
- m('.timepicker-plate', {
5755
- oncreate: (vnode) => {
5756
- state.plate = vnode.dom;
5757
- state.plate.addEventListener('mousedown', handleClockClickStart);
5758
- state.plate.addEventListener('touchstart', handleClockClickStart);
5759
- },
5760
- onremove: () => {
5761
- if (state.plate) {
5762
- state.plate.removeEventListener('mousedown', handleClockClickStart);
5763
- state.plate.removeEventListener('touchstart', handleClockClickStart);
5764
- }
5765
- },
5766
- }, [
5767
- m('.timepicker-canvas', {
5768
- oncreate: (vnode) => {
5769
- state.canvas = vnode.dom;
5770
- buildSVGClock();
5771
- // Position the hand after SVG is built
5772
- setTimeout(() => resetClock(), 10);
5773
- },
5774
- }),
5775
- m('.timepicker-dial.timepicker-hours', {
5776
- oncreate: (vnode) => {
5777
- state.hoursView = vnode.dom;
5778
- buildHoursView();
6237
+ // Conditional rendering: digital or analog mode
6238
+ isDigitalMode
6239
+ ? m('.timepicker-digital-mode', [
6240
+ m(DigitalClock, {
6241
+ hours: state.hours,
6242
+ minutes: state.minutes,
6243
+ amOrPm: state.amOrPm,
6244
+ twelveHour: options.twelveHour,
6245
+ minuteStep: options.minuteStep,
6246
+ hourStep: options.hourStep,
6247
+ minTime: options.minTime,
6248
+ maxTime: options.maxTime,
6249
+ onTimeChange: (hours, minutes, amOrPm) => {
6250
+ state.hours = hours;
6251
+ state.minutes = minutes;
6252
+ state.amOrPm = amOrPm;
6253
+ updateAmPmView();
6254
+ if (options.onSelect)
6255
+ options.onSelect(hours, minutes);
5779
6256
  },
6257
+ spanHours: state.spanHours,
6258
+ spanMinutes: state.spanMinutes,
6259
+ spanAmPm: state.spanAmPm,
5780
6260
  }),
5781
- m('.timepicker-dial.timepicker-minutes.timepicker-dial-out', {
6261
+ m('.timepicker-footer', {
5782
6262
  oncreate: (vnode) => {
5783
- state.minutesView = vnode.dom;
5784
- buildMinutesView();
6263
+ state.footer = vnode.dom;
5785
6264
  },
5786
- }),
5787
- ]),
5788
- m('.timepicker-footer', {
5789
- oncreate: (vnode) => {
5790
- state.footer = vnode.dom;
5791
- },
5792
- }, [
5793
- m('button.btn-flat.timepicker-clear.waves-effect', {
5794
- type: 'button',
5795
- tabindex: options.twelveHour ? '3' : '1',
5796
- style: showClearBtn ? '' : 'visibility: hidden;',
5797
- onclick: () => clear(),
5798
- }, i18n.clear),
5799
- m('.confirmation-btns', [
5800
- m('button.btn-flat.timepicker-close.waves-effect', {
6265
+ }, [
6266
+ m('button.btn-flat.timepicker-clear.waves-effect', {
5801
6267
  type: 'button',
5802
6268
  tabindex: options.twelveHour ? '3' : '1',
5803
- onclick: () => close(),
5804
- }, i18n.cancel),
5805
- m('button.btn-flat.timepicker-close.waves-effect', {
6269
+ style: showClearBtn ? '' : 'visibility: hidden;',
6270
+ onclick: () => clear(),
6271
+ }, i18n.clear),
6272
+ m('.confirmation-btns', [
6273
+ m('button.btn-flat.timepicker-close.waves-effect', {
6274
+ type: 'button',
6275
+ tabindex: options.twelveHour ? '3' : '1',
6276
+ onclick: () => close(),
6277
+ }, i18n.cancel),
6278
+ m('button.btn-flat.timepicker-close.waves-effect', {
6279
+ type: 'button',
6280
+ tabindex: options.twelveHour ? '3' : '1',
6281
+ onclick: () => done(),
6282
+ }, i18n.done),
6283
+ ]),
6284
+ ]),
6285
+ ])
6286
+ : m('.timepicker-analog-display', [
6287
+ m('.timepicker-plate', m(AnalogClock, {
6288
+ hours: state.hours,
6289
+ minutes: state.minutes,
6290
+ amOrPm: state.amOrPm,
6291
+ currentView: state.currentView,
6292
+ twelveHour: options.twelveHour,
6293
+ dialRadius: options.dialRadius,
6294
+ outerRadius: options.outerRadius,
6295
+ innerRadius: options.innerRadius,
6296
+ tickRadius: options.tickRadius,
6297
+ roundBy5: options.roundBy5,
6298
+ vibrate: options.vibrate,
6299
+ onTimeChange: (hours, minutes) => {
6300
+ state.hours = hours;
6301
+ state.minutes = minutes;
6302
+ if (options.onSelect)
6303
+ options.onSelect(hours, minutes);
6304
+ },
6305
+ onViewChange: (view) => {
6306
+ state.currentView = view;
6307
+ if (view === 'minutes' && options.autoClose) {
6308
+ setTimeout(() => done(), options.duration / 2);
6309
+ }
6310
+ },
6311
+ spanHours: state.spanHours,
6312
+ spanMinutes: state.spanMinutes,
6313
+ })),
6314
+ m('.timepicker-footer', {
6315
+ oncreate: (vnode) => {
6316
+ state.footer = vnode.dom;
6317
+ },
6318
+ }, [
6319
+ m('button.btn-flat.timepicker-clear.waves-effect', {
5806
6320
  type: 'button',
5807
6321
  tabindex: options.twelveHour ? '3' : '1',
5808
- onclick: () => done(),
5809
- }, i18n.done),
6322
+ style: showClearBtn ? '' : 'visibility: hidden;',
6323
+ onclick: () => clear(),
6324
+ }, i18n.clear),
6325
+ m('.confirmation-btns', [
6326
+ m('button.btn-flat.timepicker-close.waves-effect', {
6327
+ type: 'button',
6328
+ tabindex: options.twelveHour ? '3' : '1',
6329
+ onclick: () => close(),
6330
+ }, i18n.cancel),
6331
+ m('button.btn-flat.timepicker-close.waves-effect', {
6332
+ type: 'button',
6333
+ tabindex: options.twelveHour ? '3' : '1',
6334
+ onclick: () => done(),
6335
+ }, i18n.done),
6336
+ ]),
5810
6337
  ]),
5811
6338
  ]),
5812
- ]),
5813
6339
  ]),
5814
6340
  ];
5815
6341
  },
@@ -5822,7 +6348,7 @@ const TimePicker = () => {
5822
6348
  m.redraw();
5823
6349
  }
5824
6350
  };
5825
- const renderPickerToPortal = (attrs) => {
6351
+ const renderPickerToPortal = () => {
5826
6352
  const pickerModal = m('.timepicker-modal-wrapper', {
5827
6353
  style: {
5828
6354
  position: 'fixed',
@@ -5883,11 +6409,6 @@ const TimePicker = () => {
5883
6409
  minutes: 0,
5884
6410
  amOrPm: 'AM',
5885
6411
  currentView: 'hours',
5886
- moved: false,
5887
- x0: 0,
5888
- y0: 0,
5889
- dx: 0,
5890
- dy: 0,
5891
6412
  portalContainerId: `timepicker-portal-${uniqueId()}`,
5892
6413
  };
5893
6414
  // Handle value after options are set
@@ -5899,27 +6420,17 @@ const TimePicker = () => {
5899
6420
  },
5900
6421
  onremove: () => {
5901
6422
  // Cleanup
5902
- if (state.toggleViewTimer) {
5903
- clearTimeout(state.toggleViewTimer);
5904
- }
5905
- if (state.vibrateTimer) {
5906
- clearTimeout(state.vibrateTimer);
5907
- }
5908
- document.removeEventListener('mousemove', handleDocumentClickMove);
5909
- document.removeEventListener('touchmove', handleDocumentClickMove);
5910
- document.removeEventListener('mouseup', handleDocumentClickEnd);
5911
- document.removeEventListener('touchend', handleDocumentClickEnd);
5912
6423
  document.removeEventListener('keydown', handleKeyDown);
5913
6424
  // Clean up portal if picker was open
5914
6425
  if (state.isOpen) {
5915
6426
  clearPortal(state.portalContainerId);
5916
6427
  }
5917
6428
  },
5918
- onupdate: (vnode) => {
5919
- const { useModal = true } = vnode.attrs;
6429
+ onupdate: ({ attrs }) => {
6430
+ const { useModal = true } = attrs;
5920
6431
  // Only render to portal when using modal mode
5921
6432
  if (useModal && state.isOpen) {
5922
- renderPickerToPortal(vnode.attrs);
6433
+ renderPickerToPortal();
5923
6434
  }
5924
6435
  else {
5925
6436
  clearPortal(state.portalContainerId);
@@ -6800,46 +7311,50 @@ const Tabs = () => {
6800
7311
  };
6801
7312
 
6802
7313
  // Proper components to avoid anonymous closures
6803
- const SelectedChip = {
6804
- view: ({ attrs: { option, onRemove } }) => m('.chip', [
6805
- option.label || option.id.toString(),
6806
- m(MaterialIcon, {
6807
- name: 'close',
6808
- className: 'close',
6809
- onclick: (e) => {
6810
- e.stopPropagation();
6811
- onRemove(option.id);
6812
- },
6813
- }),
6814
- ]),
6815
- };
6816
- const DropdownOption = {
6817
- view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
6818
- const checkboxId = `search-select-option-${option.id}`;
6819
- const optionLabel = option.label || option.id.toString();
6820
- return m('li', {
6821
- key: option.id,
6822
- onclick: (e) => {
6823
- e.preventDefault();
6824
- e.stopPropagation();
6825
- onToggle(option);
6826
- },
6827
- class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
6828
- onmouseover: () => {
6829
- if (!option.disabled) {
6830
- onMouseOver(index);
6831
- }
6832
- },
6833
- }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
6834
- showCheckbox &&
6835
- m('input', {
6836
- type: 'checkbox',
6837
- id: checkboxId,
6838
- checked: selectedIds.includes(option.id),
6839
- }),
6840
- m('span', optionLabel),
6841
- ]));
6842
- },
7314
+ const SelectedChip = () => {
7315
+ return {
7316
+ view: ({ attrs: { option, onRemove } }) => m('.chip', [
7317
+ option.label || option.id.toString(),
7318
+ m(MaterialIcon, {
7319
+ name: 'close',
7320
+ className: 'close',
7321
+ onclick: (e) => {
7322
+ e.stopPropagation();
7323
+ onRemove(option.id);
7324
+ },
7325
+ }),
7326
+ ]),
7327
+ };
7328
+ };
7329
+ const DropdownOption = () => {
7330
+ return {
7331
+ view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
7332
+ const checkboxId = `search-select-option-${option.id}`;
7333
+ const optionLabel = option.label || option.id.toString();
7334
+ return m('li', {
7335
+ key: option.id,
7336
+ onclick: (e) => {
7337
+ e.preventDefault();
7338
+ e.stopPropagation();
7339
+ onToggle(option);
7340
+ },
7341
+ class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
7342
+ onmouseover: () => {
7343
+ if (!option.disabled) {
7344
+ onMouseOver(index);
7345
+ }
7346
+ },
7347
+ }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
7348
+ showCheckbox &&
7349
+ m('input', {
7350
+ type: 'checkbox',
7351
+ id: checkboxId,
7352
+ checked: selectedIds.includes(option.id),
7353
+ }),
7354
+ m('span', optionLabel),
7355
+ ]));
7356
+ },
7357
+ };
6843
7358
  };
6844
7359
  /**
6845
7360
  * Mithril Factory Component for Multi-Select Dropdown with search
@@ -7078,8 +7593,7 @@ const SearchSelect = () => {
7078
7593
  style: { position: 'absolute', left: '-9999px', opacity: 0 },
7079
7594
  }),
7080
7595
  // Selected Options (chips)
7081
- ...selectedOptions.map((option) => m(SelectedChip, {
7082
- // key: option.id,
7596
+ ...selectedOptions.map((option) => m(SelectedChip(), {
7083
7597
  option,
7084
7598
  onRemove: (id) => removeOption(id, attrs),
7085
7599
  })),
@@ -7195,8 +7709,7 @@ const SearchSelect = () => {
7195
7709
  ]
7196
7710
  : []),
7197
7711
  // List of filtered options
7198
- ...displayedOptions.map((option, index) => m(DropdownOption, {
7199
- // key: option.id,
7712
+ ...displayedOptions.map((option, index) => m(DropdownOption(), {
7200
7713
  option,
7201
7714
  index,
7202
7715
  selectedIds,
@@ -7213,6 +7726,411 @@ const SearchSelect = () => {
7213
7726
  };
7214
7727
  };
7215
7728
 
7729
+ const defaultI18n$2 = {
7730
+ cancel: 'Cancel',
7731
+ clear: 'Clear',
7732
+ done: 'Ok',
7733
+ next: 'Next',
7734
+ };
7735
+ /**
7736
+ * TimeRangePicker component for selecting time ranges
7737
+ * Custom implementation with embedded digital clock picker
7738
+ */
7739
+ const TimeRangePicker = () => {
7740
+ let state;
7741
+ const calculateMinTime = (startTime, twelveHour) => {
7742
+ if (!startTime)
7743
+ return undefined;
7744
+ let hours = startTime.hours;
7745
+ let minutes = startTime.minutes + 1;
7746
+ let amOrPm = startTime.amOrPm;
7747
+ if (minutes >= 60) {
7748
+ minutes = 0;
7749
+ hours++;
7750
+ if (twelveHour) {
7751
+ if (hours > 12) {
7752
+ hours = 1;
7753
+ amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
7754
+ }
7755
+ else if (hours === 12) {
7756
+ amOrPm = amOrPm === 'AM' ? 'PM' : 'AM';
7757
+ }
7758
+ }
7759
+ else {
7760
+ if (hours >= 24)
7761
+ hours = 0;
7762
+ }
7763
+ }
7764
+ return formatTime({ hours, minutes, amOrPm }, twelveHour);
7765
+ };
7766
+ const handleNextOrDone = (validateRange, twelveHour, onchange) => {
7767
+ if (state.currentSelection === 'start') {
7768
+ // Move to end time selection
7769
+ state.currentSelection = 'end';
7770
+ state.currentView = 'hours'; // Reset to hours view for end time
7771
+ // If validation is enabled and end time is before or equal to start time, reset end time
7772
+ if (validateRange) {
7773
+ const startMins = timeToMinutes(state.tempStartTime, twelveHour);
7774
+ const endMins = timeToMinutes(state.tempEndTime, twelveHour);
7775
+ if (endMins <= startMins) {
7776
+ // Reset end time to start time
7777
+ state.tempEndTime = Object.assign({}, state.tempStartTime);
7778
+ }
7779
+ }
7780
+ }
7781
+ else {
7782
+ // Finalize selection
7783
+ state.startTime = Object.assign({}, state.tempStartTime);
7784
+ state.endTime = Object.assign({}, state.tempEndTime);
7785
+ state.isPickerOpen = false;
7786
+ state.currentSelection = 'start';
7787
+ // Call onchange callback
7788
+ if (onchange && state.startTime && state.endTime) {
7789
+ const startTimeStr = formatTime(state.startTime, twelveHour);
7790
+ const endTimeStr = formatTime(state.endTime, twelveHour);
7791
+ onchange(startTimeStr, endTimeStr);
7792
+ }
7793
+ }
7794
+ };
7795
+ const TimeRangePickerModal = () => {
7796
+ return {
7797
+ view: ({ attrs }) => {
7798
+ const { i18n, showClearBtn, twelveHour, minuteStep, hourStep, minTime, maxTime, validateRange, displayMode = 'digital', dialRadius = 135, outerRadius = 105, innerRadius = 70, tickRadius = 20, roundBy5 = false, vibrate = true, } = attrs;
7799
+ const isAnalogMode = displayMode === 'analog';
7800
+ // Calculate effective minTime for end time selection
7801
+ const effectiveMinTime = state.currentSelection === 'end' && validateRange
7802
+ ? calculateMinTime(state.tempStartTime, twelveHour)
7803
+ : minTime;
7804
+ return m('.modal-content.timepicker-container', [
7805
+ // Vertical time range display on the left
7806
+ m('.timerange-display-vertical', [
7807
+ m('.timerange-time-section', { class: state.currentSelection === 'start' ? 'active' : '' }, [
7808
+ m('.timerange-label', 'Start'),
7809
+ m('.timerange-time', [
7810
+ m('span.timerange-hours', {
7811
+ oncreate: (vnode) => {
7812
+ state.spanStartHours = vnode.dom;
7813
+ state.spanStartHours.innerHTML = addLeadingZero(state.tempStartTime.hours);
7814
+ },
7815
+ }, addLeadingZero(state.tempStartTime.hours)),
7816
+ ':',
7817
+ m('span.timerange-minutes', {
7818
+ oncreate: (vnode) => {
7819
+ state.spanStartMinutes = vnode.dom;
7820
+ state.spanStartMinutes.innerHTML = addLeadingZero(state.tempStartTime.minutes);
7821
+ },
7822
+ }, addLeadingZero(state.tempStartTime.minutes)),
7823
+ twelveHour &&
7824
+ m('span.timerange-ampm', {
7825
+ oncreate: (vnode) => {
7826
+ state.spanStartAmPm = vnode.dom;
7827
+ state.spanStartAmPm.innerHTML = ` ${state.tempStartTime.amOrPm}`;
7828
+ },
7829
+ }, ` ${state.tempStartTime.amOrPm}`),
7830
+ ]),
7831
+ ]),
7832
+ m('.timerange-time-section', { class: state.currentSelection === 'end' ? 'active' : '' }, [
7833
+ m('.timerange-label', 'End'),
7834
+ m('.timerange-time', [
7835
+ m('span.timerange-hours', {
7836
+ oncreate: (vnode) => {
7837
+ state.spanEndHours = vnode.dom;
7838
+ state.spanEndHours.innerHTML = addLeadingZero(state.tempEndTime.hours);
7839
+ },
7840
+ }, addLeadingZero(state.tempEndTime.hours)),
7841
+ ':',
7842
+ m('span.timerange-minutes', {
7843
+ oncreate: (vnode) => {
7844
+ state.spanEndMinutes = vnode.dom;
7845
+ state.spanEndMinutes.innerHTML = addLeadingZero(state.tempEndTime.minutes);
7846
+ },
7847
+ }, addLeadingZero(state.tempEndTime.minutes)),
7848
+ twelveHour &&
7849
+ m('span.timerange-ampm', {
7850
+ oncreate: (vnode) => {
7851
+ state.spanEndAmPm = vnode.dom;
7852
+ state.spanEndAmPm.innerHTML = ` ${state.tempEndTime.amOrPm}`;
7853
+ },
7854
+ }, ` ${state.tempEndTime.amOrPm}`),
7855
+ ]),
7856
+ ]),
7857
+ ]),
7858
+ // Clock picker (analog or digital mode)
7859
+ isAnalogMode
7860
+ ? m('.timepicker-analog-display', [
7861
+ m('.timepicker-plate', m(AnalogClock, {
7862
+ key: state.currentSelection, // Force component recreation when switching between start/end
7863
+ hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
7864
+ minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
7865
+ amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
7866
+ currentView: state.currentView,
7867
+ twelveHour,
7868
+ dialRadius,
7869
+ outerRadius,
7870
+ innerRadius,
7871
+ tickRadius,
7872
+ roundBy5,
7873
+ vibrate,
7874
+ onTimeChange: (hours, minutes) => {
7875
+ if (state.currentSelection === 'start') {
7876
+ state.tempStartTime = Object.assign(Object.assign({}, state.tempStartTime), { hours, minutes });
7877
+ if (state.spanStartHours)
7878
+ state.spanStartHours.innerHTML = addLeadingZero(hours);
7879
+ if (state.spanStartMinutes)
7880
+ state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
7881
+ }
7882
+ else {
7883
+ state.tempEndTime = Object.assign(Object.assign({}, state.tempEndTime), { hours, minutes });
7884
+ if (state.spanEndHours)
7885
+ state.spanEndHours.innerHTML = addLeadingZero(hours);
7886
+ if (state.spanEndMinutes)
7887
+ state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
7888
+ }
7889
+ },
7890
+ onViewChange: (view) => {
7891
+ state.currentView = view;
7892
+ },
7893
+ spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
7894
+ spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
7895
+ })),
7896
+ // Footer (inside analog display)
7897
+ m('.timepicker-footer', [
7898
+ m('button.btn-flat.timepicker-clear.waves-effect', {
7899
+ type: 'button',
7900
+ style: showClearBtn ? '' : 'visibility: hidden;',
7901
+ onclick: () => {
7902
+ state.isPickerOpen = false;
7903
+ },
7904
+ }, i18n.clear),
7905
+ m('.confirmation-btns', [
7906
+ m('button.btn-flat.timepicker-close.waves-effect', {
7907
+ type: 'button',
7908
+ onclick: () => {
7909
+ state.isPickerOpen = false;
7910
+ state.currentSelection = 'start';
7911
+ state.currentView = 'hours'; // Reset to hours view
7912
+ },
7913
+ }, i18n.cancel),
7914
+ m('button.btn-flat.timepicker-close.waves-effect', {
7915
+ type: 'button',
7916
+ onclick: () => {
7917
+ handleNextOrDone(validateRange, twelveHour, attrs.onchange);
7918
+ },
7919
+ }, state.currentSelection === 'start' ? i18n.next : i18n.done),
7920
+ ]),
7921
+ ]),
7922
+ ])
7923
+ : m('.timepicker-digital-mode', [
7924
+ m(DigitalClock, {
7925
+ key: state.currentSelection, // Force component recreation when switching between start/end
7926
+ hours: state.currentSelection === 'start' ? state.tempStartTime.hours : state.tempEndTime.hours,
7927
+ minutes: state.currentSelection === 'start' ? state.tempStartTime.minutes : state.tempEndTime.minutes,
7928
+ amOrPm: state.currentSelection === 'start' ? state.tempStartTime.amOrPm : state.tempEndTime.amOrPm,
7929
+ twelveHour,
7930
+ minuteStep,
7931
+ hourStep,
7932
+ minTime: effectiveMinTime,
7933
+ maxTime,
7934
+ onTimeChange: (hours, minutes, amOrPm) => {
7935
+ if (state.currentSelection === 'start') {
7936
+ state.tempStartTime = { hours, minutes, amOrPm };
7937
+ if (state.spanStartHours)
7938
+ state.spanStartHours.innerHTML = addLeadingZero(hours);
7939
+ if (state.spanStartMinutes)
7940
+ state.spanStartMinutes.innerHTML = addLeadingZero(minutes);
7941
+ if (state.spanStartAmPm)
7942
+ state.spanStartAmPm.innerHTML = ` ${amOrPm}`;
7943
+ }
7944
+ else {
7945
+ state.tempEndTime = { hours, minutes, amOrPm };
7946
+ if (state.spanEndHours)
7947
+ state.spanEndHours.innerHTML = addLeadingZero(hours);
7948
+ if (state.spanEndMinutes)
7949
+ state.spanEndMinutes.innerHTML = addLeadingZero(minutes);
7950
+ if (state.spanEndAmPm)
7951
+ state.spanEndAmPm.innerHTML = ` ${amOrPm}`;
7952
+ }
7953
+ },
7954
+ spanHours: state.currentSelection === 'start' ? state.spanStartHours : state.spanEndHours,
7955
+ spanMinutes: state.currentSelection === 'start' ? state.spanStartMinutes : state.spanEndMinutes,
7956
+ spanAmPm: state.currentSelection === 'start' ? state.spanStartAmPm : state.spanEndAmPm,
7957
+ }),
7958
+ // Footer (inside digital mode)
7959
+ m('.timepicker-footer', { key: 'timepicker-footer' }, [
7960
+ m('button.btn-flat.timepicker-clear.waves-effect', {
7961
+ type: 'button',
7962
+ style: showClearBtn ? '' : 'visibility: hidden;',
7963
+ onclick: () => {
7964
+ state.isPickerOpen = false;
7965
+ },
7966
+ }, i18n.clear),
7967
+ m('.confirmation-btns', [
7968
+ m('button.btn-flat.timepicker-close.waves-effect', {
7969
+ type: 'button',
7970
+ onclick: () => {
7971
+ state.isPickerOpen = false;
7972
+ state.currentSelection = 'start';
7973
+ state.currentView = 'hours'; // Reset to hours view
7974
+ },
7975
+ }, i18n.cancel),
7976
+ m('button.btn-flat.timepicker-close.waves-effect', {
7977
+ type: 'button',
7978
+ onclick: () => {
7979
+ handleNextOrDone(validateRange, twelveHour, attrs.onchange);
7980
+ },
7981
+ }, state.currentSelection === 'start' ? i18n.next : i18n.done),
7982
+ ]),
7983
+ ]),
7984
+ ]),
7985
+ ]);
7986
+ },
7987
+ };
7988
+ };
7989
+ const handleKeyDown = (e) => {
7990
+ if (e.key === 'Escape' && state.isPickerOpen) {
7991
+ state.isPickerOpen = false;
7992
+ clearPortal(state.portalContainerId);
7993
+ }
7994
+ };
7995
+ const renderPickerToPortal = (attrs) => {
7996
+ const mergedI18n = Object.assign(Object.assign({}, defaultI18n$2), attrs.i18n);
7997
+ const pickerModal = m('.timepicker-modal-wrapper', {
7998
+ style: {
7999
+ position: 'fixed',
8000
+ top: '0',
8001
+ left: '0',
8002
+ width: '100%',
8003
+ height: '100%',
8004
+ pointerEvents: 'auto',
8005
+ display: 'flex',
8006
+ alignItems: 'center',
8007
+ justifyContent: 'center',
8008
+ },
8009
+ }, [
8010
+ // Modal overlay
8011
+ m('.modal-overlay', {
8012
+ style: {
8013
+ position: 'absolute',
8014
+ top: '0',
8015
+ left: '0',
8016
+ width: '100%',
8017
+ height: '100%',
8018
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
8019
+ zIndex: '1002',
8020
+ },
8021
+ onclick: () => {
8022
+ state.isPickerOpen = false;
8023
+ state.currentSelection = 'start';
8024
+ state.currentView = 'hours'; // Reset to hours view
8025
+ },
8026
+ }),
8027
+ // Modal content
8028
+ m('.modal.timepicker-modal.open.timerange-modal', {
8029
+ style: {
8030
+ position: 'relative',
8031
+ zIndex: '1003',
8032
+ display: 'block',
8033
+ opacity: 1,
8034
+ top: 'auto',
8035
+ transform: 'scaleX(1) scaleY(1)',
8036
+ margin: '0 auto',
8037
+ },
8038
+ }, [
8039
+ m(TimeRangePickerModal, {
8040
+ i18n: mergedI18n,
8041
+ showClearBtn: attrs.showClearBtn || false,
8042
+ twelveHour: attrs.twelveHour !== undefined ? attrs.twelveHour : true,
8043
+ minuteStep: attrs.minuteStep || 5,
8044
+ hourStep: attrs.hourStep || 1,
8045
+ minTime: attrs.minTime,
8046
+ maxTime: attrs.maxTime,
8047
+ validateRange: attrs.validateRange || false,
8048
+ onchange: attrs.onchange,
8049
+ displayMode: attrs.displayMode,
8050
+ dialRadius: attrs.dialRadius,
8051
+ outerRadius: attrs.outerRadius,
8052
+ innerRadius: attrs.innerRadius,
8053
+ tickRadius: attrs.tickRadius,
8054
+ roundBy5: attrs.roundBy5,
8055
+ vibrate: attrs.vibrate,
8056
+ }),
8057
+ ]),
8058
+ ]);
8059
+ renderToPortal(state.portalContainerId, pickerModal, 1004);
8060
+ };
8061
+ return {
8062
+ oninit: (vnode) => {
8063
+ const attrs = vnode.attrs;
8064
+ const twelveHour = attrs.twelveHour !== undefined ? attrs.twelveHour : true;
8065
+ const startTime = parseTime(attrs.startValue || '', twelveHour);
8066
+ const endTime = parseTime(attrs.endValue || '', twelveHour);
8067
+ state = {
8068
+ id: uniqueId(),
8069
+ currentSelection: 'start',
8070
+ startTime,
8071
+ endTime,
8072
+ tempStartTime: Object.assign({}, startTime),
8073
+ tempEndTime: Object.assign({}, endTime),
8074
+ isPickerOpen: false,
8075
+ portalContainerId: `timerange-portal-${uniqueId()}`,
8076
+ currentView: 'hours',
8077
+ };
8078
+ document.addEventListener('keydown', handleKeyDown);
8079
+ },
8080
+ onremove: () => {
8081
+ document.removeEventListener('keydown', handleKeyDown);
8082
+ if (state.isPickerOpen) {
8083
+ clearPortal(state.portalContainerId);
8084
+ }
8085
+ },
8086
+ onupdate: ({ attrs }) => {
8087
+ if (state.isPickerOpen) {
8088
+ renderPickerToPortal(attrs);
8089
+ }
8090
+ else {
8091
+ clearPortal(state.portalContainerId);
8092
+ }
8093
+ },
8094
+ view: ({ attrs }) => {
8095
+ const { id = state.id, label, placeholder = 'Select time range', disabled, readonly, required, iconName, helperText, className: cn1, class: cn2, twelveHour = true, } = attrs;
8096
+ const className = cn1 || cn2 || 'col s12';
8097
+ const displayValue = state.startTime && state.endTime
8098
+ ? `${formatTime(state.startTime, twelveHour)} - ${formatTime(state.endTime, twelveHour)}`
8099
+ : state.startTime
8100
+ ? `${formatTime(state.startTime, twelveHour)} - ...`
8101
+ : '';
8102
+ return m('.input-field', { className }, [
8103
+ iconName && m('i.material-icons.prefix', iconName),
8104
+ // Display input
8105
+ m('input.timerangepicker', {
8106
+ id,
8107
+ type: 'text',
8108
+ value: displayValue,
8109
+ placeholder,
8110
+ readonly: true,
8111
+ disabled,
8112
+ required,
8113
+ onclick: () => {
8114
+ if (!disabled && !readonly) {
8115
+ state.isPickerOpen = true;
8116
+ state.currentSelection = 'start';
8117
+ state.currentView = 'hours'; // Reset to hours view when opening
8118
+ state.tempStartTime = Object.assign({}, state.startTime);
8119
+ state.tempEndTime = Object.assign({}, state.endTime);
8120
+ }
8121
+ },
8122
+ }),
8123
+ label &&
8124
+ m('label', {
8125
+ for: id,
8126
+ class: displayValue || placeholder ? 'active' : '',
8127
+ }, label),
8128
+ helperText && m('span.helper-text', helperText),
8129
+ ]);
8130
+ },
8131
+ };
8132
+ };
8133
+
7216
8134
  class Toast {
7217
8135
  constructor(options = {}) {
7218
8136
  this.options = Object.assign(Object.assign({}, Toast.defaults), options);
@@ -9816,6 +10734,7 @@ const isValidationError = (result) => !isValidationSuccess(result);
9816
10734
  // ============================================================================
9817
10735
  // All types are already exported via individual export declarations above
9818
10736
 
10737
+ exports.AnalogClock = AnalogClock;
9819
10738
  exports.AnchorItem = AnchorItem;
9820
10739
  exports.Autocomplete = Autocomplete;
9821
10740
  exports.Breadcrumb = Breadcrumb;
@@ -9832,6 +10751,7 @@ exports.Collection = Collection;
9832
10751
  exports.ColorInput = ColorInput;
9833
10752
  exports.DataTable = DataTable;
9834
10753
  exports.DatePicker = DatePicker;
10754
+ exports.DigitalClock = DigitalClock;
9835
10755
  exports.DoubleRangeSlider = DoubleRangeSlider;
9836
10756
  exports.Dropdown = Dropdown;
9837
10757
  exports.EmailInput = EmailInput;
@@ -9884,6 +10804,7 @@ exports.ThemeManager = ThemeManager;
9884
10804
  exports.ThemeSwitcher = ThemeSwitcher;
9885
10805
  exports.ThemeToggle = ThemeToggle;
9886
10806
  exports.TimePicker = TimePicker;
10807
+ exports.TimeRangePicker = TimeRangePicker;
9887
10808
  exports.Timeline = Timeline;
9888
10809
  exports.Toast = Toast;
9889
10810
  exports.ToastComponent = ToastComponent;
@@ -9892,20 +10813,29 @@ exports.TooltipComponent = TooltipComponent;
9892
10813
  exports.TreeView = TreeView;
9893
10814
  exports.UrlInput = UrlInput;
9894
10815
  exports.Wizard = Wizard;
10816
+ exports.addLeadingZero = addLeadingZero;
9895
10817
  exports.clearPortal = clearPortal;
9896
10818
  exports.createBreadcrumb = createBreadcrumb;
10819
+ exports.formatTime = formatTime;
10820
+ exports.generateHourOptions = generateHourOptions;
10821
+ exports.generateMinuteOptions = generateMinuteOptions;
9897
10822
  exports.getDropdownStyles = getDropdownStyles;
9898
10823
  exports.getPortalContainer = getPortalContainer;
9899
10824
  exports.initPushpins = initPushpins;
9900
10825
  exports.initTooltips = initTooltips;
9901
10826
  exports.isNumeric = isNumeric;
10827
+ exports.isTimeDisabled = isTimeDisabled;
9902
10828
  exports.isValidationError = isValidationError;
9903
10829
  exports.isValidationSuccess = isValidationSuccess;
9904
10830
  exports.padLeft = padLeft;
10831
+ exports.parseTime = parseTime;
9905
10832
  exports.range = range;
9906
10833
  exports.releasePortalContainer = releasePortalContainer;
9907
10834
  exports.renderToPortal = renderToPortal;
10835
+ exports.scrollToValue = scrollToValue;
10836
+ exports.snapToNearestItem = snapToNearestItem;
9908
10837
  exports.sortOptions = sortOptions;
10838
+ exports.timeToMinutes = timeToMinutes;
9909
10839
  exports.toast = toast;
9910
10840
  exports.uniqueId = uniqueId;
9911
10841
  exports.uuid4 = uuid4;