mithril-materialized 3.4.0 → 3.4.1

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,18 @@ const Select = () => {
5960
6083
  }
5961
6084
  };
5962
6085
  const closeDropdown = (e) => {
6086
+ console.log('select closeDropdown called');
6087
+ if (!state.isMultiple) {
6088
+ state.isOpen = false;
6089
+ return;
6090
+ }
5963
6091
  const target = e.target;
5964
- if (!target.closest('.input-field.select-space')) {
6092
+ // When inside modal, check both the select component AND the portaled dropdown
6093
+ const isClickInsideSelect = target.closest('.input-field.select-space');
6094
+ const isClickInsidePortalDropdown = state.isInsideModal && state.dropdownRef && (state.dropdownRef.contains(target) || target === state.dropdownRef);
6095
+ if (!isClickInsideSelect && !isClickInsidePortalDropdown) {
6096
+ console.log('select closeDropdown called: set state');
5965
6097
  state.isOpen = false;
5966
- m.redraw();
5967
6098
  }
5968
6099
  };
5969
6100
  const getPortalStyles = (inputRef) => {
@@ -5971,13 +6102,21 @@ const Select = () => {
5971
6102
  return {};
5972
6103
  const rect = inputRef.getBoundingClientRect();
5973
6104
  const viewportHeight = window.innerHeight;
6105
+ const spaceBelow = viewportHeight - rect.bottom;
6106
+ const spaceAbove = rect.top;
6107
+ // Choose whether to show above or below based on available space
6108
+ const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
5974
6109
  return {
5975
6110
  position: 'fixed',
5976
- top: `${rect.bottom}px`,
6111
+ top: showAbove ? 'auto' : `${rect.bottom}px`,
6112
+ bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
5977
6113
  left: `${rect.left}px`,
5978
6114
  width: `${rect.width}px`,
5979
6115
  zIndex: 10000, // Higher than modal z-index
5980
- maxHeight: `${viewportHeight - rect.bottom - 20}px`, // Leave 20px margin from bottom
6116
+ maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`, // Leave 20px margin
6117
+ overflow: 'auto',
6118
+ display: 'block',
6119
+ opacity: 1,
5981
6120
  };
5982
6121
  };
5983
6122
  const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
@@ -5985,15 +6124,10 @@ const Select = () => {
5985
6124
  // Render ungrouped options first
5986
6125
  attrs.options
5987
6126
  .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
6127
+ .map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
5993
6128
  ? {}
5994
6129
  : {
5995
- onclick: (e) => {
5996
- e.stopPropagation();
6130
+ onclick: () => {
5997
6131
  toggleOption(option.id, multiple, attrs);
5998
6132
  },
5999
6133
  })), [
@@ -6023,8 +6157,8 @@ const Select = () => {
6023
6157
  return groups;
6024
6158
  }, {}))
6025
6159
  .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
6160
+ m('li.optgroup', { tabindex: 0 }, m('span', groupName)),
6161
+ ...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
6162
  ? {}
6029
6163
  : {
6030
6164
  onclick: (e) => {
@@ -6051,23 +6185,20 @@ const Select = () => {
6051
6185
  .reduce((acc, val) => acc.concat(val), []),
6052
6186
  ];
6053
6187
  const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
6054
- var _a;
6055
6188
  if (!state.isInsideModal)
6056
6189
  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);
6190
+ // Clean up existing portal
6191
+ const existingPortal = document.getElementById(state.dropdownId);
6192
+ if (existingPortal) {
6193
+ existingPortal.remove();
6070
6194
  }
6195
+ if (!state.isOpen || !state.inputRef)
6196
+ return;
6197
+ // Create portal element
6198
+ const portalElement = document.createElement('div');
6199
+ portalElement.id = state.dropdownId;
6200
+ document.body.appendChild(portalElement);
6201
+ // Create dropdown with proper positioning
6071
6202
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6072
6203
  tabindex: 0,
6073
6204
  style: getPortalStyles(state.inputRef),
@@ -6078,6 +6209,7 @@ const Select = () => {
6078
6209
  state.dropdownRef = null;
6079
6210
  },
6080
6211
  }, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
6212
+ // Render to portal
6081
6213
  m.render(portalElement, dropdownVnode);
6082
6214
  };
6083
6215
  return {
@@ -6120,7 +6252,8 @@ const Select = () => {
6120
6252
  view: ({ attrs }) => {
6121
6253
  var _a;
6122
6254
  const controlled = isControlled(attrs);
6123
- const { disabled } = attrs;
6255
+ const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
6256
+ state.isMultiple = multiple;
6124
6257
  // Get selected IDs from props or internal state
6125
6258
  let selectedIds;
6126
6259
  if (controlled) {
@@ -6136,7 +6269,6 @@ const Select = () => {
6136
6269
  // Interactive uncontrolled: use internal state
6137
6270
  selectedIds = state.internalSelectedIds;
6138
6271
  }
6139
- const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
6140
6272
  const finalClassName = newRow ? `${className} clear` : className;
6141
6273
  const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6142
6274
  // Update portal dropdown when inside modal
@@ -6173,7 +6305,8 @@ const Select = () => {
6173
6305
  },
6174
6306
  }),
6175
6307
  // Dropdown Menu - render inline only when NOT inside modal
6176
- state.isOpen && !state.isInsideModal &&
6308
+ state.isOpen &&
6309
+ !state.isInsideModal &&
6177
6310
  m('ul.dropdown-content.select-dropdown', {
6178
6311
  tabindex: 0,
6179
6312
  oncreate: ({ dom }) => {
@@ -6331,7 +6464,6 @@ const Tabs = () => {
6331
6464
  }
6332
6465
  state.isDragging = false;
6333
6466
  state.translateX = 0;
6334
- // m.redraw();
6335
6467
  };
6336
6468
  /** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
6337
6469
  const setActiveTabId = (anchoredTabs, selectedTabId) => {
@@ -6358,7 +6490,6 @@ const Tabs = () => {
6358
6490
  },
6359
6491
  oncreate: () => {
6360
6492
  updateIndicator();
6361
- m.redraw();
6362
6493
  },
6363
6494
  view: ({ attrs }) => {
6364
6495
  const { tabWidth, tabs, className, style, swipeable = false } = attrs;
@@ -6492,7 +6623,6 @@ const SearchSelect = () => {
6492
6623
  else {
6493
6624
  // Click outside, close dropdown
6494
6625
  state.isOpen = false;
6495
- m.redraw();
6496
6626
  }
6497
6627
  };
6498
6628
  // Handle keyboard navigation
@@ -6727,9 +6857,7 @@ const SearchSelect = () => {
6727
6857
  ]),
6728
6858
  // No options found message or list of options
6729
6859
  ...(filteredOptions.length === 0 && !showAddNew
6730
- ? [
6731
- m('li.search-select-no-options', texts.noOptionsFound),
6732
- ]
6860
+ ? [m('li.search-select-no-options', texts.noOptionsFound)]
6733
6861
  : []),
6734
6862
  // Add new option item
6735
6863
  ...(showAddNew