@placeholderco/placeholder-ui 2.1.0 → 2.2.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.
Files changed (80) hide show
  1. package/dist/editors/CodeMirror.svelte +295 -297
  2. package/dist/editors/tiptap/LinkActionMenu.svelte +6 -1
  3. package/dist/editors/tiptap/extensions/Alert.js +17 -17
  4. package/dist/editors/tiptap/extensions/Div.js +19 -19
  5. package/dist/editors/tiptap/extensions/Span.js +14 -14
  6. package/dist/editors/tiptap/toolbar/AlertButton.svelte +254 -253
  7. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +11 -11
  8. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +11 -11
  9. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +11 -11
  10. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +11 -11
  11. package/dist/editors/tiptap/toolbar/BoldButton.svelte +11 -11
  12. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +11 -11
  13. package/dist/editors/tiptap/toolbar/CodeButton.svelte +11 -11
  14. package/dist/editors/tiptap/toolbar/ColorButton.svelte +265 -265
  15. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +51 -51
  16. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +21 -21
  17. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +48 -52
  18. package/dist/editors/tiptap/toolbar/ImageButton.svelte +35 -39
  19. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +11 -11
  20. package/dist/editors/tiptap/toolbar/LinkButton.svelte +249 -249
  21. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +11 -11
  22. package/dist/editors/tiptap/toolbar/RedoButton.svelte +11 -11
  23. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +11 -11
  24. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +11 -11
  25. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +11 -11
  26. package/dist/editors/tiptap/toolbar/TableButton.svelte +231 -224
  27. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +59 -65
  28. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +11 -11
  29. package/dist/editors/tiptap/toolbar/UndoButton.svelte +11 -11
  30. package/dist/form/Autocomplete.svelte +24 -22
  31. package/dist/form/AutocompleteMulti.svelte +24 -22
  32. package/dist/form/Checkbox.svelte +4 -4
  33. package/dist/form/Chips.svelte +15 -7
  34. package/dist/form/ColorPicker.svelte +5 -20
  35. package/dist/form/ComboBox.svelte +4 -4
  36. package/dist/form/ComboBoxItemBuilder.svelte +14 -32
  37. package/dist/form/ComboBoxMulti.svelte +7 -3
  38. package/dist/form/CronBuilder.svelte +32 -110
  39. package/dist/form/DatePicker.svelte +9 -28
  40. package/dist/form/DateRangePicker.svelte +46 -36
  41. package/dist/form/DateTimePicker.svelte +11 -30
  42. package/dist/form/Number.svelte +1 -3
  43. package/dist/form/RadioGroup.svelte +1 -3
  44. package/dist/form/Select.svelte +25 -28
  45. package/dist/form/SelectMulti.svelte +36 -38
  46. package/dist/form/StringArrayBuilder.svelte +1 -3
  47. package/dist/form/TextArea.svelte +5 -6
  48. package/dist/form/Textbox.svelte +10 -4
  49. package/dist/form/TimePicker.svelte +10 -4
  50. package/dist/icon/Icon.svelte +5 -1
  51. package/dist/index.d.ts +147 -146
  52. package/dist/index.js +78 -77
  53. package/dist/layout/CustomNavbar.svelte +1 -1
  54. package/dist/layout/NavbarItemDisplay.svelte +1 -1
  55. package/dist/layout/NavbarItemView.svelte +3 -1
  56. package/dist/layout/Sidenav.svelte +1 -1
  57. package/dist/models/ComboBoxItem.d.ts +4 -4
  58. package/dist/models/NotifyModel.js +0 -1
  59. package/dist/theme.svelte.d.ts +1 -1
  60. package/dist/theme.svelte.js +46 -22
  61. package/dist/ui/Accordion.svelte +1 -3
  62. package/dist/ui/Badge.svelte +4 -5
  63. package/dist/ui/Button.svelte +7 -1
  64. package/dist/ui/ContextMenu.svelte +5 -11
  65. package/dist/ui/Dialog.svelte +16 -3
  66. package/dist/ui/Dialog.svelte.d.ts +7 -1
  67. package/dist/ui/Dropdown.svelte +21 -10
  68. package/dist/ui/MultiSortable.svelte +1 -3
  69. package/dist/ui/Pagination.svelte +2 -2
  70. package/dist/ui/Popover.svelte +16 -37
  71. package/dist/ui/ProgressBar.svelte +1 -4
  72. package/dist/ui/ThemeSwitcher.svelte +2 -1
  73. package/dist/ui/Tooltip.svelte +1 -37
  74. package/dist/util/DateFunctions.js +1 -1
  75. package/dist/util/Floating.d.ts +70 -0
  76. package/dist/util/Floating.js +321 -0
  77. package/dist/util/NavigateTo.js +2 -2
  78. package/dist/util/Transitions.d.ts +1 -1
  79. package/dist/util/Transitions.js +1 -1
  80. package/package.json +25 -25
@@ -1,18 +1,18 @@
1
1
  <script lang="ts">
2
- import type { Editor } from '@tiptap/core'
3
- import ToolbarButton from './ToolbarButton.svelte'
4
- import { iconArrowBackUp } from '../../../icon/index.js'
2
+ import type { Editor } from '@tiptap/core';
3
+ import ToolbarButton from './ToolbarButton.svelte';
4
+ import { iconArrowBackUp } from '../../../icon/index.js';
5
5
 
6
- interface Props {
7
- editor: Editor
8
- }
6
+ interface Props {
7
+ editor: Editor;
8
+ }
9
9
 
10
- let { editor }: Props = $props()
10
+ let { editor }: Props = $props();
11
11
  </script>
12
12
 
13
13
  <ToolbarButton
14
- icon={iconArrowBackUp}
15
- title="Undo"
16
- disabled={!editor.can().undo()}
17
- onclick={() => editor.chain().focus().undo().run()}
14
+ icon={iconArrowBackUp}
15
+ title="Undo"
16
+ disabled={!editor.can().undo()}
17
+ onclick={() => editor.chain().focus().undo().run()}
18
18
  />
@@ -81,6 +81,7 @@
81
81
  <script lang="ts">
82
82
  import ComboBoxMulti from './ComboBoxMulti.svelte';
83
83
  import { clickOutside } from '../util/ClickOutside.js';
84
+ import { floating } from '../util/Floating.js';
84
85
  import Textbox from './Textbox.svelte';
85
86
  import { ComboBoxCore, findItemByValue } from './comboBoxCore.svelte.js';
86
87
  import { tick } from 'svelte';
@@ -258,6 +259,7 @@
258
259
 
259
260
  let textboxElement: HTMLElement | undefined = $state(undefined);
260
261
  let comboBoxEl: ReturnType<typeof ComboBoxMulti> | undefined = $state(undefined);
262
+ let containerElement: HTMLElement | undefined = $state(undefined);
261
263
 
262
264
  function onKeyDown(e: KeyboardEvent) {
263
265
  if (!open) {
@@ -289,7 +291,7 @@
289
291
  }
290
292
  </script>
291
293
 
292
- <div class="autocomplete-container">
294
+ <div class="autocomplete-container" bind:this={containerElement}>
293
295
  <Textbox
294
296
  {name}
295
297
  type="search"
@@ -316,21 +318,27 @@
316
318
  onfocus={onFocus}
317
319
  onkeydown={onKeyDown}
318
320
  />
319
- <div class="autocomplete-panel {open ? '' : 'hidden'}">
320
- <div class="autocomplete-panel-inner" use:clickOutside={closePopover}>
321
- <ComboBoxMulti
322
- bind:this={comboBoxEl}
323
- filterString={value}
324
- values={rawValue ? [rawValue] : []}
325
- {onSelection}
326
- groupedOptions={displayGroups}
327
- {open}
328
- loading={core.searching}
329
- {hideNoResults}
330
- {loadingText}
331
- />
321
+ {#if open}
322
+ <!-- Rendered in the top layer so a scrolling Dialog body cannot clip it -->
323
+ <div
324
+ class="autocomplete-panel"
325
+ use:floating={{ anchor: () => containerElement, placement: 'bottom-start', matchWidth: true }}
326
+ >
327
+ <div class="autocomplete-panel-inner" use:clickOutside={closePopover}>
328
+ <ComboBoxMulti
329
+ bind:this={comboBoxEl}
330
+ filterString={value}
331
+ values={rawValue ? [rawValue] : []}
332
+ {onSelection}
333
+ groupedOptions={displayGroups}
334
+ {open}
335
+ loading={core.searching}
336
+ {hideNoResults}
337
+ {loadingText}
338
+ />
339
+ </div>
332
340
  </div>
333
- </div>
341
+ {/if}
334
342
  </div>
335
343
 
336
344
  <style>
@@ -338,10 +346,8 @@
338
346
  position: relative;
339
347
  }
340
348
 
349
+ /* Position, width and top-layer promotion come from use:floating */
341
350
  .autocomplete-panel {
342
- position: absolute;
343
- width: 100%;
344
- left: 0;
345
351
  z-index: 70;
346
352
  }
347
353
 
@@ -349,8 +355,4 @@
349
355
  position: relative;
350
356
  width: 100%;
351
357
  }
352
-
353
- .hidden {
354
- display: none;
355
- }
356
358
  </style>
@@ -86,6 +86,7 @@
86
86
  import ComboBoxMulti from './ComboBoxMulti.svelte';
87
87
  import FormGroup from './FormGroup.svelte';
88
88
  import { clickOutside } from '../util/ClickOutside.js';
89
+ import { floating } from '../util/Floating.js';
89
90
  import { ComboBoxMultiCore } from './comboBoxCore.svelte.js';
90
91
  import Icon from '../icon/Icon.svelte';
91
92
  import Loader from '../ui/Loader.svelte';
@@ -305,6 +306,7 @@
305
306
 
306
307
  let textboxElement: HTMLInputElement | undefined = $state(undefined);
307
308
  let comboBoxEl: ReturnType<typeof ComboBoxMulti> | undefined = $state(undefined);
309
+ let containerElement: HTMLElement | undefined = $state(undefined);
308
310
 
309
311
  function onKeyDown(e: KeyboardEvent) {
310
312
  if (e.key === 'Backspace' && filterValue.length === 0 && rawValues.length > 0) {
@@ -348,7 +350,7 @@
348
350
  });
349
351
  </script>
350
352
 
351
- <div class="autocomplete-multi-container {containerClass}">
353
+ <div class="autocomplete-multi-container {containerClass}" bind:this={containerElement}>
352
354
  <FormGroup
353
355
  {label}
354
356
  {required}
@@ -414,21 +416,27 @@
414
416
  </div>
415
417
  </FormGroup>
416
418
 
417
- <div class="autocomplete-panel {open ? '' : 'hidden'}">
418
- <div class="autocomplete-panel-inner" use:clickOutside={closePopover}>
419
- <ComboBoxMulti
420
- bind:this={comboBoxEl}
421
- filterString={filterValue}
422
- values={rawValues}
423
- {onSelection}
424
- groupedOptions={displayGroups}
425
- {open}
426
- loading={core.searching}
427
- {hideNoResults}
428
- {loadingText}
429
- />
419
+ {#if open}
420
+ <!-- Rendered in the top layer so a scrolling Dialog body cannot clip it -->
421
+ <div
422
+ class="autocomplete-panel"
423
+ use:floating={{ anchor: () => containerElement, placement: 'bottom-start', matchWidth: true }}
424
+ >
425
+ <div class="autocomplete-panel-inner" use:clickOutside={closePopover}>
426
+ <ComboBoxMulti
427
+ bind:this={comboBoxEl}
428
+ filterString={filterValue}
429
+ values={rawValues}
430
+ {onSelection}
431
+ groupedOptions={displayGroups}
432
+ {open}
433
+ loading={core.searching}
434
+ {hideNoResults}
435
+ {loadingText}
436
+ />
437
+ </div>
430
438
  </div>
431
- </div>
439
+ {/if}
432
440
  </div>
433
441
 
434
442
  <style>
@@ -496,10 +504,8 @@
496
504
  line-height: 0;
497
505
  }
498
506
 
507
+ /* Position, width and top-layer promotion come from use:floating */
499
508
  .autocomplete-panel {
500
- position: absolute;
501
- width: 100%;
502
- left: 0;
503
509
  z-index: 70;
504
510
  }
505
511
 
@@ -507,8 +513,4 @@
507
513
  position: relative;
508
514
  width: 100%;
509
515
  }
510
-
511
- .hidden {
512
- display: none;
513
- }
514
516
  </style>
@@ -63,14 +63,14 @@
63
63
  const autoName = `checkbox-${crypto.randomUUID()}`;
64
64
  const nonce = Math.random().toString(36).substring(2, 15);
65
65
 
66
- let resolvedName = $derived(
67
- (name || label || autoName).replace(/[^a-zA-Z0-9_\-:.]/g, '_')
68
- );
66
+ let resolvedName = $derived((name || label || autoName).replace(/[^a-zA-Z0-9_\-:.]/g, '_'));
69
67
 
70
68
  const id = $derived(inputId ? inputId : label ? `input-${label}` : `input-${nonce}`);
71
69
 
72
70
  let inputEl = $state<HTMLInputElement>();
73
- $effect(() => { if (inputEl) inputEl.indeterminate = indeterminate; });
71
+ $effect(() => {
72
+ if (inputEl) inputEl.indeterminate = indeterminate;
73
+ });
74
74
  </script>
75
75
 
76
76
  <div class="checkbox-container {disabled ? 'disabled' : ''}">
@@ -84,8 +84,6 @@
84
84
  onchange?.(values.length > 0 ? values : undefined);
85
85
  }
86
86
  }
87
-
88
-
89
87
  </script>
90
88
 
91
89
  <div class="radio-container {containerClass}">
@@ -102,15 +100,25 @@
102
100
  {@const hasSelection = multiple ? values.length > 0 : value !== undefined}
103
101
  {@const chipVariant = checked
104
102
  ? (selectedVariant ?? variant)
105
- : (deselectedVariant && (multiple || hasSelection) ? deselectedVariant : variant)}
103
+ : deselectedVariant && (multiple || hasSelection)
104
+ ? deselectedVariant
105
+ : variant}
106
106
  <div class="flex">
107
107
  {#if checked}
108
- <Button class="chip checked" {disabled} onclick={() => handleChange(option)} svg={iconCheck} iconSize={'16px'} variant={chipVariant}
109
- >{option.label}</Button
108
+ <Button
109
+ class="chip checked"
110
+ {disabled}
111
+ onclick={() => handleChange(option)}
112
+ svg={iconCheck}
113
+ iconSize={'16px'}
114
+ variant={chipVariant}>{option.label}</Button
110
115
  >
111
116
  {:else}
112
- <Button class="chip" {disabled} onclick={() => handleChange(option)} variant={chipVariant}
113
- >{option.label}</Button
117
+ <Button
118
+ class="chip"
119
+ {disabled}
120
+ onclick={() => handleChange(option)}
121
+ variant={chipVariant}>{option.label}</Button
114
122
  >
115
123
  {/if}
116
124
  </div>
@@ -205,6 +205,7 @@
205
205
  import FormGroup from './FormGroup.svelte';
206
206
  import ActionIcon from '../ui/ActionIcon.svelte';
207
207
  import { clickOutside } from '../util/ClickOutside.js';
208
+ import { floating } from '../util/Floating.js';
208
209
  import { iconX, iconPalette } from '../icon/index.js';
209
210
 
210
211
  const autoName = `colorpicker-${crypto.randomUUID()}`;
@@ -306,19 +307,10 @@
306
307
 
307
308
  // --- Popup -----------------------------------------------------------------
308
309
  let open = $state(false);
309
- let dropdownPosition: 'above' | 'below' = $state('below');
310
310
  let triggerEl: HTMLElement | undefined = $state();
311
311
 
312
- function checkDropdownPosition() {
313
- if (!triggerEl) return;
314
- const rect = triggerEl.getBoundingClientRect();
315
- const spaceBelow = window.innerHeight - rect.bottom;
316
- dropdownPosition = spaceBelow < 320 && rect.top > 320 ? 'above' : 'below';
317
- }
318
-
319
312
  function toggle() {
320
313
  if (disabled) return;
321
- if (!open) checkDropdownPosition();
322
314
  open = !open;
323
315
  }
324
316
 
@@ -511,11 +503,13 @@
511
503
  </div>
512
504
 
513
505
  {#if open}
506
+ <!-- Rendered in the top layer so a scrolling Dialog body cannot clip it -->
514
507
  <div
515
- class="panel {dropdownPosition}"
508
+ class="panel"
516
509
  role="dialog"
517
510
  aria-label="Colour picker"
518
511
  transition:fade={{ duration: 150 }}
512
+ use:floating={{ anchor: () => triggerEl, placement: 'bottom-start', offset: 4 }}
519
513
  use:clickOutside={onClickOutside}
520
514
  >
521
515
  <div
@@ -773,10 +767,9 @@
773
767
  }
774
768
 
775
769
  /* ---- Panel ----------------------------------------------------------- */
770
+ /* Position and top-layer promotion come from use:floating */
776
771
  .panel {
777
- position: absolute;
778
772
  z-index: 10;
779
- left: 0;
780
773
  width: 16rem;
781
774
  padding: 0.75rem;
782
775
  display: flex;
@@ -789,14 +782,6 @@
789
782
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
790
783
  }
791
784
 
792
- .panel.below {
793
- top: calc(100% + 0.25rem);
794
- }
795
-
796
- .panel.above {
797
- bottom: calc(100% + 0.25rem);
798
- }
799
-
800
785
  .sv-area {
801
786
  position: relative;
802
787
  width: 100%;
@@ -78,9 +78,7 @@
78
78
  {:else}
79
79
  <div class="combobox-panel">
80
80
  {#each options as option, index}
81
- {@const localClasses = [
82
- selectedIndex === index ? 'selected p-2' : 'p-2'
83
- ]}
81
+ {@const localClasses = [selectedIndex === index ? 'selected p-2' : 'p-2']}
84
82
  <button
85
83
  class="combobox-item {localClasses} {option.class}"
86
84
  class:selected={selectedIndex === index}
@@ -150,7 +148,9 @@
150
148
  padding: 0.5rem;
151
149
  }
152
150
 
153
- .error, .loading, .no-results {
151
+ .error,
152
+ .loading,
153
+ .no-results {
154
154
  padding: 0.5rem;
155
155
  }
156
156
 
@@ -84,16 +84,12 @@
84
84
 
85
85
  // Check if a value already exists (excluding a specific index for editing)
86
86
  function valueExists(value: string, excludeIndex: number | undefined = undefined): boolean {
87
- return items.some((item, i) =>
88
- item.value === value && i !== excludeIndex
89
- );
87
+ return items.some((item, i) => item.value === value && i !== excludeIndex);
90
88
  }
91
89
 
92
90
  // Check if a label already exists (excluding a specific index for editing)
93
91
  function labelExists(label: string, excludeIndex: number | undefined = undefined): boolean {
94
- return items.some((item, i) =>
95
- item.label === label && i !== excludeIndex
96
- );
92
+ return items.some((item, i) => item.label === label && i !== excludeIndex);
97
93
  }
98
94
 
99
95
  function removeItem(index: number) {
@@ -103,9 +99,9 @@
103
99
  // Remove from defaults if it was selected and no remaining item shares the value
104
100
  if (
105
101
  defaultValues.includes(removedItem.value) &&
106
- !items.some(item => item.value === removedItem.value)
102
+ !items.some((item) => item.value === removedItem.value)
107
103
  ) {
108
- defaultValues = defaultValues.filter(v => v !== removedItem.value);
104
+ defaultValues = defaultValues.filter((v) => v !== removedItem.value);
109
105
  }
110
106
 
111
107
  notifyChange();
@@ -115,7 +111,7 @@
115
111
  if (checked) {
116
112
  defaultValues = [...defaultValues, value];
117
113
  } else {
118
- defaultValues = defaultValues.filter(v => v !== value);
114
+ defaultValues = defaultValues.filter((v) => v !== value);
119
115
  }
120
116
  notifyChange();
121
117
  }
@@ -132,21 +128,14 @@
132
128
  // If they were in sync, also update the value to maintain exact match (if it wouldn't create a duplicate)
133
129
  if (wasInSync && (allowDuplicateValues || !valueExists(label, index))) {
134
130
  const oldValue = currentItem.value;
135
- items = items.map((item, i) =>
136
- i === index ? { ...item, label, value: label } : item
137
- );
131
+ items = items.map((item, i) => (i === index ? { ...item, label, value: label } : item));
138
132
 
139
133
  // Update defaults if the value was changed and no remaining item shares the old value
140
- if (
141
- defaultValues.includes(oldValue) &&
142
- !items.some(item => item.value === oldValue)
143
- ) {
144
- defaultValues = defaultValues.map(v => v === oldValue ? label : v);
134
+ if (defaultValues.includes(oldValue) && !items.some((item) => item.value === oldValue)) {
135
+ defaultValues = defaultValues.map((v) => (v === oldValue ? label : v));
145
136
  }
146
137
  } else {
147
- items = items.map((item, i) =>
148
- i === index ? { ...item, label } : item
149
- );
138
+ items = items.map((item, i) => (i === index ? { ...item, label } : item));
150
139
  }
151
140
 
152
141
  notifyChange();
@@ -157,16 +146,11 @@
157
146
  if (!allowDuplicateValues && valueExists(newVal, index)) return;
158
147
 
159
148
  const oldValue = items[index].value;
160
- items = items.map((item, i) =>
161
- i === index ? { ...item, value: newVal } : item
162
- );
149
+ items = items.map((item, i) => (i === index ? { ...item, value: newVal } : item));
163
150
 
164
151
  // Update defaults if the value was changed and no remaining item shares the old value
165
- if (
166
- defaultValues.includes(oldValue) &&
167
- !items.some(item => item.value === oldValue)
168
- ) {
169
- defaultValues = defaultValues.map(v => v === oldValue ? newVal : v);
152
+ if (defaultValues.includes(oldValue) && !items.some((item) => item.value === oldValue)) {
153
+ defaultValues = defaultValues.map((v) => (v === oldValue ? newVal : v));
170
154
  }
171
155
 
172
156
  notifyChange();
@@ -186,7 +170,7 @@
186
170
 
187
171
  // Computed output for display/copying
188
172
  let outputItems = $derived(
189
- items.map(item => ({
173
+ items.map((item) => ({
190
174
  ...item,
191
175
  selected: defaultValues.includes(item.value)
192
176
  }))
@@ -194,9 +178,7 @@
194
178
 
195
179
  // Get labels for selected defaults
196
180
  let defaultLabels = $derived(
197
- defaultValues
198
- .map(v => items.find(i => i.value === v)?.label ?? v)
199
- .join(', ')
181
+ defaultValues.map((v) => items.find((i) => i.value === v)?.label ?? v).join(', ')
200
182
  );
201
183
  </script>
202
184
 
@@ -103,7 +103,9 @@
103
103
  <div class="group-label">{group.label}</div>
104
104
  {/if}
105
105
  {#each group.items as option, localIndex}
106
- {@const globalIndex = groupedOptions.slice(0, groupIndex).reduce((acc, g) => acc + g.items.length, 0) + localIndex}
106
+ {@const globalIndex =
107
+ groupedOptions.slice(0, groupIndex).reduce((acc, g) => acc + g.items.length, 0) +
108
+ localIndex}
107
109
  {@const localClasses = [selectedIndex === globalIndex ? 'selected p-2' : 'p-2']}
108
110
  <button
109
111
  class="combobox-item {localClasses.join(' ')} {option.class}"
@@ -141,7 +143,7 @@
141
143
  } */
142
144
 
143
145
  .combobox-multi {
144
- position: absolute;
146
+ position: relative;
145
147
  width: 100%;
146
148
  z-index: 50;
147
149
  border-radius: 0.25rem;
@@ -154,7 +156,9 @@
154
156
 
155
157
  .combobox-multi-panel {
156
158
  overflow: auto;
157
- max-height: 20rem;
159
+ /* --ui-floating-max-height is set by the floating action to the room left
160
+ on the chosen side, so long lists shrink rather than run off-screen */
161
+ max-height: min(20rem, var(--ui-floating-max-height, 20rem));
158
162
  border: 1px solid var(--border-color);
159
163
  border-top: none;
160
164
  /* max-h-80 overflow-auto */