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/pickers.css CHANGED
@@ -353,12 +353,14 @@
353
353
  .timepicker-modal {
354
354
  max-width: 325px;
355
355
  max-height: none;
356
+ overflow: hidden;
356
357
  }
357
358
 
358
359
  .timepicker-container.modal-content {
359
360
  display: flex;
360
361
  flex-direction: column;
361
362
  padding: 0;
363
+ overflow: hidden;
362
364
  }
363
365
 
364
366
  .text-primary {
@@ -442,6 +444,10 @@
442
444
  bottom: 0;
443
445
  }
444
446
 
447
+ .timepicker-dial {
448
+ pointer-events: none;
449
+ }
450
+
445
451
  .timepicker-minutes {
446
452
  visibility: hidden;
447
453
  }
@@ -456,6 +462,7 @@
456
462
  position: absolute;
457
463
  cursor: pointer;
458
464
  font-size: 15px;
465
+ pointer-events: none;
459
466
  }
460
467
 
461
468
  .timepicker-tick.active,
@@ -521,6 +528,169 @@
521
528
  padding: 0 20px;
522
529
  }
523
530
 
531
+ /* Digital Clock Mode */
532
+ .timepicker-digital-mode {
533
+ padding: 20px;
534
+ display: flex;
535
+ flex-direction: column;
536
+ align-items: center;
537
+ flex: 2.5 auto;
538
+ overflow: hidden;
539
+ }
540
+
541
+ .timepicker-digital-clock {
542
+ display: flex;
543
+ align-items: center;
544
+ gap: 8px;
545
+ margin-bottom: 20px;
546
+ user-select: none;
547
+ overflow: hidden;
548
+ }
549
+
550
+ .digital-clock-column {
551
+ width: 80px;
552
+ height: 240px;
553
+ overflow-y: scroll;
554
+ overflow-x: hidden;
555
+ scroll-snap-type: y mandatory;
556
+ scrollbar-width: thin;
557
+ scrollbar-color: transparent transparent;
558
+ position: relative;
559
+ }
560
+ .digital-clock-column::-webkit-scrollbar {
561
+ width: 6px;
562
+ }
563
+ .digital-clock-column::-webkit-scrollbar-track {
564
+ background: transparent;
565
+ }
566
+ .digital-clock-column::-webkit-scrollbar-thumb {
567
+ background: transparent;
568
+ border-radius: 3px;
569
+ }
570
+ .digital-clock-column:hover {
571
+ scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
572
+ }
573
+ .digital-clock-column:hover::-webkit-scrollbar-thumb {
574
+ background: rgba(0, 0, 0, 0.3);
575
+ }
576
+ .digital-clock-column:hover::-webkit-scrollbar-thumb:hover {
577
+ background: rgba(0, 0, 0, 0.5);
578
+ }
579
+ .digital-clock-column {
580
+ background: linear-gradient(to bottom, var(--mm-background-color, #fafafa) 0%, transparent 20%, transparent 80%, var(--mm-background-color, #fafafa) 100%);
581
+ }
582
+ .digital-clock-column.ampm-column {
583
+ width: 60px;
584
+ scrollbar-width: none;
585
+ }
586
+ .digital-clock-column.ampm-column::-webkit-scrollbar {
587
+ display: none;
588
+ }
589
+ .digital-clock-column::before, .digital-clock-column::after {
590
+ content: "";
591
+ position: absolute;
592
+ left: 0;
593
+ right: 0;
594
+ height: 1px;
595
+ pointer-events: none;
596
+ z-index: 1;
597
+ background-color: rgba(0, 0, 0, 0.1);
598
+ }
599
+ .digital-clock-column::before {
600
+ top: calc(50% - 24px);
601
+ }
602
+ .digital-clock-column::after {
603
+ top: calc(50% + 24px);
604
+ }
605
+
606
+ .digital-clock-item {
607
+ height: 48px;
608
+ display: flex;
609
+ align-items: center;
610
+ justify-content: center;
611
+ font-size: 1.5rem;
612
+ font-weight: 300;
613
+ scroll-snap-align: center;
614
+ cursor: pointer;
615
+ color: rgba(0, 0, 0, 0.4);
616
+ transition: all 0.2s ease;
617
+ }
618
+ .digital-clock-item:hover:not(.padding):not(.disabled) {
619
+ background-color: rgba(0, 0, 0, 0.05);
620
+ }
621
+ .digital-clock-item.selected {
622
+ color: var(--mm-primary-color, #26a69a);
623
+ font-weight: 500;
624
+ font-size: 2rem;
625
+ }
626
+ .digital-clock-item.disabled {
627
+ color: rgba(0, 0, 0, 0.15);
628
+ cursor: not-allowed;
629
+ pointer-events: none;
630
+ }
631
+ .digital-clock-item.padding {
632
+ pointer-events: none;
633
+ cursor: default;
634
+ }
635
+
636
+ .digital-clock-separator {
637
+ font-size: 2rem;
638
+ font-weight: 300;
639
+ margin: 0 4px;
640
+ color: rgba(0, 0, 0, 0.6);
641
+ }
642
+
643
+ /* Time Range Picker Styles */
644
+ .timerange-display-vertical {
645
+ flex: 1 auto;
646
+ background-color: #26a69a;
647
+ padding: 20px;
648
+ display: flex;
649
+ flex-direction: column;
650
+ gap: 20px;
651
+ min-width: 200px;
652
+ }
653
+
654
+ .timerange-time-section {
655
+ color: rgba(255, 255, 255, 0.6);
656
+ }
657
+ .timerange-time-section.active {
658
+ color: rgb(255, 255, 255);
659
+ }
660
+
661
+ .timerange-label {
662
+ font-size: 0.9rem;
663
+ font-weight: 300;
664
+ margin-bottom: 8px;
665
+ opacity: 0.8;
666
+ }
667
+
668
+ .timerange-time {
669
+ font-size: 3rem;
670
+ font-weight: 300;
671
+ letter-spacing: 0.05em;
672
+ min-width: 200px;
673
+ display: flex;
674
+ align-items: baseline;
675
+ }
676
+
677
+ .timerange-hours {
678
+ display: inline-block;
679
+ min-width: 2ch;
680
+ text-align: right;
681
+ }
682
+
683
+ .timerange-minutes {
684
+ display: inline-block;
685
+ min-width: 2ch;
686
+ text-align: left;
687
+ }
688
+
689
+ .timerange-ampm {
690
+ font-size: 1.5rem;
691
+ margin-left: 8px;
692
+ }
693
+
524
694
  /* Media Queries */
525
695
  @media only screen and (min-width : 601px) {
526
696
  .timepicker-modal {
@@ -6,6 +6,10 @@ export interface SearchSelectI18n {
6
6
  noOptionsFound?: string;
7
7
  /** Prefix for adding new option */
8
8
  addNewPrefix?: string;
9
+ /** Message template for truncated results. Use {shown} and {total} placeholders */
10
+ showingXofY?: string;
11
+ /** Message shown when max selections reached. Use {max} placeholder */
12
+ maxSelectionsReached?: string;
9
13
  }
10
14
  export interface SearchSelectAttrs<T extends string | number> extends SelectAttrs<T> {
11
15
  /** Callback when user creates a new option: should return new ID */
@@ -14,10 +18,16 @@ export interface SearchSelectAttrs<T extends string | number> extends SelectAttr
14
18
  searchPlaceholder?: string;
15
19
  /** When no options are left, displays this text, default 'No options found' */
16
20
  noOptionsFound?: string;
17
- /** Max height of the dropdown menu, default '25rem' */
21
+ /** Max height of the dropdown menu, default '400px', use 'none' to disable it */
18
22
  maxHeight?: string;
19
23
  /** Internationalization options */
20
24
  i18n?: SearchSelectI18n;
25
+ /** Maximum number of options to display. When set, limits displayed options to improve performance with large datasets */
26
+ maxDisplayedOptions?: number;
27
+ /** Maximum number of options that can be selected. When max=1, checkboxes are hidden and behaves like single select */
28
+ maxSelectedOptions?: number;
29
+ /** Sort selected items: 'asc' (alphabetically A-Z), 'desc' (Z-A), 'none' (insertion order), or custom sort function */
30
+ sortSelected?: 'asc' | 'desc' | 'none' | ((a: InputOption<T>, b: InputOption<T>) => number);
21
31
  }
22
32
  interface SearchSelectState<T extends string | number> {
23
33
  id: string;
@@ -27,6 +37,7 @@ interface SearchSelectState<T extends string | number> {
27
37
  dropdownRef: HTMLElement | null;
28
38
  focusedIndex: number;
29
39
  internalSelectedIds: T[];
40
+ createdOptions: InputOption<T>[];
30
41
  }
31
42
  /**
32
43
  * Mithril Factory Component for Multi-Select Dropdown with search
package/dist/select.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Attributes, Component } from 'mithril';
2
2
  import { InputOption } from './option';
3
+ export type SortSelected<T extends string | number> = 'asc' | 'desc' | 'none' | ((a: InputOption<T>, b: InputOption<T>) => number);
3
4
  export interface SelectAttrs<T extends string | number> extends Attributes {
4
5
  /** Options to select from */
5
6
  options: InputOption<T>[];
@@ -45,6 +46,10 @@ export interface SelectAttrs<T extends string | number> extends Attributes {
45
46
  required?: boolean;
46
47
  /** Enable the clear icon */
47
48
  showClearButton?: boolean;
49
+ /** Max height of the dropdown menu, default '400px' */
50
+ maxHeight?: string;
51
+ /** Sort selected items: 'asc' (alphabetically A-Z), 'desc' (Z-A), 'none' (insertion order), or custom sort function */
52
+ sortSelected?: SortSelected<T>;
48
53
  }
49
54
  /** Select component */
50
55
  export declare const Select: <T extends string | number>() => Component<SelectAttrs<T>>;
@@ -0,0 +1,51 @@
1
+ import { FactoryComponent } from 'mithril';
2
+ import { InputAttrs } from './input-options';
3
+ export interface TimepickerI18n {
4
+ cancel?: string;
5
+ clear?: string;
6
+ done?: string;
7
+ next?: string;
8
+ }
9
+ export interface TimeRangePickerAttrs extends Omit<InputAttrs<string>, 'defaultValue' | 'onchange'> {
10
+ /** Starting time value in HH:MM or HH:MM AM/PM format */
11
+ startValue?: string;
12
+ /** Ending time value in HH:MM or HH:MM AM/PM format */
13
+ endValue?: string;
14
+ /** Enable validation: end time must be after start time */
15
+ validateRange?: boolean;
16
+ /** Callback when time range changes */
17
+ onchange?: (startTime: string, endTime: string) => void;
18
+ /** i18n for time range picker */
19
+ i18n?: TimepickerI18n;
20
+ /** Use 12-hour format with AM/PM */
21
+ twelveHour?: boolean;
22
+ /** Display mode: 'analog' or 'digital' (default: 'digital') */
23
+ displayMode?: 'analog' | 'digital';
24
+ /** Step for minute increments (default 5) */
25
+ minuteStep?: number;
26
+ /** Step for hour increments (default 1) */
27
+ hourStep?: number;
28
+ /** Minimum selectable time in HH:MM or HH:MM AM/PM format */
29
+ minTime?: string;
30
+ /** Maximum selectable time in HH:MM or HH:MM AM/PM format */
31
+ maxTime?: string;
32
+ /** Show clear button */
33
+ showClearBtn?: boolean;
34
+ /** Dial radius for analog clock (default: 135) */
35
+ dialRadius?: number;
36
+ /** Outer radius for analog clock (default: 105) */
37
+ outerRadius?: number;
38
+ /** Inner radius for analog clock (default: 70) */
39
+ innerRadius?: number;
40
+ /** Tick radius for analog clock (default: 20) */
41
+ tickRadius?: number;
42
+ /** Round by 5 minutes for analog clock (default: false) */
43
+ roundBy5?: boolean;
44
+ /** Vibrate on value change for analog clock (default: true) */
45
+ vibrate?: boolean;
46
+ }
47
+ /**
48
+ * TimeRangePicker component for selecting time ranges
49
+ * Custom implementation with embedded digital clock picker
50
+ */
51
+ export declare const TimeRangePicker: FactoryComponent<TimeRangePickerAttrs>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared utility functions for TimePicker and TimeRangePicker components
3
+ */
4
+ export interface TimeValue {
5
+ hours: number;
6
+ minutes: number;
7
+ amOrPm: 'AM' | 'PM';
8
+ }
9
+ export declare const addLeadingZero: (num: number) => string;
10
+ export declare const parseTime: (timeStr: string, twelveHour: boolean) => TimeValue;
11
+ export declare const formatTime: (time: TimeValue, twelveHour: boolean) => string;
12
+ export declare const timeToMinutes: (time: TimeValue, twelveHour: boolean) => number;
13
+ export declare const generateHourOptions: (twelveHour: boolean, hourStep: number) => number[];
14
+ export declare const generateMinuteOptions: (minuteStep: number) => number[];
15
+ export declare const isTimeDisabled: (hours: number, minutes: number, amOrPm: "AM" | "PM", minTime?: string, maxTime?: string, twelveHour?: boolean) => boolean;
16
+ export declare const scrollToValue: (container: HTMLElement, index: number, itemHeight: number, animated?: boolean) => void;
17
+ export declare const snapToNearestItem: (container: HTMLElement, itemHeight: number, onSnap: (index: number) => void) => void;
@@ -4,6 +4,7 @@ export interface TimepickerI18n {
4
4
  cancel?: string;
5
5
  clear?: string;
6
6
  done?: string;
7
+ next?: string;
7
8
  }
8
9
  export interface TimepickerOptions {
9
10
  dialRadius?: number;
@@ -20,6 +21,16 @@ export interface TimepickerOptions {
20
21
  twelveHour?: boolean;
21
22
  vibrate?: boolean;
22
23
  roundBy5?: boolean;
24
+ /** Display mode: 'analog' (default) or 'digital' */
25
+ displayMode?: 'analog' | 'digital';
26
+ /** Step for minute increments in digital mode (default 5) */
27
+ minuteStep?: number;
28
+ /** Step for hour increments (default 1) */
29
+ hourStep?: number;
30
+ /** Minimum selectable time in HH:MM or HH:MM AM/PM format */
31
+ minTime?: string;
32
+ /** Maximum selectable time in HH:MM or HH:MM AM/PM format */
33
+ maxTime?: string;
23
34
  onOpen?: () => void;
24
35
  onOpenStart?: () => void;
25
36
  onOpenEnd?: () => void;
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { SortSelected } from './select';
2
+ import { InputOption } from '.';
1
3
  /**
2
4
  * Create a unique ID
3
5
  * @see https://stackoverflow.com/a/2117523/319711
@@ -14,6 +16,16 @@ export declare const uniqueId: () => string;
14
16
  export declare const uuid4: () => string;
15
17
  /** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
16
18
  export declare const isNumeric: (n: string | number) => boolean;
19
+ /**
20
+ * Sort options array based on sorting configuration
21
+ * @param options - Array of options to sort
22
+ * @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
23
+ * @returns Sorted array (or original if 'none' or undefined)
24
+ */
25
+ export declare const sortOptions: <T extends string | number>(options: InputOption<T>[], sortConfig?: SortSelected<T>) => {
26
+ id: T;
27
+ label?: string;
28
+ }[];
17
29
  /**
18
30
  * Pad left, default width 2 with a '0'
19
31
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-materialized",
3
- "version": "3.5.10",
3
+ "version": "3.7.0",
4
4
  "description": "A materialize library for mithril.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -56,7 +56,6 @@
56
56
  .collapsible-header-text,
57
57
  .collapsible-header-content {
58
58
  flex: 1;
59
- display: flex;
60
59
  align-items: center;
61
60
  gap: 1rem;
62
61
  }
@@ -81,12 +81,14 @@
81
81
  .timepicker-modal {
82
82
  max-width: 325px;
83
83
  max-height: none;
84
+ overflow: hidden;
84
85
  }
85
86
 
86
87
  .timepicker-container.modal-content {
87
88
  display: flex;
88
89
  flex-direction: column;
89
90
  padding: 0;
91
+ overflow: hidden;
90
92
  }
91
93
 
92
94
  .text-primary {
@@ -171,6 +173,10 @@
171
173
  top: 0;
172
174
  bottom: 0;
173
175
  }
176
+
177
+ .timepicker-dial {
178
+ pointer-events: none;
179
+ }
174
180
  .timepicker-minutes {
175
181
  visibility: hidden;
176
182
  }
@@ -185,6 +191,7 @@
185
191
  position: absolute;
186
192
  cursor: pointer;
187
193
  font-size: 15px;
194
+ pointer-events: none;
188
195
  }
189
196
 
190
197
  .timepicker-tick.active,
@@ -248,6 +255,194 @@
248
255
  padding: 0 20px;
249
256
  }
250
257
 
258
+ /* Digital Clock Mode */
259
+ .timepicker-digital-mode {
260
+ padding: 20px;
261
+ display: flex;
262
+ flex-direction: column;
263
+ align-items: center;
264
+ flex: 2.5 auto;
265
+ overflow: hidden;
266
+ }
267
+
268
+ .timepicker-digital-clock {
269
+ display: flex;
270
+ align-items: center;
271
+ gap: 8px;
272
+ margin-bottom: 20px;
273
+ user-select: none;
274
+ overflow: hidden;
275
+ }
276
+
277
+ .digital-clock-column {
278
+ width: 80px;
279
+ height: 240px;
280
+ overflow-y: scroll;
281
+ overflow-x: hidden;
282
+ scroll-snap-type: y mandatory;
283
+ scrollbar-width: thin;
284
+ scrollbar-color: transparent transparent;
285
+ position: relative;
286
+
287
+ &::-webkit-scrollbar {
288
+ width: 6px;
289
+ }
290
+
291
+ &::-webkit-scrollbar-track {
292
+ background: transparent;
293
+ }
294
+
295
+ &::-webkit-scrollbar-thumb {
296
+ background: transparent;
297
+ border-radius: 3px;
298
+ }
299
+
300
+ // Show scrollbar on hover
301
+ &:hover {
302
+ scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
303
+
304
+ &::-webkit-scrollbar-thumb {
305
+ background: rgba(0, 0, 0, 0.3);
306
+
307
+ &:hover {
308
+ background: rgba(0, 0, 0, 0.5);
309
+ }
310
+ }
311
+ }
312
+
313
+ // Gradient fade at edges - theme aware
314
+ background: linear-gradient(
315
+ to bottom,
316
+ var(--mm-background-color, #fafafa) 0%,
317
+ transparent 20%,
318
+ transparent 80%,
319
+ var(--mm-background-color, #fafafa) 100%
320
+ );
321
+
322
+ &.ampm-column {
323
+ width: 60px;
324
+ scrollbar-width: none;
325
+
326
+ &::-webkit-scrollbar {
327
+ display: none;
328
+ }
329
+ }
330
+
331
+ // Selection indicator lines
332
+ &::before,
333
+ &::after {
334
+ content: '';
335
+ position: absolute;
336
+ left: 0;
337
+ right: 0;
338
+ height: 1px;
339
+ pointer-events: none;
340
+ z-index: 1;
341
+ background-color: rgba(0, 0, 0, 0.1);
342
+ }
343
+
344
+ &::before {
345
+ top: calc(50% - 24px);
346
+ }
347
+
348
+ &::after {
349
+ top: calc(50% + 24px);
350
+ }
351
+ }
352
+
353
+ .digital-clock-item {
354
+ height: 48px;
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: center;
358
+ font-size: 1.5rem;
359
+ font-weight: 300;
360
+ scroll-snap-align: center;
361
+ cursor: pointer;
362
+ color: rgba(0, 0, 0, 0.4);
363
+ transition: all 0.2s ease;
364
+
365
+ &:hover:not(.padding):not(.disabled) {
366
+ background-color: rgba(0, 0, 0, 0.05);
367
+ }
368
+
369
+ &.selected {
370
+ color: var(--mm-primary-color, variables.$secondary-color);
371
+ font-weight: 500;
372
+ font-size: 2rem;
373
+ }
374
+
375
+ &.disabled {
376
+ color: rgba(0, 0, 0, 0.15);
377
+ cursor: not-allowed;
378
+ pointer-events: none;
379
+ }
380
+
381
+ &.padding {
382
+ pointer-events: none;
383
+ cursor: default;
384
+ }
385
+ }
386
+
387
+ .digital-clock-separator {
388
+ font-size: 2rem;
389
+ font-weight: 300;
390
+ margin: 0 4px;
391
+ color: rgba(0, 0, 0, 0.6);
392
+ }
393
+
394
+ /* Time Range Picker Styles */
395
+ .timerange-display-vertical {
396
+ flex: 1 auto;
397
+ background-color: variables.$secondary-color;
398
+ padding: 20px;
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: 20px;
402
+ min-width: 200px;
403
+ }
404
+
405
+ .timerange-time-section {
406
+ color: rgba(255, 255, 255, 0.6);
407
+
408
+ &.active {
409
+ color: rgba(255, 255, 255, 1);
410
+ }
411
+ }
412
+
413
+ .timerange-label {
414
+ font-size: 0.9rem;
415
+ font-weight: 300;
416
+ margin-bottom: 8px;
417
+ opacity: 0.8;
418
+ }
419
+
420
+ .timerange-time {
421
+ font-size: 3rem;
422
+ font-weight: 300;
423
+ letter-spacing: 0.05em;
424
+ min-width: 200px; // Prevent width changes when time updates
425
+ display: flex;
426
+ align-items: baseline;
427
+ }
428
+
429
+ .timerange-hours {
430
+ display: inline-block;
431
+ min-width: 2ch;
432
+ text-align: right;
433
+ }
434
+
435
+ .timerange-minutes {
436
+ display: inline-block;
437
+ min-width: 2ch;
438
+ text-align: left;
439
+ }
440
+
441
+ .timerange-ampm {
442
+ font-size: 1.5rem;
443
+ margin-left: 8px;
444
+ }
445
+
251
446
  /* Media Queries */
252
447
  @media #{variables.$medium-and-up} {
253
448
  .timepicker-modal {
@@ -269,4 +464,4 @@
269
464
  text-align: center;
270
465
  margin-top: 1.2rem;
271
466
  }
272
- }
467
+ }