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/button.d.ts CHANGED
@@ -83,5 +83,6 @@ export declare const Button: m.FactoryComponent<ButtonAttrs>;
83
83
  export declare const LargeButton: m.FactoryComponent<ButtonAttrs>;
84
84
  export declare const SmallButton: m.FactoryComponent<ButtonAttrs>;
85
85
  export declare const FlatButton: m.FactoryComponent<ButtonAttrs>;
86
+ export declare const IconButton: m.FactoryComponent<ButtonAttrs>;
86
87
  export declare const RoundIconButton: m.FactoryComponent<ButtonAttrs>;
87
88
  export declare const SubmitButton: m.FactoryComponent<ButtonAttrs>;
@@ -977,6 +977,20 @@ button.btn-floating {
977
977
  display: block;
978
978
  }
979
979
 
980
+ .btn-flat.btn-icon {
981
+ min-width: auto;
982
+ padding: 0 8px;
983
+ width: auto;
984
+ line-height: 36px;
985
+ background-color: transparent !important;
986
+ }
987
+ .btn-flat.btn-icon i {
988
+ margin: 0;
989
+ }
990
+ .btn-flat.btn-icon:hover, .btn-flat.btn-icon:focus, .btn-flat.btn-icon:active, .btn-flat.btn-icon.active {
991
+ background-color: transparent !important;
992
+ }
993
+
980
994
  .modal:focus {
981
995
  outline: none;
982
996
  }
@@ -4245,6 +4259,11 @@ body {
4245
4259
  .theme-switcher .theme-switcher-buttons .btn-flat .material-icons {
4246
4260
  font-size: 1rem;
4247
4261
  }
4262
+ .theme-switcher .theme-switcher-buttons .btn-flat svg {
4263
+ width: 1rem !important;
4264
+ height: 1rem !important;
4265
+ flex-shrink: 0;
4266
+ }
4248
4267
  .theme-switcher .theme-switcher-buttons .btn-flat span {
4249
4268
  font-size: 0.75rem;
4250
4269
  font-weight: 500;
@@ -4271,6 +4290,9 @@ body {
4271
4290
  .theme-toggle .material-icons {
4272
4291
  font-size: 1.25rem;
4273
4292
  }
4293
+ .theme-toggle svg {
4294
+ flex-shrink: 0;
4295
+ }
4274
4296
 
4275
4297
  nav .theme-toggle {
4276
4298
  background: transparent;
package/dist/forms.css CHANGED
@@ -2722,6 +2722,11 @@ input[type=range]::-ms-thumb {
2722
2722
  .theme-switcher .theme-switcher-buttons .btn-flat .material-icons {
2723
2723
  font-size: 1rem;
2724
2724
  }
2725
+ .theme-switcher .theme-switcher-buttons .btn-flat svg {
2726
+ width: 1rem !important;
2727
+ height: 1rem !important;
2728
+ flex-shrink: 0;
2729
+ }
2725
2730
  .theme-switcher .theme-switcher-buttons .btn-flat span {
2726
2731
  font-size: 0.75rem;
2727
2732
  font-weight: 500;
@@ -2748,6 +2753,9 @@ input[type=range]::-ms-thumb {
2748
2753
  .theme-toggle .material-icons {
2749
2754
  font-size: 1.25rem;
2750
2755
  }
2756
+ .theme-toggle svg {
2757
+ flex-shrink: 0;
2758
+ }
2751
2759
 
2752
2760
  nav .theme-toggle {
2753
2761
  background: transparent;
package/dist/index.css CHANGED
@@ -5234,6 +5234,20 @@ button.btn-floating {
5234
5234
  display: block;
5235
5235
  }
5236
5236
 
5237
+ .btn-flat.btn-icon {
5238
+ min-width: auto;
5239
+ padding: 0 8px;
5240
+ width: auto;
5241
+ line-height: 36px;
5242
+ background-color: transparent !important;
5243
+ }
5244
+ .btn-flat.btn-icon i {
5245
+ margin: 0;
5246
+ }
5247
+ .btn-flat.btn-icon:hover, .btn-flat.btn-icon:focus, .btn-flat.btn-icon:active, .btn-flat.btn-icon.active {
5248
+ background-color: transparent !important;
5249
+ }
5250
+
5237
5251
  .dropdown-content:focus {
5238
5252
  outline: 0;
5239
5253
  }
@@ -9060,6 +9074,11 @@ input[type=range]::-ms-thumb {
9060
9074
  .theme-switcher .theme-switcher-buttons .btn-flat .material-icons {
9061
9075
  font-size: 1rem;
9062
9076
  }
9077
+ .theme-switcher .theme-switcher-buttons .btn-flat svg {
9078
+ width: 1rem !important;
9079
+ height: 1rem !important;
9080
+ flex-shrink: 0;
9081
+ }
9063
9082
  .theme-switcher .theme-switcher-buttons .btn-flat span {
9064
9083
  font-size: 0.75rem;
9065
9084
  font-weight: 500;
@@ -9086,6 +9105,9 @@ input[type=range]::-ms-thumb {
9086
9105
  .theme-toggle .material-icons {
9087
9106
  font-size: 1.25rem;
9088
9107
  }
9108
+ .theme-toggle svg {
9109
+ flex-shrink: 0;
9110
+ }
9089
9111
 
9090
9112
  nav .theme-toggle {
9091
9113
  background: transparent;
package/dist/index.esm.js CHANGED
@@ -432,6 +432,103 @@ const Icon = () => ({
432
432
  },
433
433
  });
434
434
 
435
+ /*!
436
+ * Waves Effect for Mithril Materialized
437
+ * Based on Waves v0.6.4 by Alfiana E. Sibuea
438
+ * Adapted for TypeScript and Mithril integration
439
+ */
440
+ class WavesEffect {
441
+ static offset(elem) {
442
+ const rect = elem.getBoundingClientRect();
443
+ return {
444
+ top: rect.top + window.pageYOffset,
445
+ left: rect.left + window.pageXOffset
446
+ };
447
+ }
448
+ static createRipple(e, element) {
449
+ // Disable right click
450
+ if (e.button === 2) {
451
+ return;
452
+ }
453
+ // Create ripple element
454
+ const ripple = document.createElement('div');
455
+ ripple.className = 'waves-ripple';
456
+ // Get click position relative to element
457
+ const pos = this.offset(element);
458
+ const relativeY = e.pageY - pos.top;
459
+ const relativeX = e.pageX - pos.left;
460
+ // Calculate scale based on element size
461
+ const scale = (element.clientWidth / 100) * 10;
462
+ // Set initial ripple position and style
463
+ ripple.style.cssText = `
464
+ top: ${relativeY}px;
465
+ left: ${relativeX}px;
466
+ transform: scale(0);
467
+ opacity: 1;
468
+ `;
469
+ // Add ripple to element
470
+ element.appendChild(ripple);
471
+ // Force reflow and animate
472
+ ripple.offsetHeight;
473
+ ripple.style.transform = `scale(${scale})`;
474
+ ripple.style.opacity = '1';
475
+ // Store reference for cleanup
476
+ ripple.setAttribute('data-created', Date.now().toString());
477
+ }
478
+ static removeRipples(element) {
479
+ const ripples = element.querySelectorAll('.waves-ripple');
480
+ ripples.forEach((ripple) => {
481
+ const created = parseInt(ripple.getAttribute('data-created') || '0');
482
+ const age = Date.now() - created;
483
+ const fadeOut = () => {
484
+ ripple.style.opacity = '0';
485
+ setTimeout(() => {
486
+ if (ripple.parentNode) {
487
+ ripple.parentNode.removeChild(ripple);
488
+ }
489
+ }, this.duration);
490
+ };
491
+ if (age >= 350) {
492
+ fadeOut();
493
+ }
494
+ else {
495
+ setTimeout(fadeOut, 350 - age);
496
+ }
497
+ });
498
+ }
499
+ }
500
+ WavesEffect.duration = 750;
501
+ WavesEffect.onMouseDown = (e) => {
502
+ const element = e.currentTarget;
503
+ if (element && element.classList.contains('waves-effect')) {
504
+ WavesEffect.createRipple(e, element);
505
+ }
506
+ };
507
+ WavesEffect.onMouseUp = (e) => {
508
+ const element = e.currentTarget;
509
+ if (element && element.classList.contains('waves-effect')) {
510
+ WavesEffect.removeRipples(element);
511
+ }
512
+ };
513
+ WavesEffect.onMouseLeave = (e) => {
514
+ const element = e.currentTarget;
515
+ if (element && element.classList.contains('waves-effect')) {
516
+ WavesEffect.removeRipples(element);
517
+ }
518
+ };
519
+ WavesEffect.onTouchStart = (e) => {
520
+ const element = e.currentTarget;
521
+ if (element && element.classList.contains('waves-effect')) {
522
+ WavesEffect.createRipple(e, element);
523
+ }
524
+ };
525
+ WavesEffect.onTouchEnd = (e) => {
526
+ const element = e.currentTarget;
527
+ if (element && element.classList.contains('waves-effect')) {
528
+ WavesEffect.removeRipples(element);
529
+ }
530
+ };
531
+
435
532
  /**
436
533
  * A factory to create new buttons.
437
534
  *
@@ -445,13 +542,18 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
445
542
  iconName, iconClass, label, className, variant } = attrs, params = __rest(attrs, ["tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "variant"]);
446
543
  // Use variant or fallback to factory type
447
544
  const buttonType = variant || type || 'button';
448
- const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className]
449
- .filter(Boolean)
450
- .join(' ')
451
- .trim();
545
+ const cn = [tooltip ? 'tooltipped' : '', defaultClassNames, className].filter(Boolean).join(' ').trim();
452
546
  // Use tooltipPosition if available, fallback to legacy tooltipPostion
453
547
  const position = tooltipPosition || tooltipPostion || 'top';
454
- 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);
548
+ // Add waves effect event handlers if waves-effect class is present
549
+ const wavesHandlers = cn.includes('waves-effect') ? {
550
+ onmousedown: WavesEffect.onMouseDown,
551
+ onmouseup: WavesEffect.onMouseUp,
552
+ onmouseleave: WavesEffect.onMouseLeave,
553
+ ontouchstart: WavesEffect.onTouchStart,
554
+ ontouchend: WavesEffect.onTouchEnd
555
+ } : {};
556
+ 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);
455
557
  },
456
558
  };
457
559
  };
@@ -460,6 +562,7 @@ const Button = ButtonFactory('a', 'waves-effect waves-light btn', 'button');
460
562
  const LargeButton = ButtonFactory('a', 'waves-effect waves-light btn-large', 'button');
461
563
  const SmallButton = ButtonFactory('a', 'waves-effect waves-light btn-small', 'button');
462
564
  const FlatButton = ButtonFactory('a', 'waves-effect waves-teal btn-flat', 'button');
565
+ const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
463
566
  const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
464
567
  const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
465
568
 
@@ -896,7 +999,7 @@ const MaterialIcon = () => {
896
999
  };
897
1000
  const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
898
1001
  const transform = rotation ? `rotate(${rotation}deg)` : undefined;
899
- 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', {
1002
+ 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', {
900
1003
  d,
901
1004
  fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
902
1005
  })));
@@ -2811,6 +2914,7 @@ const TextArea = () => {
2811
2914
  height: undefined,
2812
2915
  active: false,
2813
2916
  textarea: undefined,
2917
+ hiddenDiv: undefined,
2814
2918
  internalValue: '',
2815
2919
  };
2816
2920
  const updateHeight = (textarea, hiddenDiv) => {
@@ -2907,13 +3011,13 @@ const TextArea = () => {
2907
3011
  overflowWrap: 'break-word',
2908
3012
  },
2909
3013
  oncreate: ({ dom }) => {
2910
- const hiddenDiv = dom;
3014
+ const hiddenDiv = state.hiddenDiv = dom;
2911
3015
  if (state.textarea) {
2912
3016
  updateHeight(state.textarea, hiddenDiv);
2913
3017
  }
2914
3018
  },
2915
3019
  onupdate: ({ dom }) => {
2916
- const hiddenDiv = dom;
3020
+ const hiddenDiv = state.hiddenDiv = dom;
2917
3021
  if (state.textarea) {
2918
3022
  updateHeight(state.textarea, hiddenDiv);
2919
3023
  }
@@ -2938,7 +3042,10 @@ const TextArea = () => {
2938
3042
  const textarea = dom;
2939
3043
  if (state.height)
2940
3044
  textarea.style.height = state.height;
2941
- // No need to manually sync in onupdate since value attribute handles it
3045
+ // Trigger height recalculation when value changes programmatically
3046
+ if (state.hiddenDiv) {
3047
+ updateHeight(textarea, state.hiddenDiv);
3048
+ }
2942
3049
  }, onfocus: () => {
2943
3050
  state.active = true;
2944
3051
  }, oninput: (e) => {
@@ -3098,9 +3205,6 @@ const InputField = (type, defaultClass = '') => () => {
3098
3205
  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"]);
3099
3206
  // const attributes = toAttrs(params);
3100
3207
  const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
3101
- const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
3102
- ? true
3103
- : false;
3104
3208
  // Special rendering for minmax range sliders
3105
3209
  if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
3106
3210
  return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
@@ -3121,12 +3225,15 @@ const InputField = (type, defaultClass = '') => () => {
3121
3225
  }
3122
3226
  else if (isNonInteractive) {
3123
3227
  // Non-interactive components: prefer defaultValue, fallback to value
3124
- value = ((_c = (_b = attrs.defaultValue) !== null && _b !== void 0 ? _b : attrs.value) !== null && _c !== void 0 ? _c : (isNumeric ? 0 : ''));
3228
+ value = ((_b = (_a = attrs.defaultValue) !== null && _a !== void 0 ? _a : attrs.value) !== null && _b !== void 0 ? _b : (isNumeric ? 0 : ''));
3125
3229
  }
3126
3230
  else {
3127
3231
  // Interactive uncontrolled: use internal state
3128
- value = ((_e = (_d = state.internalValue) !== null && _d !== void 0 ? _d : attrs.defaultValue) !== null && _e !== void 0 ? _e : (isNumeric ? 0 : ''));
3232
+ value = ((_d = (_c = state.internalValue) !== null && _c !== void 0 ? _c : attrs.defaultValue) !== null && _d !== void 0 ? _d : (isNumeric ? 0 : ''));
3129
3233
  }
3234
+ const isActive = state.active || ((_e = state.inputElement) === null || _e === void 0 ? void 0 : _e.value) || value || placeholder || type === 'color' || type === 'range'
3235
+ ? true
3236
+ : false;
3130
3237
  const rangeType = type === 'range' && !attrs.minmax;
3131
3238
  return m('.input-field', { className: cn, style }, [
3132
3239
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
@@ -4340,12 +4447,17 @@ const FloatingActionButton = () => {
4340
4447
  }
4341
4448
  : undefined,
4342
4449
  }, [
4343
- m('a.btn-floating.btn-large', {
4450
+ m('a.btn-floating.btn-large.waves-effect.waves-light', {
4344
4451
  className,
4452
+ onmousedown: WavesEffect.onMouseDown,
4453
+ onmouseup: WavesEffect.onMouseUp,
4454
+ onmouseleave: WavesEffect.onMouseLeave,
4455
+ ontouchstart: WavesEffect.onTouchStart,
4456
+ ontouchend: WavesEffect.onTouchEnd,
4345
4457
  }, m('i.material-icons', { className: iconClass }, iconName)),
4346
4458
  buttons &&
4347
4459
  buttons.length > 0 &&
4348
- m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
4460
+ m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.waves-effect.waves-light.${button.className || 'red'}`, {
4349
4461
  style: {
4350
4462
  opacity: state.isOpen ? '1' : '0',
4351
4463
  transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
@@ -4356,6 +4468,11 @@ const FloatingActionButton = () => {
4356
4468
  if (button.onclick)
4357
4469
  button.onclick(e);
4358
4470
  },
4471
+ onmousedown: WavesEffect.onMouseDown,
4472
+ onmouseup: WavesEffect.onMouseUp,
4473
+ onmouseleave: WavesEffect.onMouseLeave,
4474
+ ontouchstart: WavesEffect.onTouchStart,
4475
+ ontouchend: WavesEffect.onTouchEnd,
4359
4476
  }, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
4360
4477
  ]));
4361
4478
  },
@@ -5752,11 +5869,13 @@ const RadioButtons = () => {
5752
5869
  const Select = () => {
5753
5870
  const state = {
5754
5871
  id: '',
5872
+ dropdownId: '',
5755
5873
  isOpen: false,
5756
5874
  focusedIndex: -1,
5757
5875
  inputRef: null,
5758
5876
  dropdownRef: null,
5759
5877
  internalSelectedIds: [],
5878
+ isInsideModal: false,
5760
5879
  };
5761
5880
  const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5762
5881
  const isSelected = (id, selectedIds) => {
@@ -5845,9 +5964,124 @@ const Select = () => {
5845
5964
  m.redraw();
5846
5965
  }
5847
5966
  };
5967
+ const getPortalStyles = (inputRef) => {
5968
+ if (!inputRef)
5969
+ return {};
5970
+ const rect = inputRef.getBoundingClientRect();
5971
+ const viewportHeight = window.innerHeight;
5972
+ return {
5973
+ position: 'fixed',
5974
+ top: `${rect.bottom}px`,
5975
+ left: `${rect.left}px`,
5976
+ width: `${rect.width}px`,
5977
+ zIndex: 10000, // Higher than modal z-index
5978
+ maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
5979
+ };
5980
+ };
5981
+ const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
5982
+ placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5983
+ // Render ungrouped options first
5984
+ attrs.options
5985
+ .filter((option) => !option.group)
5986
+ .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5987
+ ? 'disabled'
5988
+ : state.focusedIndex === attrs.options.indexOf(option)
5989
+ ? 'focused'
5990
+ : '' }, (option.disabled
5991
+ ? {}
5992
+ : {
5993
+ onclick: (e) => {
5994
+ e.stopPropagation();
5995
+ toggleOption(option.id, multiple, attrs);
5996
+ },
5997
+ })), [
5998
+ option.img && m('img', { src: option.img, alt: option.label }),
5999
+ m('span', [
6000
+ multiple
6001
+ ? m('label', { for: option.id }, m('input', {
6002
+ id: option.id,
6003
+ type: 'checkbox',
6004
+ checked: selectedIds.includes(option.id),
6005
+ disabled: option.disabled ? true : undefined,
6006
+ onclick: (e) => {
6007
+ e.stopPropagation();
6008
+ },
6009
+ }), m('span', option.label))
6010
+ : m('span', option.label),
6011
+ ].filter(Boolean)),
6012
+ ])),
6013
+ // Render grouped options
6014
+ Object.entries(attrs.options
6015
+ .filter((option) => option.group)
6016
+ .reduce((groups, option) => {
6017
+ const group = option.group;
6018
+ if (!groups[group])
6019
+ groups[group] = [];
6020
+ groups[group].push(option);
6021
+ return groups;
6022
+ }, {}))
6023
+ .map(([groupName, groupOptions]) => [
6024
+ m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
6025
+ ...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
6026
+ ? {}
6027
+ : {
6028
+ onclick: (e) => {
6029
+ e.stopPropagation();
6030
+ toggleOption(option.id, multiple, attrs);
6031
+ },
6032
+ })), [
6033
+ option.img && m('img', { src: option.img, alt: option.label }),
6034
+ m('span', [
6035
+ multiple
6036
+ ? m('label', { for: option.id }, m('input', {
6037
+ id: option.id,
6038
+ type: 'checkbox',
6039
+ checked: selectedIds.includes(option.id),
6040
+ disabled: option.disabled ? true : undefined,
6041
+ onclick: (e) => {
6042
+ e.stopPropagation();
6043
+ },
6044
+ }), m('span', option.label))
6045
+ : m('span', option.label),
6046
+ ].filter(Boolean)),
6047
+ ])),
6048
+ ])
6049
+ .reduce((acc, val) => acc.concat(val), []),
6050
+ ];
6051
+ const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
6052
+ var _a;
6053
+ if (!state.isInsideModal)
6054
+ return;
6055
+ let portalElement = document.getElementById(state.dropdownId);
6056
+ if (!state.isOpen) {
6057
+ // Clean up portal when dropdown is closed
6058
+ if (portalElement) {
6059
+ m.render(portalElement, []);
6060
+ (_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
6061
+ }
6062
+ return;
6063
+ }
6064
+ if (!portalElement) {
6065
+ portalElement = document.createElement('div');
6066
+ portalElement.id = state.dropdownId;
6067
+ document.body.appendChild(portalElement);
6068
+ }
6069
+ const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6070
+ tabindex: 0,
6071
+ style: getPortalStyles(state.inputRef),
6072
+ oncreate: ({ dom }) => {
6073
+ state.dropdownRef = dom;
6074
+ },
6075
+ onremove: () => {
6076
+ state.dropdownRef = null;
6077
+ },
6078
+ }, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
6079
+ m.render(portalElement, dropdownVnode);
6080
+ };
5848
6081
  return {
5849
6082
  oninit: ({ attrs }) => {
5850
6083
  state.id = attrs.id || uniqueId();
6084
+ state.dropdownId = `${state.id}-dropdown`;
5851
6085
  const controlled = isControlled(attrs);
5852
6086
  // Warn developer for improper controlled usage
5853
6087
  if (attrs.checkedId !== undefined && !controlled && !attrs.disabled) {
@@ -5866,9 +6100,20 @@ const Select = () => {
5866
6100
  // Add global click listener to close dropdown
5867
6101
  document.addEventListener('click', closeDropdown);
5868
6102
  },
6103
+ oncreate: ({ dom }) => {
6104
+ // Detect if component is inside a modal
6105
+ state.isInsideModal = !!dom.closest('.modal');
6106
+ },
5869
6107
  onremove: () => {
5870
6108
  // Cleanup global listener
5871
6109
  document.removeEventListener('click', closeDropdown);
6110
+ // Cleanup portaled dropdown if it exists
6111
+ if (state.isInsideModal && state.dropdownRef) {
6112
+ const portalElement = document.getElementById(state.dropdownId);
6113
+ if (portalElement && portalElement.parentNode) {
6114
+ portalElement.parentNode.removeChild(portalElement);
6115
+ }
6116
+ }
5872
6117
  },
5873
6118
  view: ({ attrs }) => {
5874
6119
  var _a;
@@ -5892,6 +6137,10 @@ const Select = () => {
5892
6137
  const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
5893
6138
  const finalClassName = newRow ? `${className} clear` : className;
5894
6139
  const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6140
+ // Update portal dropdown when inside modal
6141
+ if (state.isInsideModal) {
6142
+ updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
6143
+ }
5895
6144
  return m('.input-field.select-space', {
5896
6145
  className: finalClassName,
5897
6146
  key,
@@ -5904,6 +6153,7 @@ const Select = () => {
5904
6153
  tabindex: disabled ? -1 : 0,
5905
6154
  'aria-expanded': state.isOpen ? 'true' : 'false',
5906
6155
  'aria-haspopup': 'listbox',
6156
+ 'aria-controls': state.dropdownId,
5907
6157
  role: 'combobox',
5908
6158
  }, [
5909
6159
  m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
@@ -5920,8 +6170,8 @@ const Select = () => {
5920
6170
  }
5921
6171
  },
5922
6172
  }),
5923
- // Dropdown Menu
5924
- state.isOpen &&
6173
+ // Dropdown Menu - render inline only when NOT inside modal
6174
+ state.isOpen && !state.isInsideModal &&
5925
6175
  m('ul.dropdown-content.select-dropdown', {
5926
6176
  tabindex: 0,
5927
6177
  oncreate: ({ dom }) => {
@@ -5931,76 +6181,7 @@ const Select = () => {
5931
6181
  state.dropdownRef = null;
5932
6182
  },
5933
6183
  style: getDropdownStyles(state.inputRef, true, options),
5934
- }, [
5935
- placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
5936
- // Render ungrouped options first
5937
- options
5938
- .filter((option) => !option.group)
5939
- .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5940
- ? 'disabled'
5941
- : state.focusedIndex === options.indexOf(option)
5942
- ? 'focused'
5943
- : '' }, (option.disabled
5944
- ? {}
5945
- : {
5946
- onclick: (e) => {
5947
- e.stopPropagation();
5948
- toggleOption(option.id, multiple, attrs);
5949
- },
5950
- })), [
5951
- option.img && m('img', { src: option.img, alt: option.label }),
5952
- m('span', [
5953
- multiple
5954
- ? m('label', { for: option.id }, m('input', {
5955
- id: option.id,
5956
- type: 'checkbox',
5957
- checked: selectedIds.includes(option.id),
5958
- disabled: option.disabled ? true : undefined,
5959
- onclick: (e) => {
5960
- e.stopPropagation();
5961
- },
5962
- }), m('span', option.label))
5963
- : m('span', option.label),
5964
- ].filter(Boolean)),
5965
- ])),
5966
- // Render grouped options
5967
- Object.entries(options
5968
- .filter((option) => option.group)
5969
- .reduce((groups, option) => {
5970
- const group = option.group;
5971
- if (!groups[group])
5972
- groups[group] = [];
5973
- groups[group].push(option);
5974
- return groups;
5975
- }, {}))
5976
- .map(([groupName, groupOptions]) => [
5977
- m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
5978
- ...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
5979
- ? {}
5980
- : {
5981
- onclick: (e) => {
5982
- e.stopPropagation();
5983
- toggleOption(option.id, multiple, attrs);
5984
- },
5985
- })), [
5986
- option.img && m('img', { src: option.img, alt: option.label }),
5987
- m('span', [
5988
- multiple
5989
- ? m('label', { for: option.id }, m('input', {
5990
- id: option.id,
5991
- type: 'checkbox',
5992
- checked: selectedIds.includes(option.id),
5993
- disabled: option.disabled ? true : undefined,
5994
- onclick: (e) => {
5995
- e.stopPropagation();
5996
- },
5997
- }), m('span', option.label))
5998
- : m('span', option.label),
5999
- ].filter(Boolean)),
6000
- ])),
6001
- ])
6002
- .reduce((acc, val) => acc.concat(val), []),
6003
- ]),
6184
+ }, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
6004
6185
  m(MaterialIcon, {
6005
6186
  name: 'caret',
6006
6187
  direction: 'down',
@@ -8920,4 +9101,4 @@ const isValidationError = (result) => !isValidationSuccess(result);
8920
9101
  // ============================================================================
8921
9102
  // All types are already exported via individual export declarations above
8922
9103
 
8923
- 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, 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, createBreadcrumb, getDropdownStyles, initPushpins, initTooltips, isNumeric, isValidationError, isValidationSuccess, padLeft, range, toast, uniqueId, uuid4 };
9104
+ 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, createBreadcrumb, getDropdownStyles, initPushpins, initTooltips, isNumeric, isValidationError, isValidationSuccess, padLeft, range, toast, uniqueId, uuid4 };