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.umd.js CHANGED
@@ -232,8 +232,6 @@
232
232
  if (attrs.onAutocomplete) {
233
233
  attrs.onAutocomplete(suggestion.key);
234
234
  }
235
- // Force redraw to update label state
236
- m.redraw();
237
235
  };
238
236
  const handleKeydown = (e, attrs) => {
239
237
  if (!state.isOpen)
@@ -312,7 +310,7 @@
312
310
  const id = attrs.id || state.id;
313
311
  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"]);
314
312
  const controlled = isControlled(attrs);
315
- const currentValue = controlled ? (attrs.value || '') : state.internalValue;
313
+ const currentValue = controlled ? attrs.value || '' : state.internalValue;
316
314
  const cn = newRow ? className + ' clear' : className;
317
315
  // Update suggestions when input changes
318
316
  state.suggestions = filterSuggestions(currentValue, data, limit, minLength);
@@ -327,7 +325,7 @@
327
325
  style,
328
326
  }, [
329
327
  iconName ? m('i.material-icons.prefix', iconName) : '',
330
- m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: controlled ? currentValue : undefined, oncreate: (vnode) => {
328
+ m('input', Object.assign(Object.assign({}, params), { className: 'autocomplete', type: 'text', tabindex: 0, id, value: currentValue, oncreate: (vnode) => {
331
329
  state.inputElement = vnode.dom;
332
330
  // Set initial value for uncontrolled mode
333
331
  if (!controlled && attrs.defaultValue) {
@@ -363,14 +361,10 @@
363
361
  }
364
362
  }, onblur: (e) => {
365
363
  state.isActive = false;
366
- // Delay closing to allow clicks on suggestions
367
- setTimeout(() => {
368
- if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
369
- state.isOpen = false;
370
- state.selectedIndex = -1;
371
- m.redraw();
372
- }
373
- }, 150);
364
+ if (!e.relatedTarget || !e.relatedTarget.closest('.autocomplete-content')) {
365
+ state.isOpen = false;
366
+ state.selectedIndex = -1;
367
+ }
374
368
  } })),
375
369
  // Autocomplete dropdown
376
370
  state.isOpen &&
@@ -386,7 +380,6 @@
386
380
  },
387
381
  onmouseover: () => {
388
382
  state.selectedIndex = index;
389
- m.redraw();
390
383
  },
391
384
  }, [
392
385
  // Check if value contains image URL or icon
@@ -2487,36 +2480,52 @@
2487
2480
  return null;
2488
2481
  }
2489
2482
  };
2483
+ const isControlled = (attrs) => {
2484
+ return attrs.value !== undefined && typeof attrs.oninput === 'function';
2485
+ };
2486
+ const isRangeControlled = (attrs) => {
2487
+ return (attrs.minValue !== undefined || attrs.maxValue !== undefined) && typeof attrs.oninput === 'function';
2488
+ };
2490
2489
  const initRangeState = (state, attrs) => {
2491
- const { min = 0, max = 100, value, minValue, maxValue } = attrs;
2490
+ const { min = 0, max = 100, value, minValue, maxValue, defaultValue } = attrs;
2492
2491
  // Initialize single range value
2493
- if (value !== undefined) {
2494
- const currentValue = value;
2492
+ if (isControlled(attrs)) {
2493
+ // Always use value from props in controlled mode
2494
+ state.singleValue = value !== undefined ? value : min;
2495
+ }
2496
+ else {
2497
+ // Use internal state for uncontrolled mode
2495
2498
  if (state.singleValue === undefined) {
2496
- state.singleValue = currentValue;
2499
+ state.singleValue = defaultValue !== undefined ? defaultValue : value !== undefined ? value : min;
2497
2500
  }
2498
- if (state.lastValue !== value && !state.hasUserInteracted) {
2501
+ // Only update internal state if props changed and user hasn't interacted
2502
+ if (state.lastValue !== value && !state.hasUserInteracted && value !== undefined) {
2499
2503
  state.singleValue = value;
2500
2504
  state.lastValue = value;
2501
2505
  }
2502
2506
  }
2503
- else if (state.singleValue === undefined) {
2504
- state.singleValue = min;
2505
- }
2506
2507
  // Initialize range values
2507
- const currentMinValue = minValue !== undefined ? minValue : min;
2508
- const currentMaxValue = maxValue !== undefined ? maxValue : max;
2509
- if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
2510
- state.rangeMinValue = currentMinValue;
2511
- state.rangeMaxValue = currentMaxValue;
2508
+ if (isRangeControlled(attrs)) {
2509
+ // Always use values from props in controlled mode
2510
+ state.rangeMinValue = minValue !== undefined ? minValue : min;
2511
+ state.rangeMaxValue = maxValue !== undefined ? maxValue : max;
2512
2512
  }
2513
- if (!state.hasUserInteracted &&
2514
- ((minValue !== undefined && state.lastMinValue !== minValue) ||
2515
- (maxValue !== undefined && state.lastMaxValue !== maxValue))) {
2516
- state.rangeMinValue = currentMinValue;
2517
- state.rangeMaxValue = currentMaxValue;
2518
- state.lastMinValue = minValue;
2519
- state.lastMaxValue = maxValue;
2513
+ else {
2514
+ // Use internal state for uncontrolled mode
2515
+ const currentMinValue = minValue !== undefined ? minValue : min;
2516
+ const currentMaxValue = maxValue !== undefined ? maxValue : max;
2517
+ if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
2518
+ state.rangeMinValue = currentMinValue;
2519
+ state.rangeMaxValue = currentMaxValue;
2520
+ }
2521
+ if (!state.hasUserInteracted &&
2522
+ ((minValue !== undefined && state.lastMinValue !== minValue) ||
2523
+ (maxValue !== undefined && state.lastMaxValue !== maxValue))) {
2524
+ state.rangeMinValue = currentMinValue;
2525
+ state.rangeMaxValue = currentMaxValue;
2526
+ state.lastMinValue = minValue;
2527
+ state.lastMaxValue = maxValue;
2528
+ }
2520
2529
  }
2521
2530
  // Initialize active thumb if not set
2522
2531
  if (state.activeThumb === null) {
@@ -2533,15 +2542,18 @@
2533
2542
  minValue = maxValue;
2534
2543
  if (maxValue < minValue)
2535
2544
  maxValue = minValue;
2536
- state.rangeMinValue = minValue;
2537
- state.rangeMaxValue = maxValue;
2545
+ // Only update internal state for uncontrolled mode
2546
+ if (!isRangeControlled(attrs)) {
2547
+ state.rangeMinValue = minValue;
2548
+ state.rangeMaxValue = maxValue;
2549
+ }
2538
2550
  state.hasUserInteracted = true;
2539
- // Call oninput for immediate feedback or onchange for final changes
2551
+ // Call appropriate handler based on interaction type, not control mode
2540
2552
  if (immediate && attrs.oninput) {
2541
- attrs.oninput(minValue, maxValue);
2553
+ attrs.oninput(minValue, maxValue); // Immediate feedback during drag
2542
2554
  }
2543
- else if (!immediate && attrs.onchange) {
2544
- attrs.onchange(minValue, maxValue);
2555
+ if (!immediate && attrs.onchange) {
2556
+ attrs.onchange(minValue, maxValue); // Final value on interaction end (blur/mouseup)
2545
2557
  }
2546
2558
  };
2547
2559
  // Single Range Slider Component
@@ -2575,19 +2587,24 @@
2575
2587
  : tooltipPos
2576
2588
  : tooltipPos;
2577
2589
  const updateSingleValue = (newValue, immediate = false) => {
2578
- state.singleValue = newValue;
2590
+ // Only update internal state for uncontrolled mode
2591
+ if (!isControlled(attrs)) {
2592
+ state.singleValue = newValue;
2593
+ }
2579
2594
  state.hasUserInteracted = true;
2595
+ // Call appropriate handler based on interaction type, not control mode
2580
2596
  if (immediate && oninput) {
2581
- oninput(newValue);
2597
+ oninput(newValue); // Immediate feedback during drag
2582
2598
  }
2583
- else if (!immediate && onchange) {
2584
- onchange(newValue);
2599
+ if (!immediate && onchange) {
2600
+ onchange(newValue); // Final value on interaction end (blur/mouseup)
2585
2601
  }
2586
2602
  };
2587
2603
  const handleMouseDown = (e) => {
2588
2604
  if (disabled)
2589
2605
  return;
2590
2606
  e.preventDefault();
2607
+ e.stopPropagation();
2591
2608
  state.isDragging = true;
2592
2609
  if (finalValueDisplay === 'auto') {
2593
2610
  m.redraw();
@@ -2662,6 +2679,11 @@
2662
2679
  updateSingleValue(newValue, false);
2663
2680
  }
2664
2681
  },
2682
+ onblur: () => {
2683
+ if (disabled || !onchange)
2684
+ return;
2685
+ onchange(state.singleValue);
2686
+ },
2665
2687
  }, [
2666
2688
  m(`.track.${orientation}`),
2667
2689
  m(`.range-progress.${orientation}`, { style: progressStyle }),
@@ -2720,6 +2742,7 @@
2720
2742
  if (disabled)
2721
2743
  return;
2722
2744
  e.preventDefault();
2745
+ e.stopPropagation();
2723
2746
  state.isDragging = true;
2724
2747
  state.activeThumb = thumb;
2725
2748
  if (finalValueDisplay === 'auto') {
@@ -2810,6 +2833,11 @@
2810
2833
  maxThumb.focus();
2811
2834
  }
2812
2835
  },
2836
+ onblur: () => {
2837
+ if (disabled || !attrs.onchange)
2838
+ return;
2839
+ attrs.onchange(state.rangeMinValue, state.rangeMaxValue);
2840
+ },
2813
2841
  }, [
2814
2842
  m(`.track.${orientation}`),
2815
2843
  m(`.range.${orientation}`, { style: rangeStyle }),
@@ -3015,13 +3043,13 @@
3015
3043
  overflowWrap: 'break-word',
3016
3044
  },
3017
3045
  oncreate: ({ dom }) => {
3018
- const hiddenDiv = state.hiddenDiv = dom;
3046
+ const hiddenDiv = (state.hiddenDiv = dom);
3019
3047
  if (state.textarea) {
3020
3048
  updateHeight(state.textarea, hiddenDiv);
3021
3049
  }
3022
3050
  },
3023
3051
  onupdate: ({ dom }) => {
3024
- const hiddenDiv = state.hiddenDiv = dom;
3052
+ const hiddenDiv = (state.hiddenDiv = dom);
3025
3053
  if (state.textarea) {
3026
3054
  updateHeight(state.textarea, hiddenDiv);
3027
3055
  }
@@ -3134,8 +3162,7 @@
3134
3162
  isDragging: false,
3135
3163
  activeThumb: null,
3136
3164
  };
3137
- const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' &&
3138
- (typeof attrs.oninput === 'function' || typeof attrs.onchange === 'function');
3165
+ const isControlled = (attrs) => 'value' in attrs && typeof attrs.value !== 'undefined' && typeof attrs.oninput === 'function';
3139
3166
  const getValue = (target) => {
3140
3167
  const val = target.value;
3141
3168
  return (val ? (type === 'number' || type === 'range' ? +val : val) : val);
@@ -3185,7 +3212,7 @@
3185
3212
  const isNonInteractive = attrs.readonly || attrs.disabled;
3186
3213
  // Warn developer for improper controlled usage
3187
3214
  if (attrs.value !== undefined && !controlled && !isNonInteractive) {
3188
- console.warn(`${type} input received 'value' prop without 'oninput' or 'onchange' handler. ` +
3215
+ console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
3189
3216
  `Use 'defaultValue' for uncontrolled components or add an event handler for controlled components.`);
3190
3217
  }
3191
3218
  // Initialize internal value if not in controlled mode
@@ -4212,14 +4239,12 @@
4212
4239
  inputRef: null,
4213
4240
  dropdownRef: null,
4214
4241
  internalCheckedId: undefined,
4242
+ isInsideModal: false,
4215
4243
  };
4216
4244
  const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
4217
- const closeDropdown = (e) => {
4218
- const target = e.target;
4219
- if (!target.closest('.dropdown-wrapper.input-field')) {
4220
- state.isOpen = false;
4221
- m.redraw();
4222
- }
4245
+ const closeDropdown = () => {
4246
+ state.isOpen = false;
4247
+ m.redraw(); // Needed to remove the dropdown options list (potentially added to document root)
4223
4248
  };
4224
4249
  const handleKeyDown = (e, items) => {
4225
4250
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
@@ -4264,6 +4289,83 @@
4264
4289
  return undefined;
4265
4290
  }
4266
4291
  };
4292
+ const getPortalStyles = (inputRef) => {
4293
+ if (!inputRef)
4294
+ return {};
4295
+ const rect = inputRef.getBoundingClientRect();
4296
+ const viewportHeight = window.innerHeight;
4297
+ const spaceBelow = viewportHeight - rect.bottom;
4298
+ const spaceAbove = rect.top;
4299
+ // Choose whether to show above or below based on available space
4300
+ const showAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
4301
+ return {
4302
+ position: 'fixed',
4303
+ top: showAbove ? 'auto' : `${rect.bottom}px`,
4304
+ bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
4305
+ left: `${rect.left}px`,
4306
+ width: `${rect.width}px`,
4307
+ zIndex: 10000,
4308
+ maxHeight: showAbove ? `${spaceAbove - 20}px` : `${spaceBelow - 20}px`,
4309
+ overflow: 'auto',
4310
+ display: 'block',
4311
+ opacity: 1,
4312
+ };
4313
+ };
4314
+ const updatePortalDropdown = (items, selectedLabel, onSelectItem) => {
4315
+ if (!state.isInsideModal)
4316
+ return;
4317
+ // Clean up existing portal
4318
+ const existingPortal = document.getElementById(`${state.id}-dropdown`);
4319
+ if (existingPortal) {
4320
+ existingPortal.remove();
4321
+ }
4322
+ if (!state.isOpen || !state.inputRef)
4323
+ return;
4324
+ // Create portal element
4325
+ const portalElement = document.createElement('div');
4326
+ portalElement.id = `${state.id}-dropdown`;
4327
+ document.body.appendChild(portalElement);
4328
+ // Create dropdown content
4329
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
4330
+ const dropdownContent = items.map((item) => {
4331
+ if (item.divider) {
4332
+ return m('li.divider');
4333
+ }
4334
+ const itemIndex = availableItems.indexOf(item);
4335
+ const isSelected = selectedLabel === item.label;
4336
+ const isFocused = state.focusedIndex === itemIndex;
4337
+ return m('li', {
4338
+ class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
4339
+ onclick: item.disabled ? undefined : () => onSelectItem(item),
4340
+ }, m('span', {
4341
+ style: {
4342
+ display: 'flex',
4343
+ alignItems: 'center',
4344
+ padding: '14px 16px',
4345
+ },
4346
+ }, [
4347
+ item.iconName
4348
+ ? m('i.material-icons', {
4349
+ style: { marginRight: '32px' },
4350
+ }, item.iconName)
4351
+ : undefined,
4352
+ item.label,
4353
+ ]));
4354
+ });
4355
+ // Create dropdown with proper positioning
4356
+ const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
4357
+ tabindex: 0,
4358
+ style: getPortalStyles(state.inputRef),
4359
+ oncreate: ({ dom }) => {
4360
+ state.dropdownRef = dom;
4361
+ },
4362
+ onremove: () => {
4363
+ state.dropdownRef = null;
4364
+ },
4365
+ }, dropdownContent);
4366
+ // Render to portal
4367
+ m.render(portalElement, dropdownVnode);
4368
+ };
4267
4369
  return {
4268
4370
  oninit: ({ attrs }) => {
4269
4371
  var _a;
@@ -4275,9 +4377,18 @@
4275
4377
  // Add global click listener to close dropdown
4276
4378
  document.addEventListener('click', closeDropdown);
4277
4379
  },
4380
+ oncreate: ({ dom }) => {
4381
+ // Detect if component is inside a modal
4382
+ state.isInsideModal = !!dom.closest('.modal');
4383
+ },
4278
4384
  onremove: () => {
4279
4385
  // Cleanup global listener
4280
4386
  document.removeEventListener('click', closeDropdown);
4387
+ // Cleanup portal
4388
+ const portalElement = document.getElementById(`${state.id}-dropdown`);
4389
+ if (portalElement) {
4390
+ portalElement.remove();
4391
+ }
4281
4392
  },
4282
4393
  view: ({ attrs }) => {
4283
4394
  const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
@@ -4300,6 +4411,16 @@
4300
4411
  : undefined;
4301
4412
  const title = selectedItem ? selectedItem.label : label || 'Select';
4302
4413
  const availableItems = items.filter((item) => !item.divider && !item.disabled);
4414
+ // Update portal dropdown when inside modal
4415
+ if (state.isInsideModal) {
4416
+ updatePortalDropdown(items, title, (item) => {
4417
+ if (item.id) {
4418
+ state.isOpen = false;
4419
+ state.focusedIndex = -1;
4420
+ handleSelection(item.id);
4421
+ }
4422
+ });
4423
+ }
4303
4424
  return m('.dropdown-wrapper.input-field', { className, key, style }, [
4304
4425
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
4305
4426
  m(HelperText, { helperText }),
@@ -4333,8 +4454,9 @@
4333
4454
  }
4334
4455
  },
4335
4456
  }),
4336
- // Dropdown Menu using Select component's positioning logic
4457
+ // Dropdown Menu - render inline only when NOT inside modal
4337
4458
  state.isOpen &&
4459
+ !state.isInsideModal &&
4338
4460
  m('ul.dropdown-content.select-dropdown', {
4339
4461
  tabindex: 0,
4340
4462
  role: 'listbox',
@@ -4784,7 +4906,7 @@
4784
4906
  maxWidth: '75%',
4785
4907
  borderRadius: '4px',
4786
4908
  })), { 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)' }),
4787
- onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
4909
+ // onclick: (e: Event) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
4788
4910
  }, [
4789
4911
  // Close button
4790
4912
  showCloseButton &&
@@ -5880,6 +6002,7 @@
5880
6002
  dropdownRef: null,
5881
6003
  internalSelectedIds: [],
5882
6004
  isInsideModal: false,
6005
+ isMultiple: false,
5883
6006
  };
5884
6007
  const isControlled = (attrs) => attrs.checkedId !== undefined && attrs.onchange !== undefined;
5885
6008
  const isSelected = (id, selectedIds) => {
@@ -5962,10 +6085,16 @@
5962
6085
  }
5963
6086
  };
5964
6087
  const closeDropdown = (e) => {
6088
+ if (!state.isMultiple) {
6089
+ state.isOpen = false;
6090
+ return;
6091
+ }
5965
6092
  const target = e.target;
5966
- if (!target.closest('.input-field.select-space')) {
6093
+ // When inside modal, check both the select component AND the portaled dropdown
6094
+ const isClickInsideSelect = target.closest('.input-field.select-space');
6095
+ const isClickInsidePortalDropdown = state.isInsideModal && state.dropdownRef && (state.dropdownRef.contains(target) || target === state.dropdownRef);
6096
+ if (!isClickInsideSelect && !isClickInsidePortalDropdown) {
5967
6097
  state.isOpen = false;
5968
- m.redraw();
5969
6098
  }
5970
6099
  };
5971
6100
  const getPortalStyles = (inputRef) => {
@@ -5973,13 +6102,21 @@
5973
6102
  return {};
5974
6103
  const rect = inputRef.getBoundingClientRect();
5975
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;
5976
6109
  return {
5977
6110
  position: 'fixed',
5978
- top: `${rect.bottom}px`,
6111
+ top: showAbove ? 'auto' : `${rect.bottom}px`,
6112
+ bottom: showAbove ? `${viewportHeight - rect.top}px` : 'auto',
5979
6113
  left: `${rect.left}px`,
5980
6114
  width: `${rect.width}px`,
5981
6115
  zIndex: 10000, // Higher than modal z-index
5982
- 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,
5983
6120
  };
5984
6121
  };
5985
6122
  const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
@@ -5987,15 +6124,10 @@
5987
6124
  // Render ungrouped options first
5988
6125
  attrs.options
5989
6126
  .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
6127
+ .map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
5995
6128
  ? {}
5996
6129
  : {
5997
- onclick: (e) => {
5998
- e.stopPropagation();
6130
+ onclick: () => {
5999
6131
  toggleOption(option.id, multiple, attrs);
6000
6132
  },
6001
6133
  })), [
@@ -6025,8 +6157,8 @@
6025
6157
  return groups;
6026
6158
  }, {}))
6027
6159
  .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
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
6030
6162
  ? {}
6031
6163
  : {
6032
6164
  onclick: (e) => {
@@ -6053,23 +6185,20 @@
6053
6185
  .reduce((acc, val) => acc.concat(val), []),
6054
6186
  ];
6055
6187
  const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
6056
- var _a;
6057
6188
  if (!state.isInsideModal)
6058
6189
  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);
6190
+ // Clean up existing portal
6191
+ const existingPortal = document.getElementById(state.dropdownId);
6192
+ if (existingPortal) {
6193
+ existingPortal.remove();
6072
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
6073
6202
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6074
6203
  tabindex: 0,
6075
6204
  style: getPortalStyles(state.inputRef),
@@ -6080,6 +6209,7 @@
6080
6209
  state.dropdownRef = null;
6081
6210
  },
6082
6211
  }, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
6212
+ // Render to portal
6083
6213
  m.render(portalElement, dropdownVnode);
6084
6214
  };
6085
6215
  return {
@@ -6122,7 +6252,8 @@
6122
6252
  view: ({ attrs }) => {
6123
6253
  var _a;
6124
6254
  const controlled = isControlled(attrs);
6125
- 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;
6126
6257
  // Get selected IDs from props or internal state
6127
6258
  let selectedIds;
6128
6259
  if (controlled) {
@@ -6138,7 +6269,6 @@
6138
6269
  // Interactive uncontrolled: use internal state
6139
6270
  selectedIds = state.internalSelectedIds;
6140
6271
  }
6141
- const { newRow, className = 'col s12', key, options, multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, } = attrs;
6142
6272
  const finalClassName = newRow ? `${className} clear` : className;
6143
6273
  const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6144
6274
  // Update portal dropdown when inside modal
@@ -6175,7 +6305,8 @@
6175
6305
  },
6176
6306
  }),
6177
6307
  // Dropdown Menu - render inline only when NOT inside modal
6178
- state.isOpen && !state.isInsideModal &&
6308
+ state.isOpen &&
6309
+ !state.isInsideModal &&
6179
6310
  m('ul.dropdown-content.select-dropdown', {
6180
6311
  tabindex: 0,
6181
6312
  oncreate: ({ dom }) => {
@@ -6333,7 +6464,6 @@
6333
6464
  }
6334
6465
  state.isDragging = false;
6335
6466
  state.translateX = 0;
6336
- // m.redraw();
6337
6467
  };
6338
6468
  /** Initialize active tab - selectedTabId takes precedence, next active property or first available tab */
6339
6469
  const setActiveTabId = (anchoredTabs, selectedTabId) => {
@@ -6360,7 +6490,6 @@
6360
6490
  },
6361
6491
  oncreate: () => {
6362
6492
  updateIndicator();
6363
- m.redraw();
6364
6493
  },
6365
6494
  view: ({ attrs }) => {
6366
6495
  const { tabWidth, tabs, className, style, swipeable = false } = attrs;
@@ -6494,7 +6623,6 @@
6494
6623
  else {
6495
6624
  // Click outside, close dropdown
6496
6625
  state.isOpen = false;
6497
- m.redraw();
6498
6626
  }
6499
6627
  };
6500
6628
  // Handle keyboard navigation
@@ -6729,9 +6857,7 @@
6729
6857
  ]),
6730
6858
  // No options found message or list of options
6731
6859
  ...(filteredOptions.length === 0 && !showAddNew
6732
- ? [
6733
- m('li.search-select-no-options', texts.noOptionsFound),
6734
- ]
6860
+ ? [m('li.search-select-no-options', texts.noOptionsFound)]
6735
6861
  : []),
6736
6862
  // Add new option item
6737
6863
  ...(showAddNew
@@ -2225,6 +2225,7 @@ table span.badge {
2225
2225
  font-size: 16px;
2226
2226
  line-height: 32px;
2227
2227
  padding-left: 8px;
2228
+ min-height: 1lh;
2228
2229
  }
2229
2230
 
2230
2231
  .chips {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-materialized",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "A materialize library for mithril.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -33,6 +33,7 @@
33
33
  font-size: 16px;
34
34
  line-height: 32px;
35
35
  padding-left: 8px;
36
+ min-height: 1lh;
36
37
  }
37
38
  }
38
39
 
@@ -150,9 +150,9 @@ body.keyboard-focused {
150
150
  background-color: var(--mm-dropdown-hover, variables.$select-option-hover);
151
151
  }
152
152
 
153
- &.selected {
154
- background-color: var(--mm-dropdown-selected, variables.$select-option-selected);
155
- }
153
+ // &.selected {
154
+ // background-color: var(--mm-dropdown-selected, variables.$select-option-selected);
155
+ // }
156
156
  }
157
157
  }
158
158