@budibase/bbui 3.23.37 → 3.23.47

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/bbui",
3
3
  "description": "A UI solution used in the different Budibase projects.",
4
- "version": "3.23.37",
4
+ "version": "3.23.47",
5
5
  "license": "MPL-2.0",
6
6
  "svelte": "src/index.ts",
7
7
  "module": "dist/bbui.mjs",
@@ -107,5 +107,5 @@
107
107
  }
108
108
  }
109
109
  },
110
- "gitHead": "6d158cf61da4778f9ce8d78c63da72593791237d"
110
+ "gitHead": "534845c38ed0fe209406ebe45fd87f3de79f1da5"
111
111
  }
@@ -7,7 +7,7 @@
7
7
  import "@spectrum-css/menu/dist/index-vars.css"
8
8
  import "@spectrum-css/picker/dist/index-vars.css"
9
9
  import "@spectrum-css/popover/dist/index-vars.css"
10
- import { createEventDispatcher, onDestroy } from "svelte"
10
+ import { createEventDispatcher, onDestroy, tick } from "svelte"
11
11
  import clickOutside from "../../Actions/clickOutside"
12
12
  import Icon from "../../Icon/Icon.svelte"
13
13
  import Popover from "../../Popover/Popover.svelte"
@@ -74,11 +74,19 @@
74
74
  export let toggleSelectAll: () => void = () => {}
75
75
  export let hideChevron: boolean = false
76
76
 
77
+ const maxHeight = 360
78
+ const VIRTUALIZATION_THRESHOLD = 200
79
+ const VIRTUALIZATION_OVERSCAN = 6
80
+ const OPTION_HEIGHT = 36
81
+
77
82
  const dispatch = createEventDispatcher()
78
83
 
79
84
  let button: HTMLButtonElement | null = null
80
85
  let component: HTMLUListElement | null = null
81
86
  let optionIconDescriptor: ResolvedIcon | null = null
87
+ let virtualizedOptions: Array<{ option: O; idx: number }> = []
88
+ let virtualPaddingTop = 0
89
+ let virtualPaddingBottom = 0
82
90
 
83
91
  const resolveIcon = (icon: PickerIconInput): ResolvedIcon | null => {
84
92
  if (!icon) {
@@ -105,6 +113,22 @@
105
113
  searchTerm,
106
114
  getOptionLabel
107
115
  )
116
+ $: virtualizationEnabled = filteredOptions.length > VIRTUALIZATION_THRESHOLD
117
+ $: {
118
+ if (!virtualizationEnabled) {
119
+ virtualizedOptions = filteredOptions.map((option, idx) => ({
120
+ option,
121
+ idx,
122
+ }))
123
+ virtualPaddingTop = 0
124
+ virtualPaddingBottom = 0
125
+ } else {
126
+ tick().then(updateVirtualSlice)
127
+ }
128
+ }
129
+ $: if (virtualizationEnabled && component) {
130
+ tick().then(updateVirtualSlice)
131
+ }
108
132
 
109
133
  const onClick = (e: MouseEvent) => {
110
134
  e.preventDefault()
@@ -158,9 +182,43 @@
158
182
  }
159
183
  }
160
184
 
161
- $: component?.addEventListener("scroll", onScroll)
185
+ const updateVirtualSlice = () => {
186
+ if (!virtualizationEnabled || !component) {
187
+ return
188
+ }
189
+ const total = filteredOptions.length
190
+ if (!total) {
191
+ virtualizedOptions = []
192
+ virtualPaddingTop = 0
193
+ virtualPaddingBottom = 0
194
+ return
195
+ }
196
+ const scrollTop = component.scrollTop
197
+ const baseStart = Math.floor(scrollTop / OPTION_HEIGHT)
198
+ const startIndex = Math.max(baseStart - VIRTUALIZATION_OVERSCAN, 0)
199
+ const visibleCount =
200
+ Math.ceil(maxHeight / OPTION_HEIGHT) + VIRTUALIZATION_OVERSCAN * 2
201
+ const endIndex = Math.min(startIndex + visibleCount, total)
202
+ virtualPaddingTop = startIndex * OPTION_HEIGHT
203
+ virtualPaddingBottom = Math.max(total - endIndex, 0) * OPTION_HEIGHT
204
+ virtualizedOptions = filteredOptions
205
+ .slice(startIndex, endIndex)
206
+ .map((option, offset) => ({
207
+ option,
208
+ idx: startIndex + offset,
209
+ }))
210
+ }
211
+
212
+ const handleScroll = (event: Event) => {
213
+ const target = event.currentTarget as HTMLElement
214
+ onScroll(event)
215
+ if (virtualizationEnabled && target === component) {
216
+ updateVirtualSlice()
217
+ }
218
+ }
219
+
162
220
  onDestroy(() => {
163
- component?.removeEventListener("scroll", onScroll)
221
+ component = null
164
222
  })
165
223
  </script>
166
224
 
@@ -209,7 +267,7 @@
209
267
  useAnchorWidth={!autoWidth}
210
268
  maxWidth={autoWidth ? 400 : undefined}
211
269
  customHeight={customPopoverHeight}
212
- maxHeight={360}
270
+ {maxHeight}
213
271
  >
214
272
  <div
215
273
  class="popover-content"
@@ -226,7 +284,12 @@
226
284
  placeholder={searchPlaceholder}
227
285
  />
228
286
  {/if}
229
- <ul class="spectrum-Menu" role="listbox" bind:this={component}>
287
+ <ul
288
+ class="spectrum-Menu"
289
+ role="listbox"
290
+ bind:this={component}
291
+ on:scroll={handleScroll}
292
+ >
230
293
  {#if showSelectAll && filteredOptions.length > 0}
231
294
  <li
232
295
  class="spectrum-Menu-item select-all-item"
@@ -269,7 +332,14 @@
269
332
  </li>
270
333
  {/if}
271
334
  {#if filteredOptions.length}
272
- {#each filteredOptions as option, idx (getOptionValue(option, idx) ?? idx)}
335
+ {#if virtualizationEnabled && virtualPaddingTop > 0}
336
+ <li
337
+ class="virtual-spacer"
338
+ aria-hidden="true"
339
+ style={`height:${virtualPaddingTop}px`}
340
+ />
341
+ {/if}
342
+ {#each virtualizedOptions as { option, idx } (getOptionValue(option, idx) ?? idx)}
273
343
  <li
274
344
  class="spectrum-Menu-item"
275
345
  class:is-selected={isOptionSelected(getOptionValue(option, idx))}
@@ -321,6 +391,13 @@
321
391
  </div>
322
392
  </li>
323
393
  {/each}
394
+ {#if virtualizationEnabled && virtualPaddingBottom > 0}
395
+ <li
396
+ class="virtual-spacer"
397
+ aria-hidden="true"
398
+ style={`height:${virtualPaddingBottom}px`}
399
+ />
400
+ {/if}
324
401
  {/if}
325
402
  </ul>
326
403
 
@@ -462,4 +539,10 @@
462
539
  .select-all-item .select-all-check {
463
540
  display: block;
464
541
  }
542
+ .virtual-spacer {
543
+ list-style: none;
544
+ margin: 0;
545
+ padding: 0;
546
+ pointer-events: none;
547
+ }
465
548
  </style>