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