mithril-materialized 3.3.7 → 3.4.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.umd.js CHANGED
@@ -436,6 +436,103 @@
436
436
  },
437
437
  });
438
438
 
439
+ /*!
440
+ * Waves Effect for Mithril Materialized
441
+ * Based on Waves v0.6.4 by Alfiana E. Sibuea
442
+ * Adapted for TypeScript and Mithril integration
443
+ */
444
+ class WavesEffect {
445
+ static offset(elem) {
446
+ const rect = elem.getBoundingClientRect();
447
+ return {
448
+ top: rect.top + window.pageYOffset,
449
+ left: rect.left + window.pageXOffset
450
+ };
451
+ }
452
+ static createRipple(e, element) {
453
+ // Disable right click
454
+ if (e.button === 2) {
455
+ return;
456
+ }
457
+ // Create ripple element
458
+ const ripple = document.createElement('div');
459
+ ripple.className = 'waves-ripple';
460
+ // Get click position relative to element
461
+ const pos = this.offset(element);
462
+ const relativeY = e.pageY - pos.top;
463
+ const relativeX = e.pageX - pos.left;
464
+ // Calculate scale based on element size
465
+ const scale = (element.clientWidth / 100) * 10;
466
+ // Set initial ripple position and style
467
+ ripple.style.cssText = `
468
+ top: ${relativeY}px;
469
+ left: ${relativeX}px;
470
+ transform: scale(0);
471
+ opacity: 1;
472
+ `;
473
+ // Add ripple to element
474
+ element.appendChild(ripple);
475
+ // Force reflow and animate
476
+ ripple.offsetHeight;
477
+ ripple.style.transform = `scale(${scale})`;
478
+ ripple.style.opacity = '1';
479
+ // Store reference for cleanup
480
+ ripple.setAttribute('data-created', Date.now().toString());
481
+ }
482
+ static removeRipples(element) {
483
+ const ripples = element.querySelectorAll('.waves-ripple');
484
+ ripples.forEach((ripple) => {
485
+ const created = parseInt(ripple.getAttribute('data-created') || '0');
486
+ const age = Date.now() - created;
487
+ const fadeOut = () => {
488
+ ripple.style.opacity = '0';
489
+ setTimeout(() => {
490
+ if (ripple.parentNode) {
491
+ ripple.parentNode.removeChild(ripple);
492
+ }
493
+ }, this.duration);
494
+ };
495
+ if (age >= 350) {
496
+ fadeOut();
497
+ }
498
+ else {
499
+ setTimeout(fadeOut, 350 - age);
500
+ }
501
+ });
502
+ }
503
+ }
504
+ WavesEffect.duration = 750;
505
+ WavesEffect.onMouseDown = (e) => {
506
+ const element = e.currentTarget;
507
+ if (element && element.classList.contains('waves-effect')) {
508
+ WavesEffect.createRipple(e, element);
509
+ }
510
+ };
511
+ WavesEffect.onMouseUp = (e) => {
512
+ const element = e.currentTarget;
513
+ if (element && element.classList.contains('waves-effect')) {
514
+ WavesEffect.removeRipples(element);
515
+ }
516
+ };
517
+ WavesEffect.onMouseLeave = (e) => {
518
+ const element = e.currentTarget;
519
+ if (element && element.classList.contains('waves-effect')) {
520
+ WavesEffect.removeRipples(element);
521
+ }
522
+ };
523
+ WavesEffect.onTouchStart = (e) => {
524
+ const element = e.currentTarget;
525
+ if (element && element.classList.contains('waves-effect')) {
526
+ WavesEffect.createRipple(e, element);
527
+ }
528
+ };
529
+ WavesEffect.onTouchEnd = (e) => {
530
+ const element = e.currentTarget;
531
+ if (element && element.classList.contains('waves-effect')) {
532
+ WavesEffect.removeRipples(element);
533
+ }
534
+ };
535
+
439
536
  /**
440
537
  * A factory to create new buttons.
441
538
  *
@@ -449,13 +546,18 @@
449
546
  iconName, iconClass, label, className, variant } = attrs, params = __rest(attrs, ["tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "variant"]);
450
547
  // Use variant or fallback to factory type
451
548
  const buttonType = variant || type || 'button';
452
- const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className]
453
- .filter(Boolean)
454
- .join(' ')
455
- .trim();
549
+ const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className].filter(Boolean).join(' ').trim();
456
550
  // Use tooltipPosition if available, fallback to legacy tooltipPostion
457
551
  const position = tooltipPosition || tooltipPostion || 'top';
458
- return m(element, Object.assign(Object.assign({}, params), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType }), iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined);
552
+ // Add waves effect event handlers if waves-effect class is present
553
+ const wavesHandlers = cn.includes('waves-effect') ? {
554
+ onmousedown: WavesEffect.onMouseDown,
555
+ onmouseup: WavesEffect.onMouseUp,
556
+ onmouseleave: WavesEffect.onMouseLeave,
557
+ ontouchstart: WavesEffect.onTouchStart,
558
+ ontouchend: WavesEffect.onTouchEnd
559
+ } : {};
560
+ return m(element, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType }), iconName ? m(Icon, { iconName, className: iconClass || 'left' }) : undefined, label ? label : undefined);
459
561
  },
460
562
  };
461
563
  };
@@ -464,6 +566,7 @@
464
566
  const LargeButton = ButtonFactory('a', 'waves-effect waves-light btn-large', 'button');
465
567
  const SmallButton = ButtonFactory('a', 'waves-effect waves-light btn-small', 'button');
466
568
  const FlatButton = ButtonFactory('a', 'waves-effect waves-teal btn-flat', 'button');
569
+ const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
467
570
  const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
468
571
  const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
469
572
 
@@ -900,7 +1003,7 @@
900
1003
  };
901
1004
  const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
902
1005
  const transform = rotation ? `rotate(${rotation}deg)` : undefined;
903
- return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '1lh', width: '24', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
1006
+ return m('svg', Object.assign(Object.assign({}, props), { style: Object.assign({ transform }, style), height: '24px', width: '24px', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), iconPaths[name].map((d) => m('path', {
904
1007
  d,
905
1008
  fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
906
1009
  })));
@@ -2815,6 +2918,7 @@
2815
2918
  height: undefined,
2816
2919
  active: false,
2817
2920
  textarea: undefined,
2921
+ hiddenDiv: undefined,
2818
2922
  internalValue: '',
2819
2923
  };
2820
2924
  const updateHeight = (textarea, hiddenDiv) => {
@@ -2911,13 +3015,13 @@
2911
3015
  overflowWrap: 'break-word',
2912
3016
  },
2913
3017
  oncreate: ({ dom }) => {
2914
- const hiddenDiv = dom;
3018
+ const hiddenDiv = state.hiddenDiv = dom;
2915
3019
  if (state.textarea) {
2916
3020
  updateHeight(state.textarea, hiddenDiv);
2917
3021
  }
2918
3022
  },
2919
3023
  onupdate: ({ dom }) => {
2920
- const hiddenDiv = dom;
3024
+ const hiddenDiv = state.hiddenDiv = dom;
2921
3025
  if (state.textarea) {
2922
3026
  updateHeight(state.textarea, hiddenDiv);
2923
3027
  }
@@ -2942,7 +3046,10 @@
2942
3046
  const textarea = dom;
2943
3047
  if (state.height)
2944
3048
  textarea.style.height = state.height;
2945
- // No need to manually sync in onupdate since value attribute handles it
3049
+ // Trigger height recalculation when value changes programmatically
3050
+ if (state.hiddenDiv) {
3051
+ updateHeight(textarea, state.hiddenDiv);
3052
+ }
2946
3053
  }, onfocus: () => {
2947
3054
  state.active = true;
2948
3055
  }, oninput: (e) => {
@@ -3102,9 +3209,6 @@
3102
3209
  const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate, canClear } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate", "canClear"]);
3103
3210
  // const attributes = toAttrs(params);
3104
3211
  const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
3105
- const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
3106
- ? true
3107
- : false;
3108
3212
  // Special rendering for minmax range sliders
3109
3213
  if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
3110
3214
  return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
@@ -3125,12 +3229,15 @@
3125
3229
  }
3126
3230
  else if (isNonInteractive) {
3127
3231
  // Non-interactive components: prefer defaultValue, fallback to value
3128
- value = ((_c = (_b = attrs.defaultValue) !== null && _b !== void 0 ? _b : attrs.value) !== null && _c !== void 0 ? _c : (isNumeric ? 0 : ''));
3232
+ value = ((_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : (isNumeric ? 0 : ''));
3129
3233
  }
3130
3234
  else {
3131
3235
  // Interactive uncontrolled: use internal state
3132
- value = ((_e = (_d = state.internalValue) !== null && _d !== void 0 ? _d : attrs.defaultValue) !== null && _e !== void 0 ? _e : (isNumeric ? 0 : ''));
3236
+ value = ((_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : (isNumeric ? 0 : ''));
3133
3237
  }
3238
+ const isActive = state.active || ((_e = state.inputElement) === null || _e === void 0 ? void 0 : _e.value) || value || placeholder || type === 'color' || type === 'range'
3239
+ ? true
3240
+ : false;
3134
3241
  const rangeType = type === 'range' && !attrs.minmax;
3135
3242
  return m('.input-field', { className: cn, style }, [
3136
3243
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
@@ -4344,12 +4451,17 @@
4344
4451
  }
4345
4452
  : undefined,
4346
4453
  }, [
4347
- m('a.btn-floating.btn-large', {
4454
+ m('a.btn-floating.btn-large.waves-effect.waves-light', {
4348
4455
  className,
4456
+ onmousedown: WavesEffect.onMouseDown,
4457
+ onmouseup: WavesEffect.onMouseUp,
4458
+ onmouseleave: WavesEffect.onMouseLeave,
4459
+ ontouchstart: WavesEffect.onTouchStart,
4460
+ ontouchend: WavesEffect.onTouchEnd,
4349
4461
  }, m('i.material-icons', { className: iconClass }, iconName)),
4350
4462
  buttons &&
4351
4463
  buttons.length > 0 &&
4352
- m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
4464
+ m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.waves-effect.waves-light.${button.className || 'red'}`, {
4353
4465
  style: {
4354
4466
  opacity: state.isOpen ? '1' : '0',
4355
4467
  transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
@@ -4360,6 +4472,11 @@
4360
4472
  if (button.onclick)
4361
4473
  button.onclick(e);
4362
4474
  },
4475
+ onmousedown: WavesEffect.onMouseDown,
4476
+ onmouseup: WavesEffect.onMouseUp,
4477
+ onmouseleave: WavesEffect.onMouseLeave,
4478
+ ontouchstart: WavesEffect.onTouchStart,
4479
+ ontouchend: WavesEffect.onTouchEnd,
4363
4480
  }, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
4364
4481
  ]));
4365
4482
  },
@@ -5756,11 +5873,13 @@
5756
5873
  const Select = () => {
5757
5874
  const state = {
5758
5875
  id: '',
5876
+ dropdownId: '',
5759
5877
  isOpen: false,
5760
5878
  focusedIndex: -1,
5761
5879
  inputRef: null,
5762
5880
  dropdownRef: null,
5763
5881
  internalSelectedIds: [],
5882
+ isInsideModal: false,
5764
5883
  };
5765
5884
  const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5766
5885
  const isSelected = (id, selectedIds) => {
@@ -5849,9 +5968,124 @@
5849
5968
  m.redraw();
5850
5969
  }
5851
5970
  };
5971
+ const getPortalStyles = (inputRef) => {
5972
+ if (!inputRef)
5973
+ return {};
5974
+ const rect = inputRef.getBoundingClientRect();
5975
+ const viewportHeight = window.innerHeight;
5976
+ return {
5977
+ position: 'fixed',
5978
+ top: `${rect.bottom}px`,
5979
+ left: `${rect.left}px`,
5980
+ width: `${rect.width}px`,
5981
+ zIndex: 10000, // Higher than modal z-index
5982
+ maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
5983
+ };
5984
+ };
5985
+ const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
5986
+ placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5987
+ // Render ungrouped options first
5988
+ attrs.options
5989
+ .filter((option) => !option.group)
5990
+ .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5991
+ ? 'disabled'
5992
+ : state.focusedIndex === attrs.options.indexOf(option)
5993
+ ? 'focused'
5994
+ : '' }, (option.disabled
5995
+ ? {}
5996
+ : {
5997
+ onclick: (e) => {
5998
+ e.stopPropagation();
5999
+ toggleOption(option.id, multiple, attrs);
6000
+ },
6001
+ })), [
6002
+ option.img && m('img', { src: option.img, alt: option.label }),
6003
+ m('span', [
6004
+ multiple
6005
+ ? m('label', { for: option.id }, m('input', {
6006
+ id: option.id,
6007
+ type: 'checkbox',
6008
+ checked: selectedIds.includes(option.id),
6009
+ disabled: option.disabled ? true : undefined,
6010
+ onclick: (e) => {
6011
+ e.stopPropagation();
6012
+ },
6013
+ }), m('span', option.label))
6014
+ : m('span', option.label),
6015
+ ].filter(Boolean)),
6016
+ ])),
6017
+ // Render grouped options
6018
+ Object.entries(attrs.options
6019
+ .filter((option) => option.group)
6020
+ .reduce((groups, option) => {
6021
+ const group = option.group;
6022
+ if (!groups[group])
6023
+ groups[group] = [];
6024
+ groups[group].push(option);
6025
+ return groups;
6026
+ }, {}))
6027
+ .map(([groupName, groupOptions]) => [
6028
+ m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
6029
+ ...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
6030
+ ? {}
6031
+ : {
6032
+ onclick: (e) => {
6033
+ e.stopPropagation();
6034
+ toggleOption(option.id, multiple, attrs);
6035
+ },
6036
+ })), [
6037
+ option.img && m('img', { src: option.img, alt: option.label }),
6038
+ m('span', [
6039
+ multiple
6040
+ ? m('label', { for: option.id }, m('input', {
6041
+ id: option.id,
6042
+ type: 'checkbox',
6043
+ checked: selectedIds.includes(option.id),
6044
+ disabled: option.disabled ? true : undefined,
6045
+ onclick: (e) => {
6046
+ e.stopPropagation();
6047
+ },
6048
+ }), m('span', option.label))
6049
+ : m('span', option.label),
6050
+ ].filter(Boolean)),
6051
+ ])),
6052
+ ])
6053
+ .reduce((acc, val) => acc.concat(val), []),
6054
+ ];
6055
+ const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
6056
+ var _a;
6057
+ if (!state.isInsideModal)
6058
+ return;
6059
+ let portalElement = document.getElementById(state.dropdownId);
6060
+ if (!state.isOpen) {
6061
+ // Clean up portal when dropdown is closed
6062
+ if (portalElement) {
6063
+ m.render(portalElement, []);
6064
+ (_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
6065
+ }
6066
+ return;
6067
+ }
6068
+ if (!portalElement) {
6069
+ portalElement = document.createElement('div');
6070
+ portalElement.id = state.dropdownId;
6071
+ document.body.appendChild(portalElement);
6072
+ }
6073
+ const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6074
+ tabindex: 0,
6075
+ style: getPortalStyles(state.inputRef),
6076
+ oncreate: ({ dom }) => {
6077
+ state.dropdownRef = dom;
6078
+ },
6079
+ onremove: () => {
6080
+ state.dropdownRef = null;
6081
+ },
6082
+ }, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
6083
+ m.render(portalElement, dropdownVnode);
6084
+ };
5852
6085
  return {
5853
6086
  oninit: ({ attrs }) => {
5854
6087
  state.id = attrs.id || uniqueId();
6088
+ state.dropdownId = `${state.id}-dropdown`;
5855
6089
  const controlled = isControlled(attrs);
5856
6090
  // Warn developer for improper controlled usage
5857
6091
  if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
@@ -5870,9 +6104,20 @@
5870
6104
  // Add global click listener to close dropdown
5871
6105
  document.addEventListener('click', closeDropdown);
5872
6106
  },
6107
+ oncreate: ({ dom }) => {
6108
+ // Detect if component is inside a modal
6109
+ state.isInsideModal = !!dom.closest('.modal');
6110
+ },
5873
6111
  onremove: () => {
5874
6112
  // Cleanup global listener
5875
6113
  document.removeEventListener('click', closeDropdown);
6114
+ // Cleanup portaled dropdown if it exists
6115
+ if (state.isInsideModal && state.dropdownRef) {
6116
+ const portalElement = document.getElementById(state.dropdownId);
6117
+ if (portalElement && portalElement.parentNode) {
6118
+ portalElement.parentNode.removeChild(portalElement);
6119
+ }
6120
+ }
5876
6121
  },
5877
6122
  view: ({ attrs }) => {
5878
6123
  var _a;
@@ -5896,6 +6141,10 @@
5896
6141
  const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
5897
6142
  const finalClassName = newRow ? `${className} clear` : className;
5898
6143
  const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6144
+ // Update portal dropdown when inside modal
6145
+ if (state.isInsideModal) {
6146
+ updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
6147
+ }
5899
6148
  return m('.input-field.select-space', {
5900
6149
  className: finalClassName,
5901
6150
  key,
@@ -5908,6 +6157,7 @@
5908
6157
  tabindex: disabled ? -1 : 0,
5909
6158
  'aria-expanded': state.isOpen ? 'true' : 'false',
5910
6159
  'aria-haspopup': 'listbox',
6160
+ 'aria-controls': state.dropdownId,
5911
6161
  role: 'combobox',
5912
6162
  }, [
5913
6163
  m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
@@ -5924,8 +6174,8 @@
5924
6174
  }
5925
6175
  },
5926
6176
  }),
5927
- // Dropdown Menu
5928
- state.isOpen &&
6177
+ // Dropdown Menu - render inline only when NOT inside modal
6178
+ state.isOpen && !state.isInsideModal &&
5929
6179
  m('ul.dropdown-content.select-dropdown', {
5930
6180
  tabindex: 0,
5931
6181
  oncreate: ({ dom }) => {
@@ -5935,76 +6185,7 @@
5935
6185
  state.dropdownRef = null;
5936
6186
  },
5937
6187
  style: getDropdownStyles(state.inputRef, true, options),
5938
- }, [
5939
- placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5940
- // Render ungrouped options first
5941
- options
5942
- .filter((option) => !option.group)
5943
- .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5944
- ? 'disabled'
5945
- : state.focusedIndex === options.indexOf(option)
5946
- ? 'focused'
5947
- : '' }, (option.disabled
5948
- ? {}
5949
- : {
5950
- onclick: (e) => {
5951
- e.stopPropagation();
5952
- toggleOption(option.id, multiple, attrs);
5953
- },
5954
- })), [
5955
- option.img && m('img', { src: option.img, alt: option.label }),
5956
- m('span', [
5957
- multiple
5958
- ? m('label', { for: option.id }, m('input', {
5959
- id: option.id,
5960
- type: 'checkbox',
5961
- checked: selectedIds.includes(option.id),
5962
- disabled: option.disabled ? true : undefined,
5963
- onclick: (e) => {
5964
- e.stopPropagation();
5965
- },
5966
- }), m('span', option.label))
5967
- : m('span', option.label),
5968
- ].filter(Boolean)),
5969
- ])),
5970
- // Render grouped options
5971
- Object.entries(options
5972
- .filter((option) => option.group)
5973
- .reduce((groups, option) => {
5974
- const group = option.group;
5975
- if (!groups[group])
5976
- groups[group] = [];
5977
- groups[group].push(option);
5978
- return groups;
5979
- }, {}))
5980
- .map(([groupName, groupOptions]) => [
5981
- m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
5982
- ...groupOptions.map((option) => m('li', Object.assign({ key: option.id, class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
5983
- ? {}
5984
- : {
5985
- onclick: (e) => {
5986
- e.stopPropagation();
5987
- toggleOption(option.id, multiple, attrs);
5988
- },
5989
- })), [
5990
- option.img && m('img', { src: option.img, alt: option.label }),
5991
- m('span', [
5992
- multiple
5993
- ? m('label', { for: option.id }, m('input', {
5994
- id: option.id,
5995
- type: 'checkbox',
5996
- checked: selectedIds.includes(option.id),
5997
- disabled: option.disabled ? true : undefined,
5998
- onclick: (e) => {
5999
- e.stopPropagation();
6000
- },
6001
- }), m('span', option.label))
6002
- : m('span', option.label),
6003
- ].filter(Boolean)),
6004
- ])),
6005
- ])
6006
- .reduce((acc, val) => acc.concat(val), []),
6007
- ]),
6188
+ }, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
6008
6189
  m(MaterialIcon, {
6009
6190
  name: 'caret',
6010
6191
  direction: 'down',
@@ -8949,6 +9130,7 @@
8949
9130
  exports.FloatingActionButton = FloatingActionButton;
8950
9131
  exports.HelperText = HelperText;
8951
9132
  exports.Icon = Icon;
9133
+ exports.IconButton = IconButton;
8952
9134
  exports.ImageList = ImageList;
8953
9135
  exports.InputCheckbox = InputCheckbox;
8954
9136
  exports.Label = Label;
@@ -0,0 +1,16 @@
1
+ /*!
2
+ * Waves Effect for Mithril Materialized
3
+ * Based on Waves v0.6.4 by Alfiana E. Sibuea
4
+ * Adapted for TypeScript and Mithril integration
5
+ */
6
+ export declare class WavesEffect {
7
+ private static duration;
8
+ private static offset;
9
+ private static createRipple;
10
+ private static removeRipples;
11
+ static onMouseDown: (e: MouseEvent) => void;
12
+ static onMouseUp: (e: MouseEvent) => void;
13
+ static onMouseLeave: (e: MouseEvent) => void;
14
+ static onTouchStart: (e: TouchEvent) => void;
15
+ static onTouchEnd: (e: TouchEvent) => void;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-materialized",
3
- "version": "3.3.7",
3
+ "version": "3.4.0",
4
4
  "description": "A materialize library for mithril.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -325,3 +325,24 @@ button.btn-floating {
325
325
  .btn-block {
326
326
  display: block;
327
327
  }
328
+
329
+ // Icon button - compact flat button for icons only
330
+ .btn-flat.btn-icon {
331
+ min-width: auto;
332
+ padding: 0 8px;
333
+ width: auto;
334
+ line-height: variables.$button-height;
335
+ background-color: transparent !important;
336
+
337
+ i {
338
+ margin: 0;
339
+ }
340
+
341
+ // Ensure background stays transparent in all states
342
+ &:hover,
343
+ &:focus,
344
+ &:active,
345
+ &.active {
346
+ background-color: transparent !important;
347
+ }
348
+ }
@@ -53,6 +53,12 @@
53
53
  .material-icons {
54
54
  font-size: 1rem;
55
55
  }
56
+
57
+ svg {
58
+ width: 1rem !important;
59
+ height: 1rem !important;
60
+ flex-shrink: 0;
61
+ }
56
62
 
57
63
  span {
58
64
  font-size: 0.75rem;
@@ -84,6 +90,10 @@
84
90
  .material-icons {
85
91
  font-size: 1.25rem;
86
92
  }
93
+
94
+ svg {
95
+ flex-shrink: 0;
96
+ }
87
97
  }
88
98
 
89
99
  // Navbar theme toggle specific styles