mithril-materialized 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -230,8 +230,6 @@ const Autocomplete = () => {
230
230
  if (attrs.onAutocomplete) {
231
231
  attrs.onAutocomplete(suggestion.key);
232
232
  }
233
- // Force redraw to update label state
234
- m.redraw();
235
233
  };
236
234
  const handleKeydown = (e, attrs) => {
237
235
  if (!state.isOpen)
@@ -310,7 +308,7 @@ const Autocomplete = () => {
310
308
  const id = attrs.id || state.id;
311
309
  const { label, helperText, onchange, newRow, className = 'col s12', style, iconName, isMandatory, data = {}, limit = Infinity, minLength = 1 } = attrs, params = __rest(attrs, ["label", "helperText", "onchange", "newRow", "className", "style", "iconName", "isMandatory", "data", "limit", "minLength"]);
312
310
  const controlled = isControlled(attrs);
313
- const currentValue = controlled ? (attrs.value || '') : state.internalValue;
311
+ const currentValue = controlled ? attrs.value || '' : state.internalValue;
314
312
  const cn = newRow ? className + ' clear' : className;
315
313
  // Update suggestions when input changes
316
314
  state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
@@ -325,7 +323,7 @@ const Autocomplete = () => {
325
323
  style,
326
324
  }, [
327
325
  iconName ? m('i.material-icons.prefix', iconName) : '',
328
- m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: controlled ? currentValue : undefined, oncreate: (vnode) => {
326
+ m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: currentValue, oncreate: (vnode) => {
329
327
  state.inputElement = vnode.dom;
330
328
  // Set initial value for uncontrolled mode
331
329
  if (!controlled && attrs.defaultValue) {
@@ -361,14 +359,10 @@ const Autocomplete = () => {
361
359
  }
362
360
  }, onblur: (e) => {
363
361
  state.isActive = false;
364
- // Delay closing to allow clicks on suggestions
365
- setTimeout(() => {
366
- if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
367
- state.isOpen = false;
368
- state.selectedIndex = -1;
369
- m.redraw();
370
- }
371
- }, 150);
362
+ if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
363
+ state.isOpen = false;
364
+ state.selectedIndex = -1;
365
+ }
372
366
  } })),
373
367
  // Autocomplete dropdown
374
368
  state.isOpen &&
@@ -384,7 +378,6 @@ const Autocomplete = () => {
384
378
  },
385
379
  onmouseover: () => {
386
380
  state.selectedIndex = index;
387
- m.redraw();
388
381
  },
389
382
  }, [
390
383
  // Check if value contains image URL or icon
@@ -2485,36 +2478,52 @@ const handleKeyboardNavigation = (key, currentValue, min, max, step) => {
2485
2478
  return null;
2486
2479
  }
2487
2480
  };
2481
+ const isControlled = (attrs) => {
2482
+ return attrs.value !== undefined && typeof attrs.oninput === 'function';
2483
+ };
2484
+ const isRangeControlled = (attrs) => {
2485
+ return (attrs.minValue !== undefined || attrs.maxValue !== undefined) && typeof attrs.oninput === 'function';
2486
+ };
2488
2487
  const initRangeState = (state, attrs) => {
2489
- const { min = 0, max = 100, value, minValue, maxValue } = attrs;
2488
+ const { min = 0, max = 100, value, minValue, maxValue, defaultValue } = attrs;
2490
2489
  // Initialize single range value
2491
- if (value !== undefined) {
2492
- const currentValue = value;
2490
+ if (isControlled(attrs)) {
2491
+ // Always use value from props in controlled mode
2492
+ state.singleValue = value !== undefined ? value : min;
2493
+ }
2494
+ else {
2495
+ // Use internal state for uncontrolled mode
2493
2496
  if (state.singleValue === undefined) {
2494
- state.singleValue = currentValue;
2497
+ state.singleValue = defaultValue !== undefined ? defaultValue : value !== undefined ? value : min;
2495
2498
  }
2496
- if (state.lastValue !== value && !state.hasUserInteracted) {
2499
+ // Only update internal state if props changed and user hasn't interacted
2500
+ if (state.lastValue !== value && !state.hasUserInteracted && value !== undefined) {
2497
2501
  state.singleValue = value;
2498
2502
  state.lastValue = value;
2499
2503
  }
2500
2504
  }
2501
- else if (state.singleValue === undefined) {
2502
- state.singleValue = min;
2503
- }
2504
2505
  // Initialize range values
2505
- const currentMinValue = minValue !== undefined ? minValue : min;
2506
- const currentMaxValue = maxValue !== undefined ? maxValue : max;
2507
- if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
2508
- state.rangeMinValue = currentMinValue;
2509
- state.rangeMaxValue = currentMaxValue;
2506
+ if (isRangeControlled(attrs)) {
2507
+ // Always use values from props in controlled mode
2508
+ state.rangeMinValue = minValue !== undefined ? minValue : min;
2509
+ state.rangeMaxValue = maxValue !== undefined ? maxValue : max;
2510
2510
  }
2511
- if (!state.hasUserInteracted &&
2512
- ((minValue !== undefined && state.lastMinValue !== minValue) ||
2513
- (maxValue !== undefined && state.lastMaxValue !== maxValue))) {
2514
- state.rangeMinValue = currentMinValue;
2515
- state.rangeMaxValue = currentMaxValue;
2516
- state.lastMinValue = minValue;
2517
- state.lastMaxValue = maxValue;
2511
+ else {
2512
+ // Use internal state for uncontrolled mode
2513
+ const currentMinValue = minValue !== undefined ? minValue : min;
2514
+ const currentMaxValue = maxValue !== undefined ? maxValue : max;
2515
+ if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
2516
+ state.rangeMinValue = currentMinValue;
2517
+ state.rangeMaxValue = currentMaxValue;
2518
+ }
2519
+ if (!state.hasUserInteracted &&
2520
+ ((minValue !== undefined && state.lastMinValue !== minValue) ||
2521
+ (maxValue !== undefined && state.lastMaxValue !== maxValue))) {
2522
+ state.rangeMinValue = currentMinValue;
2523
+ state.rangeMaxValue = currentMaxValue;
2524
+ state.lastMinValue = minValue;
2525
+ state.lastMaxValue = maxValue;
2526
+ }
2518
2527
  }
2519
2528
  // Initialize active thumb if not set
2520
2529
  if (state.activeThumb === null) {
@@ -2531,15 +2540,18 @@ const updateRangeValues = (minValue, maxValue, attrs, state, immediate) => {
2531
2540
  minValue = maxValue;
2532
2541
  if (maxValue < minValue)
2533
2542
  maxValue = minValue;
2534
- state.rangeMinValue = minValue;
2535
- state.rangeMaxValue = maxValue;
2543
+ // Only update internal state for uncontrolled mode
2544
+ if (!isRangeControlled(attrs)) {
2545
+ state.rangeMinValue = minValue;
2546
+ state.rangeMaxValue = maxValue;
2547
+ }
2536
2548
  state.hasUserInteracted = true;
2537
- // Call oninput for immediate feedback or onchange for final changes
2549
+ // Call appropriate handler based on interaction type, not control mode
2538
2550
  if (immediate && attrs.oninput) {
2539
- attrs.oninput(minValue, maxValue);
2551
+ attrs.oninput(minValue, maxValue); // Immediate feedback during drag
2540
2552
  }
2541
- else if (!immediate && attrs.onchange) {
2542
- attrs.onchange(minValue, maxValue);
2553
+ if (!immediate && attrs.onchange) {
2554
+ attrs.onchange(minValue, maxValue); // Final value on interaction end (blur/mouseup)
2543
2555
  }
2544
2556
  };
2545
2557
  // Single Range Slider Component
@@ -2573,19 +2585,24 @@ const SingleRangeSlider = {
2573
2585
  : tooltipPos
2574
2586
  : tooltipPos;
2575
2587
  const updateSingleValue = (newValue, immediate = false) => {
2576
- state.singleValue = newValue;
2588
+ // Only update internal state for uncontrolled mode
2589
+ if (!isControlled(attrs)) {
2590
+ state.singleValue = newValue;
2591
+ }
2577
2592
  state.hasUserInteracted = true;
2593
+ // Call appropriate handler based on interaction type, not control mode
2578
2594
  if (immediate && oninput) {
2579
- oninput(newValue);
2595
+ oninput(newValue); // Immediate feedback during drag
2580
2596
  }
2581
- else if (!immediate && onchange) {
2582
- onchange(newValue);
2597
+ if (!immediate && onchange) {
2598
+ onchange(newValue); // Final value on interaction end (blur/mouseup)
2583
2599
  }
2584
2600
  };
2585
2601
  const handleMouseDown = (e) => {
2586
2602
  if (disabled)
2587
2603
  return;
2588
2604
  e.preventDefault();
2605
+ e.stopPropagation();
2589
2606
  state.isDragging = true;
2590
2607
  if (finalValueDisplay === 'auto') {
2591
2608
  m.redraw();
@@ -2660,6 +2677,11 @@ const SingleRangeSlider = {
2660
2677
  updateSingleValue(newValue, false);
2661
2678
  }
2662
2679
  },
2680
+ onblur: () => {
2681
+ if (disabled || !onchange)
2682
+ return;
2683
+ onchange(state.singleValue);
2684
+ },
2663
2685
  }, [
2664
2686
  m(`.track.${orientation}`),
2665
2687
  m(`.range-progress.${orientation}`, { style: progressStyle }),
@@ -2718,6 +2740,7 @@ const DoubleRangeSlider = {
2718
2740
  if (disabled)
2719
2741
  return;
2720
2742
  e.preventDefault();
2743
+ e.stopPropagation();
2721
2744
  state.isDragging = true;
2722
2745
  state.activeThumb = thumb;
2723
2746
  if (finalValueDisplay === 'auto') {
@@ -2808,6 +2831,11 @@ const DoubleRangeSlider = {
2808
2831
  maxThumb.focus();
2809
2832
  }
2810
2833
  },
2834
+ onblur: () => {
2835
+ if (disabled || !attrs.onchange)
2836
+ return;
2837
+ attrs.onchange(state.rangeMinValue, state.rangeMaxValue);
2838
+ },
2811
2839
  }, [
2812
2840
  m(`.track.${orientation}`),
2813
2841
  m(`.range.${orientation}`, { style: rangeStyle }),
@@ -3013,13 +3041,13 @@ const TextArea = () => {
3013
3041
  overflowWrap: 'break-word',
3014
3042
  },
3015
3043
  oncreate: ({ dom }) => {
3016
- const hiddenDiv = state.hiddenDiv = dom;
3044
+ const hiddenDiv = (state.hiddenDiv = dom);
3017
3045
  if (state.textarea) {
3018
3046
  updateHeight(state.textarea, hiddenDiv);
3019
3047
  }
3020
3048
  },
3021
3049
  onupdate: ({ dom }) => {
3022
- const hiddenDiv = state.hiddenDiv = dom;
3050
+ const hiddenDiv = (state.hiddenDiv = dom);
3023
3051
  if (state.textarea) {
3024
3052
  updateHeight(state.textarea, hiddenDiv);
3025
3053
  }
@@ -3132,8 +3160,7 @@ const InputField = (type, defaultClass = '') => () => {
3132
3160
  isDragging: false,
3133
3161
  activeThumb: null,
3134
3162
  };
3135
- const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
3136
- (typeof attrs.oninput === 'function' || typeof attrs.onchange === 'function');
3163
+ const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
3137
3164
  const getValue = (target) => {
3138
3165
  const val = target.value;
3139
3166
  return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
@@ -3183,7 +3210,7 @@ const InputField = (type, defaultClass = '') => () => {
3183
3210
  const isNonInteractive = attrs.readonly || attrs.disabled;
3184
3211
  // Warn developer for improper controlled usage
3185
3212
  if (attrs.value !== undefined && !controlled && !isNonInteractive) {
3186
- console.warn(`${type} input received 'value' prop without 'oninput' or 'onchange' handler. ` +
3213
+ console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
3187
3214
  `Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
3188
3215
  }
3189
3216
  // Initialize internal value if not in controlled mode
@@ -4210,14 +4237,12 @@ const Dropdown = () => {
4210
4237
  inputRef: null,
4211
4238
  dropdownRef: null,
4212
4239
  internalCheckedId: undefined,
4240
+ isInsideModal: false,
4213
4241
  };
4214
4242
  const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
4215
- const closeDropdown = (e) => {
4216
- const target = e.target;
4217
- if (!target.closest('.dropdown-wrapper.input-field')) {
4218
- state.isOpen = false;
4219
- m.redraw();
4220
- }
4243
+ const closeDropdown = () => {
4244
+ state.isOpen = false;
4245
+ m.redraw(); // Needed to remove the dropdown options list (potentially added to document root)
4221
4246
  };
4222
4247
  const handleKeyDown = (e, items) => {
4223
4248
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -4262,6 +4287,83 @@ const Dropdown = () => {
4262
4287
  return undefined;
4263
4288
  }
4264
4289
  };
4290
+ const getPortalStyles = (inputRef) => {
4291
+ if (!inputRef)
4292
+ return {};
4293
+ const rect = inputRef.getBoundingClientRect();
4294
+ const viewportHeight = window.innerHeight;
4295
+ const spaceBelow = viewportHeight - rect.bottom;
4296
+ const spaceAbove = rect.top;
4297
+ // Choose whether to show above or below based on available space
4298
+ const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
4299
+ return {
4300
+ position: 'fixed',
4301
+ top: showAbove ? 'auto' : `${rect.bottom}px`,
4302
+ bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
4303
+ left: `${rect.left}px`,
4304
+ width: `${rect.width}px`,
4305
+ zIndex: 10000,
4306
+ maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`,
4307
+ overflow: 'auto',
4308
+ display: 'block',
4309
+ opacity: 1,
4310
+ };
4311
+ };
4312
+ const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
4313
+ if (!state.isInsideModal)
4314
+ return;
4315
+ // Clean up existing portal
4316
+ const existingPortal = document.getElementById(`${state.id}-dropdown`);
4317
+ if (existingPortal) {
4318
+ existingPortal.remove();
4319
+ }
4320
+ if (!state.isOpen || !state.inputRef)
4321
+ return;
4322
+ // Create portal element
4323
+ const portalElement = document.createElement('div');
4324
+ portalElement.id = `${state.id}-dropdown`;
4325
+ document.body.appendChild(portalElement);
4326
+ // Create dropdown content
4327
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
4328
+ const dropdownContent = items.map((item) => {
4329
+ if (item.divider) {
4330
+ return m('li.divider');
4331
+ }
4332
+ const itemIndex = availableItems.indexOf(item);
4333
+ const isSelected = selectedLabel === item.label;
4334
+ const isFocused = state.focusedIndex === itemIndex;
4335
+ return m('li', {
4336
+ class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
4337
+ onclick: item.disabled ? undefined : () => onSelectItem(item),
4338
+ }, m('span', {
4339
+ style: {
4340
+ display: 'flex',
4341
+ alignItems: 'center',
4342
+ padding: '14px 16px',
4343
+ },
4344
+ }, [
4345
+ item.iconName
4346
+ ? m('i.material-icons', {
4347
+ style: { marginRight: '32px' },
4348
+ }, item.iconName)
4349
+ : undefined,
4350
+ item.label,
4351
+ ]));
4352
+ });
4353
+ // Create dropdown with proper positioning
4354
+ const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
4355
+ tabindex: 0,
4356
+ style: getPortalStyles(state.inputRef),
4357
+ oncreate: ({ dom }) => {
4358
+ state.dropdownRef = dom;
4359
+ },
4360
+ onremove: () => {
4361
+ state.dropdownRef = null;
4362
+ },
4363
+ }, dropdownContent);
4364
+ // Render to portal
4365
+ m.render(portalElement, dropdownVnode);
4366
+ };
4265
4367
  return {
4266
4368
  oninit: ({ attrs }) => {
4267
4369
  var _a;
@@ -4273,9 +4375,18 @@ const Dropdown = () => {
4273
4375
  // Add global click listener to close dropdown
4274
4376
  document.addEventListener('click', closeDropdown);
4275
4377
  },
4378
+ oncreate: ({ dom }) => {
4379
+ // Detect if component is inside a modal
4380
+ state.isInsideModal = !!dom.closest('.modal');
4381
+ },
4276
4382
  onremove: () => {
4277
4383
  // Cleanup global listener
4278
4384
  document.removeEventListener('click', closeDropdown);
4385
+ // Cleanup portal
4386
+ const portalElement = document.getElementById(`${state.id}-dropdown`);
4387
+ if (portalElement) {
4388
+ portalElement.remove();
4389
+ }
4279
4390
  },
4280
4391
  view: ({ attrs }) => {
4281
4392
  const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
@@ -4298,6 +4409,16 @@ const Dropdown = () => {
4298
4409
  : undefined;
4299
4410
  const title = selectedItem ? selectedItem.label : label || 'Select';
4300
4411
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
4412
+ // Update portal dropdown when inside modal
4413
+ if (state.isInsideModal) {
4414
+ updatePortalDropdown(items, title, (item) => {
4415
+ if (item.id) {
4416
+ state.isOpen = false;
4417
+ state.focusedIndex = -1;
4418
+ handleSelection(item.id);
4419
+ }
4420
+ });
4421
+ }
4301
4422
  return m('.dropdown-wrapper.input-field', { className, key, style }, [
4302
4423
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
4303
4424
  m(HelperText, { helperText }),
@@ -4331,8 +4452,9 @@ const Dropdown = () => {
4331
4452
  }
4332
4453
  },
4333
4454
  }),
4334
- // Dropdown Menu using Select component's positioning logic
4455
+ // Dropdown Menu - render inline only when NOT inside modal
4335
4456
  state.isOpen &&
4457
+ !state.isInsideModal &&
4336
4458
  m('ul.dropdown-content.select-dropdown', {
4337
4459
  tabindex: 0,
4338
4460
  role: 'listbox',
@@ -4782,7 +4904,7 @@ const ModalPanel = () => {
4782
4904
  maxWidth: '75%',
4783
4905
  borderRadius: '4px',
4784
4906
  })), { backgroundColor: 'var(--mm-modal-background, #fff)', maxHeight: '85%', overflow: 'auto', zIndex: '1003', padding: '0', flexDirection: 'column', boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)' }),
4785
- onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
4907
+ // onclick: (e: Event) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
4786
4908
  }, [
4787
4909
  // Close button
4788
4910
  showCloseButton &&
@@ -5878,6 +6000,7 @@ const Select = () => {
5878
6000
  dropdownRef: null,
5879
6001
  internalSelectedIds: [],
5880
6002
  isInsideModal: false,
6003
+ isMultiple: false,
5881
6004
  };
5882
6005
  const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5883
6006
  const isSelected = (id, selectedIds) => {
@@ -5960,10 +6083,16 @@ const Select = () => {
5960
6083
  }
5961
6084
  };
5962
6085
  const closeDropdown = (e) => {
6086
+ if (!state.isMultiple) {
6087
+ state.isOpen = false;
6088
+ return;
6089
+ }
5963
6090
  const target = e.target;
5964
- if (!target.closest('.input-field.select-space')) {
6091
+ // When inside modal, check both the select component AND the portaled dropdown
6092
+ const isClickInsideSelect = target.closest('.input-field.select-space');
6093
+ const isClickInsidePortalDropdown = state.isInsideModal && state.dropdownRef && (state.dropdownRef.contains(target) || target === state.dropdownRef);
6094
+ if (!isClickInsideSelect && !isClickInsidePortalDropdown) {
5965
6095
  state.isOpen = false;
5966
- m.redraw();
5967
6096
  }
5968
6097
  };
5969
6098
  const getPortalStyles = (inputRef) => {
@@ -5971,13 +6100,21 @@ const Select = () => {
5971
6100
  return {};
5972
6101
  const rect = inputRef.getBoundingClientRect();
5973
6102
  const viewportHeight = window.innerHeight;
6103
+ const spaceBelow = viewportHeight - rect.bottom;
6104
+ const spaceAbove = rect.top;
6105
+ // Choose whether to show above or below based on available space
6106
+ const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
5974
6107
  return {
5975
6108
  position: 'fixed',
5976
- top: `${rect.bottom}px`,
6109
+ top: showAbove ? 'auto' : `${rect.bottom}px`,
6110
+ bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
5977
6111
  left: `${rect.left}px`,
5978
6112
  width: `${rect.width}px`,
5979
6113
  zIndex: 10000, // Higher than modal z-index
5980
- maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
6114
+ maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`, // Leave 20px margin
6115
+ overflow: 'auto',
6116
+ display: 'block',
6117
+ opacity: 1,
5981
6118
  };
5982
6119
  };
5983
6120
  const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
@@ -5985,15 +6122,10 @@ const Select = () => {
5985
6122
  // Render ungrouped options first
5986
6123
  attrs.options
5987
6124
  .filter((option) => !option.group)
5988
- .map((option) => m('li', Object.assign({ key: option.id, class: option.disabled
5989
- ? 'disabled'
5990
- : state.focusedIndex === attrs.options.indexOf(option)
5991
- ? 'focused'
5992
- : '' }, (option.disabled
6125
+ .map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
5993
6126
  ? {}
5994
6127
  : {
5995
- onclick: (e) => {
5996
- e.stopPropagation();
6128
+ onclick: () => {
5997
6129
  toggleOption(option.id, multiple, attrs);
5998
6130
  },
5999
6131
  })), [
@@ -6023,8 +6155,8 @@ const Select = () => {
6023
6155
  return groups;
6024
6156
  }, {}))
6025
6157
  .map(([groupName, groupOptions]) => [
6026
- m('li.optgroup', { key: `group-${groupName}`, tabindex: 0 }, m('span', groupName)),
6027
- ...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
6158
+ m('li.optgroup', { tabindex: 0 }, m('span', groupName)),
6159
+ ...groupOptions.map((option) => m('li', Object.assign({ class: `optgroup-option${option.disabled ? ' disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
6028
6160
  ? {}
6029
6161
  : {
6030
6162
  onclick: (e) => {
@@ -6051,23 +6183,20 @@ const Select = () => {
6051
6183
  .reduce((acc, val) => acc.concat(val), []),
6052
6184
  ];
6053
6185
  const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
6054
- var _a;
6055
6186
  if (!state.isInsideModal)
6056
6187
  return;
6057
- let portalElement = document.getElementById(state.dropdownId);
6058
- if (!state.isOpen) {
6059
- // Clean up portal when dropdown is closed
6060
- if (portalElement) {
6061
- m.render(portalElement, []);
6062
- (_a = portalElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(portalElement);
6063
- }
6064
- return;
6065
- }
6066
- if (!portalElement) {
6067
- portalElement = document.createElement('div');
6068
- portalElement.id = state.dropdownId;
6069
- document.body.appendChild(portalElement);
6188
+ // Clean up existing portal
6189
+ const existingPortal = document.getElementById(state.dropdownId);
6190
+ if (existingPortal) {
6191
+ existingPortal.remove();
6070
6192
  }
6193
+ if (!state.isOpen || !state.inputRef)
6194
+ return;
6195
+ // Create portal element
6196
+ const portalElement = document.createElement('div');
6197
+ portalElement.id = state.dropdownId;
6198
+ document.body.appendChild(portalElement);
6199
+ // Create dropdown with proper positioning
6071
6200
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6072
6201
  tabindex: 0,
6073
6202
  style: getPortalStyles(state.inputRef),
@@ -6078,6 +6207,7 @@ const Select = () => {
6078
6207
  state.dropdownRef = null;
6079
6208
  },
6080
6209
  }, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
6210
+ // Render to portal
6081
6211
  m.render(portalElement, dropdownVnode);
6082
6212
  };
6083
6213
  return {
@@ -6120,7 +6250,8 @@ const Select = () => {
6120
6250
  view: ({ attrs }) => {
6121
6251
  var _a;
6122
6252
  const controlled = isControlled(attrs);
6123
- const { disabled } = attrs;
6253
+ const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
6254
+ state.isMultiple = multiple;
6124
6255
  // Get selected IDs from props or internal state
6125
6256
  let selectedIds;
6126
6257
  if (controlled) {
@@ -6136,7 +6267,6 @@ const Select = () => {
6136
6267
  // Interactive uncontrolled: use internal state
6137
6268
  selectedIds = state.internalSelectedIds;
6138
6269
  }
6139
- const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
6140
6270
  const finalClassName = newRow ? `${className} clear` : className;
6141
6271
  const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6142
6272
  // Update portal dropdown when inside modal
@@ -6173,7 +6303,8 @@ const Select = () => {
6173
6303
  },
6174
6304
  }),
6175
6305
  // Dropdown Menu - render inline only when NOT inside modal
6176
- state.isOpen && !state.isInsideModal &&
6306
+ state.isOpen &&
6307
+ !state.isInsideModal &&
6177
6308
  m('ul.dropdown-content.select-dropdown', {
6178
6309
  tabindex: 0,
6179
6310
  oncreate: ({ dom }) => {
@@ -6331,7 +6462,6 @@ const Tabs = () => {
6331
6462
  }
6332
6463
  state.isDragging = false;
6333
6464
  state.translateX = 0;
6334
- // m.redraw();
6335
6465
  };
6336
6466
  /** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
6337
6467
  const setActiveTabId = (anchoredTabs, selectedTabId) => {
@@ -6358,7 +6488,6 @@ const Tabs = () => {
6358
6488
  },
6359
6489
  oncreate: () => {
6360
6490
  updateIndicator();
6361
- m.redraw();
6362
6491
  },
6363
6492
  view: ({ attrs }) => {
6364
6493
  const { tabWidth, tabs, className, style, swipeable = false } = attrs;
@@ -6492,7 +6621,6 @@ const SearchSelect = () => {
6492
6621
  else {
6493
6622
  // Click outside, close dropdown
6494
6623
  state.isOpen = false;
6495
- m.redraw();
6496
6624
  }
6497
6625
  };
6498
6626
  // Handle keyboard navigation
@@ -6727,9 +6855,7 @@ const SearchSelect = () => {
6727
6855
  ]),
6728
6856
  // No options found message or list of options
6729
6857
  ...(filteredOptions.length === 0 && !showAddNew
6730
- ? [
6731
- m('li.search-select-no-options', texts.noOptionsFound),
6732
- ]
6858
+ ? [m('li.search-select-no-options', texts.noOptionsFound)]
6733
6859
  : []),
6734
6860
  // Add new option item
6735
6861
  ...(showAddNew