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