mithril-materialized 3.5.10 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -66,6 +66,28 @@
66
66
  };
67
67
  /** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
68
68
  const isNumeric = (n) => !isNaN(parseFloat(n)) && isFinite(n);
69
+ /**
70
+ * Sort options array based on sorting configuration
71
+ * @param options - Array of options to sort
72
+ * @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
73
+ * @returns Sorted array (or original if 'none' or undefined)
74
+ */
75
+ const sortOptions = (options, sortConfig) => {
76
+ if (!sortConfig || sortConfig === 'none') {
77
+ return options;
78
+ }
79
+ const sorted = [...options]; // Create a copy to avoid mutating original
80
+ if (typeof sortConfig === 'function') {
81
+ return sorted.sort(sortConfig);
82
+ }
83
+ // Sort by label, fallback to id if no label
84
+ return sorted.sort((a, b) => {
85
+ const aLabel = (a.label || a.id.toString()).toLowerCase();
86
+ const bLabel = (b.label || b.id.toString()).toLowerCase();
87
+ const comparison = aLabel.localeCompare(bLabel);
88
+ return sortConfig === 'asc' ? comparison : -comparison;
89
+ });
90
+ };
69
91
  /**
70
92
  * Pad left, default width 2 with a '0'
71
93
  *
@@ -4543,7 +4565,7 @@
4543
4565
  // Create dropdown with proper positioning
4544
4566
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
4545
4567
  tabindex: 0,
4546
- style: getPortalStyles(state.inputRef),
4568
+ style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
4547
4569
  oncreate: ({ dom }) => {
4548
4570
  state.dropdownRef = dom;
4549
4571
  },
@@ -4655,7 +4677,7 @@
4655
4677
  onremove: () => {
4656
4678
  state.dropdownRef = null;
4657
4679
  },
4658
- style: getDropdownStyles(state.inputRef, true, items, true),
4680
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, items, true)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
4659
4681
  }, items.map((item) => {
4660
4682
  if (item.divider) {
4661
4683
  return m('li.divider');
@@ -6429,7 +6451,7 @@
6429
6451
  // Create dropdown with proper positioning
6430
6452
  const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
6431
6453
  tabindex: 0,
6432
- style: getPortalStyles(state.inputRef),
6454
+ style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
6433
6455
  oncreate: ({ dom }) => {
6434
6456
  state.dropdownRef = dom;
6435
6457
  },
@@ -6498,7 +6520,8 @@
6498
6520
  selectedIds = state.internalSelectedIds;
6499
6521
  }
6500
6522
  const finalClassName = newRow ? `${className} clear` : className;
6501
- const selectedOptions = options.filter((opt) => isSelected(opt.id, selectedIds));
6523
+ const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
6524
+ const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
6502
6525
  // Update portal dropdown when inside modal
6503
6526
  if (state.isInsideModal) {
6504
6527
  updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
@@ -6543,7 +6566,7 @@
6543
6566
  onremove: () => {
6544
6567
  state.dropdownRef = null;
6545
6568
  },
6546
- style: getDropdownStyles(state.inputRef, true, options),
6569
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, options)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
6547
6570
  }, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
6548
6571
  m(MaterialIcon, {
6549
6572
  name: 'caret',
@@ -6793,7 +6816,7 @@
6793
6816
  ]),
6794
6817
  };
6795
6818
  const DropdownOption = {
6796
- view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
6819
+ view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
6797
6820
  const checkboxId = `search-select-option-${option.id}`;
6798
6821
  const optionLabel = option.label || option.id.toString();
6799
6822
  return m('li', {
@@ -6810,11 +6833,12 @@
6810
6833
  }
6811
6834
  },
6812
6835
  }, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
6813
- m('input', {
6814
- type: 'checkbox',
6815
- id: checkboxId,
6816
- checked: selectedIds.includes(option.id),
6817
- }),
6836
+ showCheckbox &&
6837
+ m('input', {
6838
+ type: 'checkbox',
6839
+ id: checkboxId,
6840
+ checked: selectedIds.includes(option.id),
6841
+ }),
6818
6842
  m('span', optionLabel),
6819
6843
  ]));
6820
6844
  },
@@ -6832,6 +6856,7 @@
6832
6856
  dropdownRef: null,
6833
6857
  focusedIndex: -1,
6834
6858
  internalSelectedIds: [],
6859
+ createdOptions: [],
6835
6860
  };
6836
6861
  const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
6837
6862
  const componentId = uniqueId();
@@ -6874,7 +6899,10 @@
6874
6899
  // Handle add new option
6875
6900
  return 'addNew';
6876
6901
  }
6877
- else if (state.focusedIndex < filteredOptions.length) ;
6902
+ else if (state.focusedIndex < filteredOptions.length) {
6903
+ // This will be handled in the view method where attrs are available
6904
+ return 'selectOption';
6905
+ }
6878
6906
  }
6879
6907
  break;
6880
6908
  case 'Escape':
@@ -6885,11 +6913,22 @@
6885
6913
  }
6886
6914
  return null;
6887
6915
  };
6916
+ // Create new option and add to state
6917
+ const createAndSelectOption = async (attrs) => {
6918
+ if (!attrs.oncreateNewOption || !state.searchTerm)
6919
+ return;
6920
+ const newOption = await attrs.oncreateNewOption(state.searchTerm);
6921
+ // Store the created option internally
6922
+ state.createdOptions.push(newOption);
6923
+ // Select the new option
6924
+ toggleOption(newOption, attrs);
6925
+ };
6888
6926
  // Toggle option selection
6889
6927
  const toggleOption = (option, attrs) => {
6890
6928
  if (option.disabled)
6891
6929
  return;
6892
6930
  const controlled = isControlled(attrs);
6931
+ const { maxSelectedOptions } = attrs;
6893
6932
  // Get current selected IDs from props or internal state
6894
6933
  const currentSelectedIds = controlled
6895
6934
  ? attrs.checkedId !== undefined
@@ -6898,9 +6937,29 @@
6898
6937
  : [attrs.checkedId]
6899
6938
  : []
6900
6939
  : state.internalSelectedIds;
6901
- const newIds = currentSelectedIds.includes(option.id)
6902
- ? currentSelectedIds.filter((id) => id !== option.id)
6903
- : [...currentSelectedIds, option.id];
6940
+ const isSelected = currentSelectedIds.includes(option.id);
6941
+ let newIds;
6942
+ if (isSelected) {
6943
+ // Remove if already selected
6944
+ newIds = currentSelectedIds.filter((id) => id !== option.id);
6945
+ }
6946
+ else {
6947
+ // Check if we've reached the max selection limit
6948
+ if (maxSelectedOptions && currentSelectedIds.length >= maxSelectedOptions) {
6949
+ // If max=1, replace the selection
6950
+ if (maxSelectedOptions === 1) {
6951
+ newIds = [option.id];
6952
+ }
6953
+ else {
6954
+ // Otherwise, don't add more
6955
+ return;
6956
+ }
6957
+ }
6958
+ else {
6959
+ // Add to selection
6960
+ newIds = [...currentSelectedIds, option.id];
6961
+ }
6962
+ }
6904
6963
  // Update internal state for uncontrolled mode
6905
6964
  if (!controlled) {
6906
6965
  state.internalSelectedIds = newIds;
@@ -6962,21 +7021,32 @@
6962
7021
  : [attrs.checkedId]
6963
7022
  : []
6964
7023
  : state.internalSelectedIds;
6965
- const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
7024
+ const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
6966
7025
  // Use i18n values if provided, otherwise use defaults
6967
7026
  const texts = {
6968
7027
  noOptionsFound: i18n.noOptionsFound || noOptionsFound,
6969
7028
  addNewPrefix: i18n.addNewPrefix || '+',
7029
+ showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
7030
+ maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
6970
7031
  };
7032
+ // Check if max selections is reached
7033
+ const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
7034
+ // Merge provided options with internally created options
7035
+ const allOptions = [...options, ...state.createdOptions];
6971
7036
  // Get selected options for display
6972
- const selectedOptions = options.filter((opt) => selectedIds.includes(opt.id));
7037
+ const selectedOptionsUnsorted = allOptions.filter((opt) => selectedIds.includes(opt.id));
7038
+ const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
6973
7039
  // Safely filter options
6974
- const filteredOptions = options.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
7040
+ const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
6975
7041
  !selectedIds.includes(option.id));
7042
+ // Apply display limit if configured
7043
+ const totalFilteredCount = filteredOptions.length;
7044
+ const displayedOptions = maxDisplayedOptions ? filteredOptions.slice(0, maxDisplayedOptions) : filteredOptions;
7045
+ const isTruncated = maxDisplayedOptions && totalFilteredCount > maxDisplayedOptions;
6976
7046
  // Check if we should show the "add new option" element
6977
7047
  const showAddNew = oncreateNewOption &&
6978
7048
  state.searchTerm &&
6979
- !filteredOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
7049
+ !displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
6980
7050
  // Render the dropdown
6981
7051
  return m('.input-field.multi-select-dropdown', { className }, [
6982
7052
  m('.chips.chips-initial.chips-container', {
@@ -7049,7 +7119,7 @@
7049
7119
  onremove: () => {
7050
7120
  state.dropdownRef = null;
7051
7121
  },
7052
- style: getDropdownStyles(state.inputRef),
7122
+ style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
7053
7123
  }, [
7054
7124
  m('li', // Search Input
7055
7125
  {
@@ -7069,41 +7139,65 @@
7069
7139
  state.focusedIndex = -1; // Reset focus when typing
7070
7140
  },
7071
7141
  onkeydown: async (e) => {
7072
- const result = handleKeyDown(e, filteredOptions, !!showAddNew);
7142
+ const result = handleKeyDown(e, displayedOptions, !!showAddNew);
7073
7143
  if (result === 'addNew' && oncreateNewOption) {
7074
- const option = await oncreateNewOption(state.searchTerm);
7075
- toggleOption(option, attrs);
7144
+ await createAndSelectOption(attrs);
7076
7145
  }
7077
- else if (e.key === 'Enter' &&
7078
- state.focusedIndex >= 0 &&
7079
- state.focusedIndex < filteredOptions.length) {
7080
- toggleOption(filteredOptions[state.focusedIndex], attrs);
7146
+ else if (result === 'selectOption' && state.focusedIndex < displayedOptions.length) {
7147
+ toggleOption(displayedOptions[state.focusedIndex], attrs);
7081
7148
  }
7082
7149
  },
7083
7150
  class: 'search-select-input',
7084
7151
  }),
7085
7152
  ]),
7086
7153
  // No options found message or list of options
7087
- ...(filteredOptions.length === 0 && !showAddNew
7154
+ ...(displayedOptions.length === 0 && !showAddNew
7088
7155
  ? [m('li.search-select-no-options', texts.noOptionsFound)]
7089
7156
  : []),
7157
+ // Truncation message
7158
+ ...(isTruncated
7159
+ ? [
7160
+ m('li.search-select-truncation-info', {
7161
+ style: {
7162
+ fontStyle: 'italic',
7163
+ color: 'var(--mm-text-hint, #9e9e9e)',
7164
+ padding: '8px 16px',
7165
+ cursor: 'default',
7166
+ },
7167
+ }, texts.showingXofY
7168
+ .replace('{shown}', displayedOptions.length.toString())
7169
+ .replace('{total}', totalFilteredCount.toString())),
7170
+ ]
7171
+ : []),
7172
+ // Max selections reached message
7173
+ ...(isMaxSelectionsReached
7174
+ ? [
7175
+ m('li.search-select-max-info', {
7176
+ style: {
7177
+ fontStyle: 'italic',
7178
+ color: 'var(--mm-text-hint, #9e9e9e)',
7179
+ padding: '8px 16px',
7180
+ cursor: 'default',
7181
+ },
7182
+ }, texts.maxSelectionsReached.replace('{max}', maxSelectedOptions.toString())),
7183
+ ]
7184
+ : []),
7090
7185
  // Add new option item
7091
7186
  ...(showAddNew
7092
7187
  ? [
7093
7188
  m('li', {
7094
7189
  onclick: async () => {
7095
- const option = await oncreateNewOption(state.searchTerm);
7096
- toggleOption(option, attrs);
7190
+ await createAndSelectOption(attrs);
7097
7191
  },
7098
- class: state.focusedIndex === filteredOptions.length ? 'active' : '',
7192
+ class: state.focusedIndex === displayedOptions.length ? 'active' : '',
7099
7193
  onmouseover: () => {
7100
- state.focusedIndex = filteredOptions.length;
7194
+ state.focusedIndex = displayedOptions.length;
7101
7195
  },
7102
7196
  }, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
7103
7197
  ]
7104
7198
  : []),
7105
7199
  // List of filtered options
7106
- ...filteredOptions.map((option, index) => m(DropdownOption, {
7200
+ ...displayedOptions.map((option, index) => m(DropdownOption, {
7107
7201
  // key: option.id,
7108
7202
  option,
7109
7203
  index,
@@ -7113,6 +7207,7 @@
7113
7207
  onMouseOver: (idx) => {
7114
7208
  state.focusedIndex = idx;
7115
7209
  },
7210
+ showCheckbox: maxSelectedOptions !== 1,
7116
7211
  })),
7117
7212
  ]),
7118
7213
  ]);
@@ -9812,6 +9907,7 @@
9812
9907
  exports.range = range;
9813
9908
  exports.releasePortalContainer = releasePortalContainer;
9814
9909
  exports.renderToPortal = renderToPortal;
9910
+ exports.sortOptions = sortOptions;
9815
9911
  exports.toast = toast;
9816
9912
  exports.uniqueId = uniqueId;
9817
9913
  exports.uuid4 = uuid4;
@@ -6,6 +6,10 @@ export interface SearchSelectI18n {
6
6
  noOptionsFound?: string;
7
7
  /** Prefix for adding new option */
8
8
  addNewPrefix?: string;
9
+ /** Message template for truncated results. Use {shown} and {total} placeholders */
10
+ showingXofY?: string;
11
+ /** Message shown when max selections reached. Use {max} placeholder */
12
+ maxSelectionsReached?: string;
9
13
  }
10
14
  export interface SearchSelectAttrs<T extends string | number> extends SelectAttrs<T> {
11
15
  /** Callback when user creates a new option: should return new ID */
@@ -14,10 +18,16 @@ export interface SearchSelectAttrs<T extends string | number> extends SelectAttr
14
18
  searchPlaceholder?: string;
15
19
  /** When no options are left, displays this text, default 'No options found' */
16
20
  noOptionsFound?: string;
17
- /** Max height of the dropdown menu, default '25rem' */
21
+ /** Max height of the dropdown menu, default '400px', use 'none' to disable it */
18
22
  maxHeight?: string;
19
23
  /** Internationalization options */
20
24
  i18n?: SearchSelectI18n;
25
+ /** Maximum number of options to display. When set, limits displayed options to improve performance with large datasets */
26
+ maxDisplayedOptions?: number;
27
+ /** Maximum number of options that can be selected. When max=1, checkboxes are hidden and behaves like single select */
28
+ maxSelectedOptions?: number;
29
+ /** Sort selected items: 'asc' (alphabetically A-Z), 'desc' (Z-A), 'none' (insertion order), or custom sort function */
30
+ sortSelected?: 'asc' | 'desc' | 'none' | ((a: InputOption<T>, b: InputOption<T>) => number);
21
31
  }
22
32
  interface SearchSelectState<T extends string | number> {
23
33
  id: string;
@@ -27,6 +37,7 @@ interface SearchSelectState<T extends string | number> {
27
37
  dropdownRef: HTMLElement | null;
28
38
  focusedIndex: number;
29
39
  internalSelectedIds: T[];
40
+ createdOptions: InputOption<T>[];
30
41
  }
31
42
  /**
32
43
  * Mithril Factory Component for Multi-Select Dropdown with search
package/dist/select.d.ts CHANGED
@@ -45,6 +45,10 @@ export interface SelectAttrs<T extends string | number> extends Attributes {
45
45
  required?: boolean;
46
46
  /** Enable the clear icon */
47
47
  showClearButton?: boolean;
48
+ /** Max height of the dropdown menu, default '400px' */
49
+ maxHeight?: string;
50
+ /** Sort selected items: 'asc' (alphabetically A-Z), 'desc' (Z-A), 'none' (insertion order), or custom sort function */
51
+ sortSelected?: 'asc' | 'desc' | 'none' | ((a: InputOption<T>, b: InputOption<T>) => number);
48
52
  }
49
53
  /** Select component */
50
54
  export declare const Select: <T extends string | number>() => Component<SelectAttrs<T>>;
package/dist/utils.d.ts CHANGED
@@ -14,6 +14,25 @@ export declare const uniqueId: () => string;
14
14
  export declare const uuid4: () => string;
15
15
  /** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
16
16
  export declare const isNumeric: (n: string | number) => boolean;
17
+ /**
18
+ * Sort options array based on sorting configuration
19
+ * @param options - Array of options to sort
20
+ * @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
21
+ * @returns Sorted array (or original if 'none' or undefined)
22
+ */
23
+ export declare const sortOptions: <T extends string | number>(options: {
24
+ id: T;
25
+ label?: string;
26
+ }[], sortConfig?: "asc" | "desc" | "none" | ((a: {
27
+ id: T;
28
+ label?: string;
29
+ }, b: {
30
+ id: T;
31
+ label?: string;
32
+ }) => number)) => {
33
+ id: T;
34
+ label?: string;
35
+ }[];
17
36
  /**
18
37
  * Pad left, default width 2 with a '0'
19
38
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-materialized",
3
- "version": "3.5.10",
3
+ "version": "3.6.0",
4
4
  "description": "A materialize library for mithril.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -56,7 +56,6 @@
56
56
  .collapsible-header-text,
57
57
  .collapsible-header-content {
58
58
  flex: 1;
59
- display: flex;
60
59
  align-items: center;
61
60
  gap: 1rem;
62
61
  }
@@ -145,6 +145,10 @@ body.keyboard-focused {
145
145
  }
146
146
 
147
147
  .select-dropdown.dropdown-content {
148
+ // Set a reasonable default max-height to prevent overly tall dropdowns
149
+ max-height: 400px;
150
+ overflow-y: auto;
151
+
148
152
  li {
149
153
  &:hover {
150
154
  background-color: var(--mm-dropdown-hover, variables.$select-option-hover);
@@ -199,7 +203,7 @@ body.keyboard-focused {
199
203
  .dropdown-content li.active {
200
204
  background-color: #444 !important;
201
205
  }
202
-
206
+
203
207
  .chip {
204
208
  background-color: #424242 !important;
205
209
  color: var(--mm-text-secondary) !important;
@@ -214,7 +218,7 @@ body.keyboard-focused {
214
218
  .dropdown-content li.active {
215
219
  background-color: #444 !important;
216
220
  }
217
-
221
+
218
222
  .chip {
219
223
  background-color: #424242 !important;
220
224
  color: var(--mm-text-secondary) !important;
@@ -242,11 +246,11 @@ body.keyboard-focused {
242
246
  border-bottom: 1px solid var(--mm-input-border, #9e9e9e);
243
247
  background-color: transparent;
244
248
  color: var(--mm-text-primary, inherit);
245
-
249
+
246
250
  &:focus {
247
251
  border-bottom-color: var(--mm-primary-color, #2196F3);
248
252
  }
249
-
253
+
250
254
  &::placeholder {
251
255
  color: var(--mm-text-hint, #9e9e9e);
252
256
  }
@@ -259,7 +263,7 @@ body.keyboard-focused {
259
263
  font-style: italic;
260
264
  text-align: center;
261
265
  border-bottom: none;
262
-
266
+
263
267
  &:hover {
264
268
  background-color: transparent;
265
269
  cursor: default;
@@ -276,7 +280,7 @@ body.keyboard-focused {
276
280
  margin: 0;
277
281
  // Ensure it inherits the same padding as regular dropdown items
278
282
  min-height: var(--mm-dropdown-item-height, 50px);
279
-
283
+
280
284
  input[type="checkbox"] {
281
285
  margin-right: 8px;
282
286
  position: relative;
@@ -287,11 +291,11 @@ body.keyboard-focused {
287
291
  border: 2px solid var(--mm-border-color, #9e9e9e);
288
292
  border-radius: 2px;
289
293
  background-color: transparent;
290
-
294
+
291
295
  &:checked {
292
296
  background-color: var(--mm-primary-color, #2196F3);
293
297
  border-color: var(--mm-primary-color, #2196F3);
294
-
298
+
295
299
  &:after {
296
300
  content: '✓';
297
301
  color: white;
@@ -301,13 +305,13 @@ body.keyboard-focused {
301
305
  left: 2px;
302
306
  }
303
307
  }
304
-
308
+
305
309
  &:focus {
306
310
  outline: 2px solid var(--mm-primary-color, #2196F3);
307
311
  outline-offset: 2px;
308
312
  }
309
313
  }
310
-
314
+
311
315
  span {
312
316
  flex: 1;
313
317
  }
@@ -323,32 +327,32 @@ body.keyboard-focused {
323
327
  position: relative;
324
328
  min-height: var(--mm-input-height, 3rem);
325
329
  padding: 4px 0;
326
-
330
+
327
331
  .chip {
328
332
  margin: 2px 4px 2px 0;
329
333
  // Let existing chip system handle background colors for theme compatibility
330
-
334
+
331
335
  .material-icons.close {
332
336
  cursor: pointer;
333
337
  font-size: 16px;
334
338
  margin-left: 4px;
335
-
339
+
336
340
  &:hover {
337
341
  color: var(--mm-error-color, #f44336);
338
342
  }
339
343
  }
340
344
  }
341
-
345
+
342
346
  .placeholder {
343
347
  color: var(--mm-text-hint, #9e9e9e);
344
348
  flex-grow: 1;
345
349
  padding: 8px 0;
346
350
  }
347
-
351
+
348
352
  .spacer {
349
353
  flex-grow: 1;
350
354
  }
351
-
355
+
352
356
  .caret {
353
357
  margin-left: auto;
354
358
  cursor: pointer;