mithril-materialized 3.5.10 → 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
@@ -64,6 +64,28 @@ const uuid4 = () => {
64
64
  };
65
65
  /** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
66
66
  const isNumeric = (n) => !isNaN(parseFloat(n)) && isFinite(n);
67
+ /**
68
+ * Sort options array based on sorting configuration
69
+ * @param options - Array of options to sort
70
+ * @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
71
+ * @returns Sorted array (or original if 'none' or undefined)
72
+ */
73
+ const sortOptions = (options, sortConfig) => {
74
+ if (!sortConfig || sortConfig === 'none') {
75
+ return options;
76
+ }
77
+ const sorted = [...options]; // Create a copy to avoid mutating original
78
+ if (typeof sortConfig === 'function') {
79
+ return sorted.sort(sortConfig);
80
+ }
81
+ // Sort by label, fallback to id if no label
82
+ return sorted.sort((a, b) => {
83
+ const aLabel = (a.label || a.id.toString()).toLowerCase();
84
+ const bLabel = (b.label || b.id.toString()).toLowerCase();
85
+ const comparison = aLabel.localeCompare(bLabel);
86
+ return sortConfig === 'asc' ? comparison : -comparison;
87
+ });
88
+ };
67
89
  /**
68
90
  * Pad left, default width 2 with a '0'
69
91
  *
@@ -1586,7 +1608,7 @@ const Collection = () => {
1586
1608
  };
1587
1609
  };
1588
1610
 
1589
- const defaultI18n$3 = {
1611
+ const defaultI18n$4 = {
1590
1612
  cancel: 'Cancel',
1591
1613
  clear: 'Clear',
1592
1614
  done: 'Ok',
@@ -1694,9 +1716,9 @@ const DatePicker = () => {
1694
1716
  else if (attrs.displayFormat) {
1695
1717
  finalFormat = attrs.displayFormat;
1696
1718
  }
1697
- 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);
1698
1720
  // Merge i18n properly
1699
- merged.i18n = Object.assign(Object.assign({}, defaultI18n$3), attrs.i18n);
1721
+ merged.i18n = Object.assign(Object.assign({}, defaultI18n$4), attrs.i18n);
1700
1722
  return merged;
1701
1723
  };
1702
1724
  const toString = (date, format) => {
@@ -4497,7 +4519,7 @@ const Dropdown = () => {
4497
4519
  opacity: 1,
4498
4520
  };
4499
4521
  };
4500
- const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
4522
+ const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
4501
4523
  if (!state.isInsideModal)
4502
4524
  return;
4503
4525
  // Clean up existing portal
@@ -4541,7 +4563,7 @@ const Dropdown = () => {
4541
4563
  // Create dropdown with proper positioning
4542
4564
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
4543
4565
  tabindex: 0,
4544
- style: getPortalStyles(state.inputRef),
4566
+ style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
4545
4567
  oncreate: ({ dom }) => {
4546
4568
  state.dropdownRef = dom;
4547
4569
  },
@@ -4605,7 +4627,7 @@ const Dropdown = () => {
4605
4627
  state.focusedIndex = -1;
4606
4628
  handleSelection(item.id);
4607
4629
  }
4608
- });
4630
+ }, attrs.maxHeight);
4609
4631
  }
4610
4632
  return m('.dropdown-wrapper.input-field', { className, key, style }, [
4611
4633
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
@@ -4653,7 +4675,7 @@ const Dropdown = () => {
4653
4675
  onremove: () => {
4654
4676
  state.dropdownRef = null;
4655
4677
  },
4656
- style: getDropdownStyles(state.inputRef, true, items, true),
4678
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, items, true)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
4657
4679
  }, items.map((item) => {
4658
4680
  if (item.divider) {
4659
4681
  return m('li.divider');
@@ -5274,45 +5296,417 @@ const Parallax = () => {
5274
5296
  };
5275
5297
  };
5276
5298
 
5277
- const defaultI18n$2 = {
5278
- cancel: 'Cancel',
5279
- clear: 'Clear',
5280
- done: 'Ok',
5299
+ /**
5300
+ * Shared utility functions for TimePicker and TimeRangePicker components
5301
+ */
5302
+ const addLeadingZero = (num) => {
5303
+ return (num < 10 ? '0' : '') + num;
5281
5304
  };
5282
- const defaultOptions = {
5283
- dialRadius: 135,
5284
- outerRadius: 105,
5285
- innerRadius: 70,
5286
- tickRadius: 20,
5287
- duration: 350,
5288
- container: null,
5289
- defaultTime: 'now',
5290
- fromNow: 0,
5291
- showClearBtn: false,
5292
- i18n: defaultI18n$2,
5293
- autoClose: false,
5294
- twelveHour: true,
5295
- vibrate: true,
5296
- roundBy5: false,
5297
- onOpen: () => { },
5298
- onOpenStart: () => { },
5299
- onOpenEnd: () => { },
5300
- onCloseStart: () => { },
5301
- onCloseEnd: () => { },
5302
- 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
+ }
5303
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);
5416
+ };
5417
+
5304
5418
  /**
5305
- * 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
+ * ```
5306
5434
  */
5307
- const TimePicker = () => {
5308
- let state;
5309
- let options;
5310
- const addLeadingZero = (num) => {
5311
- 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
+ },
5312
5680
  };
5313
- const createSVGEl = (name) => {
5314
- const svgNS = 'http://www.w3.org/2000/svg';
5315
- 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,
5316
5710
  };
5317
5711
  const getPos = (e) => {
5318
5712
  const touchEvent = e;
@@ -5322,321 +5716,422 @@ const TimePicker = () => {
5322
5716
  }
5323
5717
  return { x: mouseEvent.clientX, y: mouseEvent.clientY };
5324
5718
  };
5325
- const vibrate = () => {
5719
+ const vibrate = (attrs) => {
5326
5720
  if (state.vibrateTimer) {
5327
5721
  clearTimeout(state.vibrateTimer);
5328
5722
  }
5329
- if (options.vibrate && navigator.vibrate) {
5723
+ if (attrs.vibrate && navigator.vibrate) {
5330
5724
  navigator.vibrate(10);
5331
5725
  state.vibrateTimer = window.setTimeout(() => {
5332
5726
  state.vibrateTimer = undefined;
5333
5727
  }, 100);
5334
5728
  }
5335
5729
  };
5336
- const handleClockClickStart = (e) => {
5337
- e.preventDefault();
5338
- if (!state.plate)
5339
- return;
5340
- const clockPlateBR = state.plate.getBoundingClientRect();
5341
- const offset = { x: clockPlateBR.left, y: clockPlateBR.top };
5342
- state.x0 = offset.x + options.dialRadius;
5343
- state.y0 = offset.y + options.dialRadius;
5344
- state.moved = false;
5345
- const clickPos = getPos(e);
5346
- state.dx = clickPos.x - state.x0;
5347
- state.dy = clickPos.y - state.y0;
5348
- setHand(state.dx, state.dy, options.roundBy5);
5349
- document.addEventListener('mousemove', handleDocumentClickMove);
5350
- document.addEventListener('touchmove', handleDocumentClickMove);
5351
- document.addEventListener('mouseup', handleDocumentClickEnd);
5352
- document.addEventListener('touchend', handleDocumentClickEnd);
5353
- };
5354
- const handleDocumentClickMove = (e) => {
5355
- e.preventDefault();
5356
- const clickPos = getPos(e);
5357
- const x = clickPos.x - state.x0;
5358
- const y = clickPos.y - state.y0;
5359
- state.moved = true;
5360
- setHand(x, y, options.roundBy5);
5361
- m.redraw();
5362
- };
5363
- const handleDocumentClickEnd = (e) => {
5364
- e.preventDefault();
5365
- document.removeEventListener('mouseup', handleDocumentClickEnd);
5366
- document.removeEventListener('touchend', handleDocumentClickEnd);
5367
- document.removeEventListener('mousemove', handleDocumentClickMove);
5368
- document.removeEventListener('touchmove', handleDocumentClickMove);
5369
- const clickPos = getPos(e);
5370
- const x = clickPos.x - state.x0;
5371
- const y = clickPos.y - state.y0;
5372
- if (state.moved && x === state.dx && y === state.dy) {
5373
- setHand(x, y);
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;
5734
+ let radian = Math.atan2(x, -y);
5735
+ const isHours = attrs.currentView === 'hours';
5736
+ const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
5737
+ const z = Math.sqrt(x * x + y * y);
5738
+ const inner = isHours && z < (outerRadius + innerRadius) / 2;
5739
+ let radius = inner ? innerRadius : outerRadius;
5740
+ if (attrs.twelveHour) {
5741
+ radius = outerRadius;
5374
5742
  }
5375
- if (state.currentView === 'hours') {
5376
- showView('minutes', options.duration / 2);
5743
+ if (radian < 0) {
5744
+ radian = Math.PI * 2 + radian;
5377
5745
  }
5378
- else if (options.autoClose) {
5379
- if (state.minutesView) {
5380
- state.minutesView.classList.add('timepicker-dial-out');
5746
+ let value = Math.round(radian / unit);
5747
+ radian = value * unit;
5748
+ if (attrs.twelveHour) {
5749
+ if (isHours) {
5750
+ if (value === 0)
5751
+ value = 12;
5752
+ }
5753
+ else {
5754
+ if (roundBy5)
5755
+ value *= 5;
5756
+ if (value === 60)
5757
+ value = 0;
5381
5758
  }
5382
- setTimeout(() => {
5383
- done();
5384
- }, options.duration / 2);
5385
- }
5386
- if (options.onSelect) {
5387
- options.onSelect(state.hours, state.minutes);
5388
5759
  }
5389
- m.redraw();
5390
- };
5391
- const updateTimeFromInput = (inputValue) => {
5392
- let value = ((inputValue || options.defaultTime || '') + '').split(':');
5393
- let amPmWasProvided = false;
5394
- if (options.twelveHour && value.length > 1) {
5395
- if (value[1].toUpperCase().indexOf('AM') > -1) {
5396
- state.amOrPm = 'AM';
5397
- amPmWasProvided = true;
5760
+ else {
5761
+ if (isHours) {
5762
+ if (value === 12)
5763
+ value = 0;
5764
+ value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
5398
5765
  }
5399
- else if (value[1].toUpperCase().indexOf('PM') > -1) {
5400
- state.amOrPm = 'PM';
5401
- amPmWasProvided = true;
5766
+ else {
5767
+ if (roundBy5)
5768
+ value *= 5;
5769
+ if (value === 60)
5770
+ value = 0;
5402
5771
  }
5403
- value[1] = value[1].replace('AM', '').replace('PM', '').trim();
5404
5772
  }
5405
- if (value[0] === 'now') {
5406
- const now = new Date(+new Date() + options.fromNow);
5407
- value = [now.getHours().toString(), now.getMinutes().toString()];
5408
- if (options.twelveHour) {
5409
- state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
5410
- amPmWasProvided = false; // For 'now', we need to do conversion
5411
- }
5773
+ const currentValue = isHours ? attrs.hours : attrs.minutes;
5774
+ if (currentValue !== value) {
5775
+ vibrate(attrs);
5412
5776
  }
5413
- let hours = +value[0] || 0;
5414
- let minutes = +value[1] || 0;
5415
- if (options.twelveHour) {
5416
- if (!amPmWasProvided) {
5417
- // No AM/PM was provided, assume this is 24-hour format input - convert it
5418
- if (hours >= 12) {
5419
- state.amOrPm = 'PM';
5420
- if (hours > 12) {
5421
- hours = hours - 12;
5422
- }
5423
- }
5424
- else {
5425
- state.amOrPm = 'AM';
5426
- if (hours === 0) {
5427
- hours = 12;
5428
- }
5429
- }
5430
- }
5431
- else {
5432
- // AM/PM was provided, hours are already in 12-hour format
5433
- // Just handle midnight/noon edge cases
5434
- if (hours === 0 && state.amOrPm === 'AM') {
5435
- hours = 12;
5436
- }
5777
+ // Update the value
5778
+ if (isHours) {
5779
+ attrs.onTimeChange(value, attrs.minutes);
5780
+ if (attrs.spanHours) {
5781
+ attrs.spanHours.innerHTML = addLeadingZero(value);
5437
5782
  }
5438
5783
  }
5439
- state.hours = hours;
5440
- state.minutes = minutes;
5441
- if (state.spanHours) {
5442
- state.spanHours.innerHTML = addLeadingZero(state.hours);
5784
+ else {
5785
+ attrs.onTimeChange(attrs.hours, value);
5786
+ if (attrs.spanMinutes) {
5787
+ attrs.spanMinutes.innerHTML = addLeadingZero(value);
5788
+ }
5443
5789
  }
5444
- if (state.spanMinutes) {
5445
- state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
5790
+ // Set clock hand position
5791
+ if (state.hand && state.bg) {
5792
+ const cx1 = Math.sin(radian) * (radius - tickRadius);
5793
+ const cy1 = -Math.cos(radian) * (radius - tickRadius);
5794
+ const cx2 = Math.sin(radian) * radius;
5795
+ const cy2 = -Math.cos(radian) * radius;
5796
+ state.hand.setAttribute('x2', cx1.toString());
5797
+ state.hand.setAttribute('y2', cy1.toString());
5798
+ state.bg.setAttribute('cx', cx2.toString());
5799
+ state.bg.setAttribute('cy', cy2.toString());
5446
5800
  }
5447
- updateAmPmView();
5448
5801
  };
5449
- const updateAmPmView = () => {
5450
- if (options.twelveHour && state.amBtn && state.pmBtn) {
5451
- state.amBtn.classList.toggle('text-primary', state.amOrPm === 'AM');
5452
- state.pmBtn.classList.toggle('text-primary', state.amOrPm === 'PM');
5453
- }
5454
- };
5455
- const showView = (view, delay) => {
5456
- const isHours = view === 'hours';
5457
- const nextView = isHours ? state.hoursView : state.minutesView;
5458
- const hideView = isHours ? state.minutesView : state.hoursView;
5459
- state.currentView = view;
5460
- if (state.spanHours) {
5461
- state.spanHours.classList.toggle('text-primary', isHours);
5462
- }
5463
- if (state.spanMinutes) {
5464
- state.spanMinutes.classList.toggle('text-primary', !isHours);
5465
- }
5466
- if (hideView) {
5467
- hideView.classList.add('timepicker-dial-out');
5468
- }
5469
- if (nextView) {
5470
- nextView.style.visibility = 'visible';
5471
- nextView.classList.remove('timepicker-dial-out');
5472
- }
5473
- resetClock(delay);
5474
- if (state.toggleViewTimer) {
5475
- clearTimeout(state.toggleViewTimer);
5476
- }
5477
- state.toggleViewTimer = window.setTimeout(() => {
5478
- if (hideView) {
5479
- hideView.style.visibility = 'hidden';
5480
- }
5481
- }, options.duration);
5482
- };
5483
- const resetClock = (delay) => {
5484
- const view = state.currentView;
5485
- const value = state[view];
5802
+ const resetClock = (attrs) => {
5803
+ const view = attrs.currentView;
5804
+ const value = view === 'hours' ? attrs.hours : attrs.minutes;
5486
5805
  const isHours = view === 'hours';
5487
5806
  const unit = Math.PI / (isHours ? 6 : 30);
5488
5807
  const radian = value * unit;
5489
- const radius = isHours && value > 0 && value < 13 ? options.innerRadius : options.outerRadius;
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
+ }
5490
5816
  const x = Math.sin(radian) * radius;
5491
5817
  const y = -Math.cos(radian) * radius;
5492
- if (delay && state.canvas) {
5493
- state.canvas.classList.add('timepicker-canvas-out');
5494
- setTimeout(() => {
5495
- if (state.canvas) {
5496
- state.canvas.classList.remove('timepicker-canvas-out');
5818
+ setHand(x, y, attrs);
5819
+ };
5820
+ const handleClockClickStart = (e, attrs) => {
5821
+ e.preventDefault();
5822
+ if (!state.plate)
5823
+ return;
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
+ }
5497
5886
  }
5498
- setHand(x, y);
5499
- }, delay);
5500
- }
5501
- else {
5502
- setHand(x, y);
5503
- }
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
+ };
5504
5905
  };
5505
- const setHand = (x, y, roundBy5, _dragging) => {
5506
- let radian = Math.atan2(x, -y);
5507
- const isHours = state.currentView === 'hours';
5508
- const unit = Math.PI / (isHours || roundBy5 ? 6 : 30);
5509
- const z = Math.sqrt(x * x + y * y);
5510
- const inner = isHours && z < (options.outerRadius + options.innerRadius) / 2;
5511
- let radius = inner ? options.innerRadius : options.outerRadius;
5512
- if (options.twelveHour) {
5513
- radius = options.outerRadius;
5514
- }
5515
- if (radian < 0) {
5516
- radian = Math.PI * 2 + radian;
5517
- }
5518
- let value = Math.round(radian / unit);
5519
- radian = value * unit;
5520
- if (options.twelveHour) {
5521
- if (isHours) {
5522
- if (value === 0)
5523
- value = 12;
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;
5524
6080
  }
5525
- else {
5526
- if (roundBy5)
5527
- value *= 5;
5528
- if (value === 60)
5529
- value = 0;
6081
+ else if (value[1].toUpperCase().indexOf('PM') > -1) {
6082
+ state.amOrPm = 'PM';
6083
+ amPmWasProvided = true;
5530
6084
  }
6085
+ value[1] = value[1].replace('AM', '').replace('PM', '').trim();
5531
6086
  }
5532
- else {
5533
- if (isHours) {
5534
- if (value === 12)
5535
- value = 0;
5536
- value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
5537
- }
5538
- else {
5539
- if (roundBy5)
5540
- value *= 5;
5541
- if (value === 60)
5542
- value = 0;
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
5543
6093
  }
5544
6094
  }
5545
- if (state[state.currentView] !== value) {
5546
- vibrate();
5547
- }
5548
- state[state.currentView] = value;
5549
- if (isHours && state.spanHours) {
5550
- state.spanHours.innerHTML = addLeadingZero(value);
5551
- }
5552
- else if (!isHours && state.spanMinutes) {
5553
- state.spanMinutes.innerHTML = addLeadingZero(value);
5554
- }
5555
- // Set clock hand position
5556
- if (state.hand && state.bg) {
5557
- const cx1 = Math.sin(radian) * (radius - options.tickRadius);
5558
- const cy1 = -Math.cos(radian) * (radius - options.tickRadius);
5559
- const cx2 = Math.sin(radian) * radius;
5560
- const cy2 = -Math.cos(radian) * radius;
5561
- state.hand.setAttribute('x2', cx1.toString());
5562
- state.hand.setAttribute('y2', cy1.toString());
5563
- state.bg.setAttribute('cx', cx2.toString());
5564
- state.bg.setAttribute('cy', cy2.toString());
5565
- }
5566
- };
5567
- const buildSVGClock = () => {
5568
- if (!state.canvas)
5569
- return;
5570
- const dialRadius = options.dialRadius;
5571
- const tickRadius = options.tickRadius;
5572
- const diameter = dialRadius * 2;
5573
- const svg = createSVGEl('svg');
5574
- svg.setAttribute('class', 'timepicker-svg');
5575
- svg.setAttribute('width', diameter.toString());
5576
- svg.setAttribute('height', diameter.toString());
5577
- const g = createSVGEl('g');
5578
- g.setAttribute('transform', `translate(${dialRadius},${dialRadius})`);
5579
- const bearing = createSVGEl('circle');
5580
- bearing.setAttribute('class', 'timepicker-canvas-bearing');
5581
- bearing.setAttribute('cx', '0');
5582
- bearing.setAttribute('cy', '0');
5583
- bearing.setAttribute('r', '4');
5584
- const hand = createSVGEl('line');
5585
- hand.setAttribute('x1', '0');
5586
- hand.setAttribute('y1', '0');
5587
- const bg = createSVGEl('circle');
5588
- bg.setAttribute('class', 'timepicker-canvas-bg');
5589
- bg.setAttribute('r', tickRadius.toString());
5590
- g.appendChild(hand);
5591
- g.appendChild(bg);
5592
- g.appendChild(bearing);
5593
- svg.appendChild(g);
5594
- state.canvas.appendChild(svg);
5595
- state.hand = hand;
5596
- state.bg = bg;
5597
- state.bearing = bearing;
5598
- state.g = g;
5599
- };
5600
- const buildHoursView = () => {
5601
- if (!state.hoursView)
5602
- return;
6095
+ let hours = +value[0] || 0;
6096
+ let minutes = +value[1] || 0;
5603
6097
  if (options.twelveHour) {
5604
- for (let i = 1; i < 13; i++) {
5605
- const tick = document.createElement('div');
5606
- tick.className = 'timepicker-tick';
5607
- const radian = (i / 6) * Math.PI;
5608
- const radius = options.outerRadius;
5609
- tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
5610
- tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
5611
- tick.innerHTML = i === 0 ? '00' : i.toString();
5612
- state.hoursView.appendChild(tick);
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
+ }
5613
6112
  }
5614
- }
5615
- else {
5616
- for (let i = 0; i < 24; i++) {
5617
- const tick = document.createElement('div');
5618
- tick.className = 'timepicker-tick';
5619
- const radian = (i / 6) * Math.PI;
5620
- const inner = i > 0 && i < 13;
5621
- const radius = inner ? options.innerRadius : options.outerRadius;
5622
- tick.style.left = options.dialRadius + Math.sin(radian) * radius - options.tickRadius + 'px';
5623
- tick.style.top = options.dialRadius - Math.cos(radian) * radius - options.tickRadius + 'px';
5624
- tick.innerHTML = i === 0 ? '00' : i.toString();
5625
- state.hoursView.appendChild(tick);
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
+ }
5626
6119
  }
5627
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();
5628
6130
  };
5629
- const buildMinutesView = () => {
5630
- if (!state.minutesView)
5631
- return;
5632
- for (let i = 0; i < 60; i += 5) {
5633
- const tick = document.createElement('div');
5634
- tick.className = 'timepicker-tick';
5635
- const radian = (i / 30) * Math.PI;
5636
- tick.style.left = options.dialRadius + Math.sin(radian) * options.outerRadius - options.tickRadius + 'px';
5637
- tick.style.top = options.dialRadius - Math.cos(radian) * options.outerRadius - options.tickRadius + 'px';
5638
- tick.innerHTML = addLeadingZero(i);
5639
- 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');
5640
6135
  }
5641
6136
  };
5642
6137
  const handleAmPmClick = (ampm) => {
@@ -5648,7 +6143,7 @@ const TimePicker = () => {
5648
6143
  return;
5649
6144
  state.isOpen = true;
5650
6145
  updateTimeFromInput(inputValue);
5651
- showView('hours');
6146
+ state.currentView = 'hours';
5652
6147
  if (options.onOpen)
5653
6148
  options.onOpen();
5654
6149
  if (options.onOpenStart)
@@ -5682,6 +6177,7 @@ const TimePicker = () => {
5682
6177
  return {
5683
6178
  view: ({ attrs }) => {
5684
6179
  const { i18n, showClearBtn } = attrs;
6180
+ const isDigitalMode = options.displayMode === 'digital';
5685
6181
  return [
5686
6182
  m('.modal-content.timepicker-container', [
5687
6183
  m('.timepicker-digital-display', [
@@ -5689,7 +6185,12 @@ const TimePicker = () => {
5689
6185
  m('.timepicker-display-column', [
5690
6186
  m('span.timepicker-span-hours', {
5691
6187
  class: state.currentView === 'hours' ? 'text-primary' : '',
5692
- onclick: () => showView('hours'),
6188
+ onclick: () => {
6189
+ if (!isDigitalMode) {
6190
+ state.currentView = 'hours';
6191
+ m.redraw();
6192
+ }
6193
+ },
5693
6194
  oncreate: (vnode) => {
5694
6195
  state.spanHours = vnode.dom;
5695
6196
  },
@@ -5697,7 +6198,12 @@ const TimePicker = () => {
5697
6198
  ':',
5698
6199
  m('span.timepicker-span-minutes', {
5699
6200
  class: state.currentView === 'minutes' ? 'text-primary' : '',
5700
- onclick: () => showView('minutes'),
6201
+ onclick: () => {
6202
+ if (!isDigitalMode) {
6203
+ state.currentView = 'minutes';
6204
+ m.redraw();
6205
+ }
6206
+ },
5701
6207
  oncreate: (vnode) => {
5702
6208
  state.spanMinutes = vnode.dom;
5703
6209
  },
@@ -5728,66 +6234,108 @@ const TimePicker = () => {
5728
6234
  ]),
5729
6235
  ]),
5730
6236
  ]),
5731
- m('.timepicker-analog-display', [
5732
- m('.timepicker-plate', {
5733
- oncreate: (vnode) => {
5734
- state.plate = vnode.dom;
5735
- state.plate.addEventListener('mousedown', handleClockClickStart);
5736
- state.plate.addEventListener('touchstart', handleClockClickStart);
5737
- },
5738
- onremove: () => {
5739
- if (state.plate) {
5740
- state.plate.removeEventListener('mousedown', handleClockClickStart);
5741
- state.plate.removeEventListener('touchstart', handleClockClickStart);
5742
- }
5743
- },
5744
- }, [
5745
- m('.timepicker-canvas', {
5746
- oncreate: (vnode) => {
5747
- state.canvas = vnode.dom;
5748
- buildSVGClock();
5749
- // Position the hand after SVG is built
5750
- setTimeout(() => resetClock(), 10);
5751
- },
5752
- }),
5753
- m('.timepicker-dial.timepicker-hours', {
5754
- oncreate: (vnode) => {
5755
- state.hoursView = vnode.dom;
5756
- 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);
5757
6256
  },
6257
+ spanHours: state.spanHours,
6258
+ spanMinutes: state.spanMinutes,
6259
+ spanAmPm: state.spanAmPm,
5758
6260
  }),
5759
- m('.timepicker-dial.timepicker-minutes.timepicker-dial-out', {
6261
+ m('.timepicker-footer', {
5760
6262
  oncreate: (vnode) => {
5761
- state.minutesView = vnode.dom;
5762
- buildMinutesView();
6263
+ state.footer = vnode.dom;
5763
6264
  },
5764
- }),
5765
- ]),
5766
- m('.timepicker-footer', {
5767
- oncreate: (vnode) => {
5768
- state.footer = vnode.dom;
5769
- },
5770
- }, [
5771
- m('button.btn-flat.timepicker-clear.waves-effect', {
5772
- type: 'button',
5773
- tabindex: options.twelveHour ? '3' : '1',
5774
- style: showClearBtn ? '' : 'visibility: hidden;',
5775
- onclick: () => clear(),
5776
- }, i18n.clear),
5777
- m('.confirmation-btns', [
5778
- m('button.btn-flat.timepicker-close.waves-effect', {
6265
+ }, [
6266
+ m('button.btn-flat.timepicker-clear.waves-effect', {
5779
6267
  type: 'button',
5780
6268
  tabindex: options.twelveHour ? '3' : '1',
5781
- onclick: () => close(),
5782
- }, i18n.cancel),
5783
- 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', {
5784
6320
  type: 'button',
5785
6321
  tabindex: options.twelveHour ? '3' : '1',
5786
- onclick: () => done(),
5787
- }, 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
+ ]),
5788
6337
  ]),
5789
6338
  ]),
5790
- ]),
5791
6339
  ]),
5792
6340
  ];
5793
6341
  },
@@ -5800,7 +6348,7 @@ const TimePicker = () => {
5800
6348
  m.redraw();
5801
6349
  }
5802
6350
  };
5803
- const renderPickerToPortal = (attrs) => {
6351
+ const renderPickerToPortal = () => {
5804
6352
  const pickerModal = m('.timepicker-modal-wrapper', {
5805
6353
  style: {
5806
6354
  position: 'fixed',
@@ -5861,11 +6409,6 @@ const TimePicker = () => {
5861
6409
  minutes: 0,
5862
6410
  amOrPm: 'AM',
5863
6411
  currentView: 'hours',
5864
- moved: false,
5865
- x0: 0,
5866
- y0: 0,
5867
- dx: 0,
5868
- dy: 0,
5869
6412
  portalContainerId: `timepicker-portal-${uniqueId()}`,
5870
6413
  };
5871
6414
  // Handle value after options are set
@@ -5877,27 +6420,17 @@ const TimePicker = () => {
5877
6420
  },
5878
6421
  onremove: () => {
5879
6422
  // Cleanup
5880
- if (state.toggleViewTimer) {
5881
- clearTimeout(state.toggleViewTimer);
5882
- }
5883
- if (state.vibrateTimer) {
5884
- clearTimeout(state.vibrateTimer);
5885
- }
5886
- document.removeEventListener('mousemove', handleDocumentClickMove);
5887
- document.removeEventListener('touchmove', handleDocumentClickMove);
5888
- document.removeEventListener('mouseup', handleDocumentClickEnd);
5889
- document.removeEventListener('touchend', handleDocumentClickEnd);
5890
6423
  document.removeEventListener('keydown', handleKeyDown);
5891
6424
  // Clean up portal if picker was open
5892
6425
  if (state.isOpen) {
5893
6426
  clearPortal(state.portalContainerId);
5894
6427
  }
5895
6428
  },
5896
- onupdate: (vnode) => {
5897
- const { useModal = true } = vnode.attrs;
6429
+ onupdate: ({ attrs }) => {
6430
+ const { useModal = true } = attrs;
5898
6431
  // Only render to portal when using modal mode
5899
6432
  if (useModal && state.isOpen) {
5900
- renderPickerToPortal(vnode.attrs);
6433
+ renderPickerToPortal();
5901
6434
  }
5902
6435
  else {
5903
6436
  clearPortal(state.portalContainerId);
@@ -6427,7 +6960,7 @@ const Select = () => {
6427
6960
  // Create dropdown with proper positioning
6428
6961
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6429
6962
  tabindex: 0,
6430
- style: getPortalStyles(state.inputRef),
6963
+ style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
6431
6964
  oncreate: ({ dom }) => {
6432
6965
  state.dropdownRef = dom;
6433
6966
  },
@@ -6496,7 +7029,8 @@ const Select = () => {
6496
7029
  selectedIds = state.internalSelectedIds;
6497
7030
  }
6498
7031
  const finalClassName = newRow ? `${className} clear` : className;
6499
- const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
7032
+ const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
7033
+ const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
6500
7034
  // Update portal dropdown when inside modal
6501
7035
  if (state.isInsideModal) {
6502
7036
  updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
@@ -6541,7 +7075,7 @@ const Select = () => {
6541
7075
  onremove: () => {
6542
7076
  state.dropdownRef = null;
6543
7077
  },
6544
- style: getDropdownStyles(state.inputRef, true, options),
7078
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, options)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
6545
7079
  }, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
6546
7080
  m(MaterialIcon, {
6547
7081
  name: 'caret',
@@ -6777,45 +7311,50 @@ const Tabs = () => {
6777
7311
  };
6778
7312
 
6779
7313
  // Proper components to avoid anonymous closures
6780
- const SelectedChip = {
6781
- view: ({ attrs: { option, onRemove } }) => m('.chip', [
6782
- option.label || option.id.toString(),
6783
- m(MaterialIcon, {
6784
- name: 'close',
6785
- className: 'close',
6786
- onclick: (e) => {
6787
- e.stopPropagation();
6788
- onRemove(option.id);
6789
- },
6790
- }),
6791
- ]),
6792
- };
6793
- const DropdownOption = {
6794
- view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
6795
- const checkboxId = `search-select-option-${option.id}`;
6796
- const optionLabel = option.label || option.id.toString();
6797
- return m('li', {
6798
- key: option.id,
6799
- onclick: (e) => {
6800
- e.preventDefault();
6801
- e.stopPropagation();
6802
- onToggle(option);
6803
- },
6804
- class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
6805
- onmouseover: () => {
6806
- if (!option.disabled) {
6807
- onMouseOver(index);
6808
- }
6809
- },
6810
- }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
6811
- m('input', {
6812
- type: 'checkbox',
6813
- id: checkboxId,
6814
- checked: selectedIds.includes(option.id),
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
+ },
6815
7325
  }),
6816
- m('span', optionLabel),
6817
- ]));
6818
- },
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
+ };
6819
7358
  };
6820
7359
  /**
6821
7360
  * Mithril Factory Component for Multi-Select Dropdown with search
@@ -6830,6 +7369,7 @@ const SearchSelect = () => {
6830
7369
  dropdownRef: null,
6831
7370
  focusedIndex: -1,
6832
7371
  internalSelectedIds: [],
7372
+ createdOptions: [],
6833
7373
  };
6834
7374
  const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
6835
7375
  const componentId = uniqueId();
@@ -6872,7 +7412,10 @@ const SearchSelect = () => {
6872
7412
  // Handle add new option
6873
7413
  return 'addNew';
6874
7414
  }
6875
- else if (state.focusedIndex < filteredOptions.length) ;
7415
+ else if (state.focusedIndex < filteredOptions.length) {
7416
+ // This will be handled in the view method where attrs are available
7417
+ return 'selectOption';
7418
+ }
6876
7419
  }
6877
7420
  break;
6878
7421
  case 'Escape':
@@ -6883,11 +7426,22 @@ const SearchSelect = () => {
6883
7426
  }
6884
7427
  return null;
6885
7428
  };
7429
+ // Create new option and add to state
7430
+ const createAndSelectOption = async (attrs) => {
7431
+ if (!attrs.oncreateNewOption || !state.searchTerm)
7432
+ return;
7433
+ const newOption = await attrs.oncreateNewOption(state.searchTerm);
7434
+ // Store the created option internally
7435
+ state.createdOptions.push(newOption);
7436
+ // Select the new option
7437
+ toggleOption(newOption, attrs);
7438
+ };
6886
7439
  // Toggle option selection
6887
7440
  const toggleOption = (option, attrs) => {
6888
7441
  if (option.disabled)
6889
7442
  return;
6890
7443
  const controlled = isControlled(attrs);
7444
+ const { maxSelectedOptions } = attrs;
6891
7445
  // Get current selected IDs from props or internal state
6892
7446
  const currentSelectedIds = controlled
6893
7447
  ? attrs.checkedId !== undefined
@@ -6896,9 +7450,29 @@ const SearchSelect = () => {
6896
7450
  : [attrs.checkedId]
6897
7451
  : []
6898
7452
  : state.internalSelectedIds;
6899
- const newIds = currentSelectedIds.includes(option.id)
6900
- ? currentSelectedIds.filter((id) => id !== option.id)
6901
- : [...currentSelectedIds, option.id];
7453
+ const isSelected = currentSelectedIds.includes(option.id);
7454
+ let newIds;
7455
+ if (isSelected) {
7456
+ // Remove if already selected
7457
+ newIds = currentSelectedIds.filter((id) => id !== option.id);
7458
+ }
7459
+ else {
7460
+ // Check if we've reached the max selection limit
7461
+ if (maxSelectedOptions && currentSelectedIds.length >= maxSelectedOptions) {
7462
+ // If max=1, replace the selection
7463
+ if (maxSelectedOptions === 1) {
7464
+ newIds = [option.id];
7465
+ }
7466
+ else {
7467
+ // Otherwise, don't add more
7468
+ return;
7469
+ }
7470
+ }
7471
+ else {
7472
+ // Add to selection
7473
+ newIds = [...currentSelectedIds, option.id];
7474
+ }
7475
+ }
6902
7476
  // Update internal state for uncontrolled mode
6903
7477
  if (!controlled) {
6904
7478
  state.internalSelectedIds = newIds;
@@ -6960,21 +7534,32 @@ const SearchSelect = () => {
6960
7534
  : [attrs.checkedId]
6961
7535
  : []
6962
7536
  : state.internalSelectedIds;
6963
- const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
7537
+ const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
6964
7538
  // Use i18n values if provided, otherwise use defaults
6965
7539
  const texts = {
6966
7540
  noOptionsFound: i18n.noOptionsFound || noOptionsFound,
6967
7541
  addNewPrefix: i18n.addNewPrefix || '+',
7542
+ showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
7543
+ maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
6968
7544
  };
7545
+ // Check if max selections is reached
7546
+ const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
7547
+ // Merge provided options with internally created options
7548
+ const allOptions = [...options, ...state.createdOptions];
6969
7549
  // Get selected options for display
6970
- const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
7550
+ const selectedOptionsUnsorted = allOptions.filter((opt) => selectedIds.includes(opt.id));
7551
+ const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
6971
7552
  // Safely filter options
6972
- const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
7553
+ const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6973
7554
  !selectedIds.includes(option.id));
7555
+ // Apply display limit if configured
7556
+ const totalFilteredCount = filteredOptions.length;
7557
+ const displayedOptions = maxDisplayedOptions ? filteredOptions.slice(0, maxDisplayedOptions) : filteredOptions;
7558
+ const isTruncated = maxDisplayedOptions && totalFilteredCount > maxDisplayedOptions;
6974
7559
  // Check if we should show the "add new option" element
6975
7560
  const showAddNew = oncreateNewOption &&
6976
7561
  state.searchTerm &&
6977
- !filteredOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
7562
+ !displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
6978
7563
  // Render the dropdown
6979
7564
  return m('.input-field.multi-select-dropdown', { className }, [
6980
7565
  m('.chips.chips-initial.chips-container', {
@@ -7008,8 +7593,7 @@ const SearchSelect = () => {
7008
7593
  style: { position: 'absolute', left: '-9999px', opacity: 0 },
7009
7594
  }),
7010
7595
  // Selected Options (chips)
7011
- ...selectedOptions.map((option) => m(SelectedChip, {
7012
- // key: option.id,
7596
+ ...selectedOptions.map((option) => m(SelectedChip(), {
7013
7597
  option,
7014
7598
  onRemove: (id) => removeOption(id, attrs),
7015
7599
  })),
@@ -7047,7 +7631,7 @@ const SearchSelect = () => {
7047
7631
  onremove: () => {
7048
7632
  state.dropdownRef = null;
7049
7633
  },
7050
- style: getDropdownStyles(state.inputRef),
7634
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
7051
7635
  }, [
7052
7636
  m('li', // Search Input
7053
7637
  {
@@ -7067,42 +7651,65 @@ const SearchSelect = () => {
7067
7651
  state.focusedIndex = -1; // Reset focus when typing
7068
7652
  },
7069
7653
  onkeydown: async (e) => {
7070
- const result = handleKeyDown(e, filteredOptions, !!showAddNew);
7654
+ const result = handleKeyDown(e, displayedOptions, !!showAddNew);
7071
7655
  if (result === 'addNew' && oncreateNewOption) {
7072
- const option = await oncreateNewOption(state.searchTerm);
7073
- toggleOption(option, attrs);
7656
+ await createAndSelectOption(attrs);
7074
7657
  }
7075
- else if (e.key === 'Enter' &&
7076
- state.focusedIndex >= 0 &&
7077
- state.focusedIndex < filteredOptions.length) {
7078
- toggleOption(filteredOptions[state.focusedIndex], attrs);
7658
+ else if (result === 'selectOption' && state.focusedIndex < displayedOptions.length) {
7659
+ toggleOption(displayedOptions[state.focusedIndex], attrs);
7079
7660
  }
7080
7661
  },
7081
7662
  class: 'search-select-input',
7082
7663
  }),
7083
7664
  ]),
7084
7665
  // No options found message or list of options
7085
- ...(filteredOptions.length === 0 && !showAddNew
7666
+ ...(displayedOptions.length === 0 && !showAddNew
7086
7667
  ? [m('li.search-select-no-options', texts.noOptionsFound)]
7087
7668
  : []),
7669
+ // Truncation message
7670
+ ...(isTruncated
7671
+ ? [
7672
+ m('li.search-select-truncation-info', {
7673
+ style: {
7674
+ fontStyle: 'italic',
7675
+ color: 'var(--mm-text-hint, #9e9e9e)',
7676
+ padding: '8px 16px',
7677
+ cursor: 'default',
7678
+ },
7679
+ }, texts.showingXofY
7680
+ .replace('{shown}', displayedOptions.length.toString())
7681
+ .replace('{total}', totalFilteredCount.toString())),
7682
+ ]
7683
+ : []),
7684
+ // Max selections reached message
7685
+ ...(isMaxSelectionsReached
7686
+ ? [
7687
+ m('li.search-select-max-info', {
7688
+ style: {
7689
+ fontStyle: 'italic',
7690
+ color: 'var(--mm-text-hint, #9e9e9e)',
7691
+ padding: '8px 16px',
7692
+ cursor: 'default',
7693
+ },
7694
+ }, texts.maxSelectionsReached.replace('{max}', maxSelectedOptions.toString())),
7695
+ ]
7696
+ : []),
7088
7697
  // Add new option item
7089
7698
  ...(showAddNew
7090
7699
  ? [
7091
7700
  m('li', {
7092
7701
  onclick: async () => {
7093
- const option = await oncreateNewOption(state.searchTerm);
7094
- toggleOption(option, attrs);
7702
+ await createAndSelectOption(attrs);
7095
7703
  },
7096
- class: state.focusedIndex === filteredOptions.length ? 'active' : '',
7704
+ class: state.focusedIndex === displayedOptions.length ? 'active' : '',
7097
7705
  onmouseover: () => {
7098
- state.focusedIndex = filteredOptions.length;
7706
+ state.focusedIndex = displayedOptions.length;
7099
7707
  },
7100
7708
  }, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
7101
7709
  ]
7102
7710
  : []),
7103
7711
  // List of filtered options
7104
- ...filteredOptions.map((option, index) => m(DropdownOption, {
7105
- // key: option.id,
7712
+ ...displayedOptions.map((option, index) => m(DropdownOption(), {
7106
7713
  option,
7107
7714
  index,
7108
7715
  selectedIds,
@@ -7111,6 +7718,7 @@ const SearchSelect = () => {
7111
7718
  onMouseOver: (idx) => {
7112
7719
  state.focusedIndex = idx;
7113
7720
  },
7721
+ showCheckbox: maxSelectedOptions !== 1,
7114
7722
  })),
7115
7723
  ]),
7116
7724
  ]);
@@ -7118,6 +7726,411 @@ const SearchSelect = () => {
7118
7726
  };
7119
7727
  };
7120
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
+
7121
8134
  class Toast {
7122
8135
  constructor(options = {}) {
7123
8136
  this.options = Object.assign(Object.assign({}, Toast.defaults), options);
@@ -9721,6 +10734,7 @@ const isValidationError = (result) => !isValidationSuccess(result);
9721
10734
  // ============================================================================
9722
10735
  // All types are already exported via individual export declarations above
9723
10736
 
10737
+ exports.AnalogClock = AnalogClock;
9724
10738
  exports.AnchorItem = AnchorItem;
9725
10739
  exports.Autocomplete = Autocomplete;
9726
10740
  exports.Breadcrumb = Breadcrumb;
@@ -9737,6 +10751,7 @@ exports.Collection = Collection;
9737
10751
  exports.ColorInput = ColorInput;
9738
10752
  exports.DataTable = DataTable;
9739
10753
  exports.DatePicker = DatePicker;
10754
+ exports.DigitalClock = DigitalClock;
9740
10755
  exports.DoubleRangeSlider = DoubleRangeSlider;
9741
10756
  exports.Dropdown = Dropdown;
9742
10757
  exports.EmailInput = EmailInput;
@@ -9789,6 +10804,7 @@ exports.ThemeManager = ThemeManager;
9789
10804
  exports.ThemeSwitcher = ThemeSwitcher;
9790
10805
  exports.ThemeToggle = ThemeToggle;
9791
10806
  exports.TimePicker = TimePicker;
10807
+ exports.TimeRangePicker = TimeRangePicker;
9792
10808
  exports.Timeline = Timeline;
9793
10809
  exports.Toast = Toast;
9794
10810
  exports.ToastComponent = ToastComponent;
@@ -9797,19 +10813,29 @@ exports.TooltipComponent = TooltipComponent;
9797
10813
  exports.TreeView = TreeView;
9798
10814
  exports.UrlInput = UrlInput;
9799
10815
  exports.Wizard = Wizard;
10816
+ exports.addLeadingZero = addLeadingZero;
9800
10817
  exports.clearPortal = clearPortal;
9801
10818
  exports.createBreadcrumb = createBreadcrumb;
10819
+ exports.formatTime = formatTime;
10820
+ exports.generateHourOptions = generateHourOptions;
10821
+ exports.generateMinuteOptions = generateMinuteOptions;
9802
10822
  exports.getDropdownStyles = getDropdownStyles;
9803
10823
  exports.getPortalContainer = getPortalContainer;
9804
10824
  exports.initPushpins = initPushpins;
9805
10825
  exports.initTooltips = initTooltips;
9806
10826
  exports.isNumeric = isNumeric;
10827
+ exports.isTimeDisabled = isTimeDisabled;
9807
10828
  exports.isValidationError = isValidationError;
9808
10829
  exports.isValidationSuccess = isValidationSuccess;
9809
10830
  exports.padLeft = padLeft;
10831
+ exports.parseTime = parseTime;
9810
10832
  exports.range = range;
9811
10833
  exports.releasePortalContainer = releasePortalContainer;
9812
10834
  exports.renderToPortal = renderToPortal;
10835
+ exports.scrollToValue = scrollToValue;
10836
+ exports.snapToNearestItem = snapToNearestItem;
10837
+ exports.sortOptions = sortOptions;
10838
+ exports.timeToMinutes = timeToMinutes;
9813
10839
  exports.toast = toast;
9814
10840
  exports.uniqueId = uniqueId;
9815
10841
  exports.uuid4 = uuid4;