svelte-tably 1.0.0-next.10 → 1.0.0-next.11

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/README.md CHANGED
@@ -16,7 +16,7 @@ A high performant dynamic table
16
16
  - [x] Virtual elements
17
17
  - [x] sorting
18
18
  - [x] select
19
- - [ ] filtering
19
+ - [x] filtering
20
20
  - [ ] orderable table
21
21
  - [ ] row context-menu
22
22
  - [ ] dropout section
@@ -80,6 +80,12 @@
80
80
  */
81
81
  resizeable?: boolean
82
82
 
83
+ /**
84
+ *
85
+ * @example (value) => value.includes(search)
86
+ */
87
+ filter?: (value: V) => boolean
88
+
83
89
  /**
84
90
  * Optional: Provide the table it is a part of
85
91
  */
@@ -93,6 +99,7 @@
93
99
  statusbar?: C['statusbar']
94
100
 
95
101
  fixed?: boolean
102
+ filter?: C['filter']
96
103
 
97
104
  /** Default options for initial table */
98
105
  defaults: {
@@ -133,6 +140,8 @@
133
140
  value,
134
141
  sort,
135
142
 
143
+ filter,
144
+
136
145
  table
137
146
  }: ColumnProps<T, V> = $props()
138
147
 
@@ -142,6 +151,7 @@
142
151
  row,
143
152
  statusbar,
144
153
  fixed,
154
+ filter,
145
155
  defaults: {
146
156
  sticky,
147
157
  sort: sortby,
@@ -62,6 +62,11 @@ export interface ColumnProps<T, V> {
62
62
  * @default true
63
63
  */
64
64
  resizeable?: boolean;
65
+ /**
66
+ *
67
+ * @example (value) => value.includes(search)
68
+ */
69
+ filter?: (value: V) => boolean;
65
70
  /**
66
71
  * Optional: Provide the table it is a part of
67
72
  */
@@ -73,6 +78,7 @@ export interface ColumnState<T = unknown, V = unknown, C extends ColumnProps<T,
73
78
  row: C['row'];
74
79
  statusbar?: C['statusbar'];
75
80
  fixed?: boolean;
81
+ filter?: C['filter'];
76
82
  /** Default options for initial table */
77
83
  defaults: {
78
84
  sticky?: boolean;
package/dist/Table.svelte CHANGED
@@ -60,6 +60,7 @@
60
60
  import { fly } from 'svelte/transition'
61
61
  import { sineInOut } from 'svelte/easing'
62
62
  import { on } from 'svelte/events'
63
+ import { Trigger } from './trigger.svelte.js'
63
64
 
64
65
  type T = $$Generic<Record<PropertyKey, unknown>>
65
66
 
@@ -89,6 +90,8 @@
89
90
  */
90
91
  resizeable?: boolean
91
92
 
93
+ filters?: ((item: T) => boolean)[]
94
+
92
95
  selected?: T[]
93
96
  select?:
94
97
  | boolean
@@ -151,20 +154,35 @@
151
154
  id = Array.from({ length: 12 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join(''),
152
155
  href,
153
156
  resizeable = true,
154
- select
157
+ select,
158
+ filters: _filters = []
155
159
  }: Props = $props()
156
160
 
157
161
  let mounted = $state(false)
158
162
  onMount(() => (mounted = true))
159
163
 
160
- const data = $derived([..._data])
164
+ let cols: TableState<T>['columns'] = $state({})
165
+
166
+ let sortedData = $state([]) as T[]
167
+ let data = $state([]) as T[]
168
+ $effect(() => {
169
+ const filters = [..._filters] as ((item: T) => boolean)[]
170
+ for(const key in cols) {
171
+ const filter = table.columns[key].filter
172
+ const valueOf = table.columns[key].options.value
173
+ if(filter && valueOf) {
174
+ filters.push((item) => filter(valueOf(item)))
175
+ }
176
+ }
177
+
178
+ data = filters.length === 0 ? [...sortedData] : sortedData.filter((value) => filters.every((filter) => filter(value)))
179
+ })
161
180
 
162
181
  const elements = $state({}) as Record<
163
182
  'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects',
164
183
  HTMLElement
165
184
  >
166
-
167
- let cols: TableState<T>['columns'] = $state({})
185
+
168
186
  let positions: TableState<T>['positions'] = $state({
169
187
  fixed: [],
170
188
  sticky: [],
@@ -226,32 +244,57 @@
226
244
  // #region Virtualization
227
245
  let scrollTop = $state(0)
228
246
  let viewportHeight = $state(0)
229
-
247
+ let topIndex = 0
230
248
  let heightPerItem = $state(8)
249
+ let virtualTop = $state(0)
250
+ let virtualBottom = $state(0)
251
+ /** The area of data being rendered */
252
+ let area = $state([]) as T[]
231
253
 
232
254
  const spacing = () => viewportHeight / 2
233
255
 
234
- let virtualTop = $derived.by(() => {
235
- let result = Math.max(scrollTop - spacing(), 0)
236
- result -= result % heightPerItem
237
- return result
238
- })
239
- let virtualBottom = $derived.by(() => {
240
- let result = heightPerItem * data.length - virtualTop - spacing() * 4
241
- result = Math.max(result, 0)
242
- return result
256
+ let renderItemLength = $derived(Math.ceil(Math.max(30, (viewportHeight / heightPerItem) * 2)))
257
+
258
+ $effect(() => {
259
+ data
260
+ untrack(calculateHeightPerItem)
243
261
  })
244
262
 
245
- let renderItemLength = $derived(Math.ceil(Math.max(30, (viewportHeight / heightPerItem) * 2)))
263
+ let waitAnimationFrame = false
246
264
 
247
- /** The area of data being rendered */
248
- let area = $derived.by(() => {
265
+ $effect(() => {
266
+ scrollTop
267
+ heightPerItem
268
+ data.length
269
+ data
270
+ untrack(() => {
271
+ if(!waitAnimationFrame) {
272
+ setTimeout(() => {
273
+ waitAnimationFrame = false
274
+
275
+ virtualTop = Math.max(scrollTop - spacing(), 0)
276
+ virtualTop -= virtualTop % heightPerItem
277
+
278
+ virtualBottom = heightPerItem * data.length - virtualTop - spacing() * 4
279
+ virtualBottom = Math.max(virtualBottom, 0)
280
+ }, 1000 / 60)
281
+ }
282
+ waitAnimationFrame = true
283
+ })
284
+ })
285
+
286
+ $effect(() => {
249
287
  table.sortReverse
250
288
  table.sortby
251
- const index = virtualTop / heightPerItem || 0
252
- const end = index + renderItemLength
253
- const result = data.slice(index, end)
254
- return result
289
+ heightPerItem
290
+ virtualTop
291
+ data.length
292
+ data
293
+ untrack(() => {
294
+ topIndex = Math.round(virtualTop / heightPerItem || 0)
295
+ const end = topIndex + renderItemLength
296
+ area = data.slice(topIndex, end)
297
+ })
255
298
  })
256
299
 
257
300
  function calculateHeightPerItem() {
@@ -260,22 +303,20 @@
260
303
  return
261
304
  }
262
305
  tick().then(() => {
263
- const firstRow = elements.rows.children[0].getBoundingClientRect().top
306
+ if(elements.rows.children.length === 0) return
307
+ const firstRow = elements.rows.children[0]?.getBoundingClientRect().top
264
308
  const lastRow =
265
309
  elements.rows.children[elements.rows.children.length - 1].getBoundingClientRect().bottom
266
310
  heightPerItem = (lastRow - firstRow) / area.length
267
311
  })
268
312
  }
269
313
 
270
- $effect(() => {
271
- data
272
- untrack(calculateHeightPerItem)
273
- })
314
+
274
315
  // #endregion
275
316
  // * --- Virtualization --- *
276
-
277
317
 
278
-
318
+ // * --- Sorting --- *
319
+ // #region sorting
279
320
  function sortBy(column: string) {
280
321
  const { sorting, value } = table.columns[column]!.options
281
322
  if(!sorting || !value) return
@@ -293,26 +334,34 @@
293
334
  }
294
335
 
295
336
  function sortTable() {
296
- if (!table.sortby) return
337
+ sortedData = [..._data]
338
+ if (!table.sortby) {
339
+ return
340
+ }
297
341
  const column = table.columns[table.sortby]
298
342
  let { sorting, value } = column.options
299
- if(!sorting || !value) return
343
+ if(!sorting || !value) {
344
+ return
345
+ }
300
346
  if(sorting === true) {
301
347
  sorting = (a, b) => String(a).localeCompare(String(b))
302
348
  }
303
349
  if(table.sortReverse) {
304
- data.sort((a, b) => sorting(value(b), value(a)))
350
+ sortedData.sort((a, b) => sorting(value(b), value(a)))
305
351
  } else {
306
- data.sort((a, b) => sorting(value(a), value(b)))
352
+ sortedData.sort((a, b) => sorting(value(a), value(b)))
307
353
  }
308
354
  }
309
355
 
310
356
  $effect.pre(() => {
311
- data
357
+ _data
358
+ _data.length
312
359
  table.sortby
313
360
  table.sortReverse
314
361
  untrack(sortTable)
315
362
  })
363
+ // #endregion
364
+ // * --- Sorting --- *
316
365
 
317
366
  const panelTween = new PanelTween(() => panel, 24)
318
367
 
@@ -523,16 +572,16 @@
523
572
  <tbody class="content" bind:this={elements.rows} onscrollcapture={onscroll} bind:clientHeight={viewportHeight}>
524
573
  {#each area as item, i (item)}
525
574
  {@const props = table.href ? { href: table.href(item) } : {}}
526
- {@const index = data.indexOf(item) + 1}
575
+ {@const index = i + topIndex}
527
576
  <svelte:element
528
577
  this={table.href ? 'a' : 'tr'}
578
+ aria-rowindex="{index+1}"
529
579
  class="row"
530
580
  class:hover={hoveredRow === item}
531
581
  class:selected={table.selected?.includes(item)}
532
582
  class:first={i === 0}
533
583
  class:last={i === area.length - 1}
534
584
  {...props}
535
- aria-rowindex={index}
536
585
  onpointerenter={() => (hoveredRow = item)}
537
586
  onpointerleave={() => (hoveredRow = null)}
538
587
  >
@@ -544,7 +593,7 @@
544
593
  item,
545
594
  {
546
595
  get index() {
547
- return index - 1
596
+ return index
548
597
  },
549
598
  get value() {
550
599
  return col.options.value ? col.options.value(item) : undefined
@@ -556,9 +605,9 @@
556
605
  return table.selected?.includes(item)
557
606
  },
558
607
  set selected(value) {
559
- value ?
560
- table.selected!.push(item)
561
- : table.selected!.splice(table.selected!.indexOf(item), 1)
608
+ value
609
+ ? table.selected!.push(item)
610
+ : table.selected!.splice(table.selected!.indexOf(item), 1)
562
611
  }
563
612
  }
564
613
  ]
@@ -623,7 +672,7 @@
623
672
  <div class="__fixed">
624
673
  {@render headerSnippet({
625
674
  get isSelected() {
626
- return table.data.length === table.selected?.length
675
+ return table.data.length === table.selected?.length && table.data.length > 0
627
676
  },
628
677
  set isSelected(value) {
629
678
  if (value) {
@@ -59,6 +59,7 @@ declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
59
59
  * @default true
60
60
  */
61
61
  resizeable?: boolean;
62
+ filters?: ((item: T) => boolean)[] | undefined;
62
63
  selected?: T[] | undefined;
63
64
  select?: boolean | {
64
65
  /**
@@ -85,13 +86,7 @@ declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
85
86
  events(): {};
86
87
  slots(): {};
87
88
  bindings(): "selected" | "panel";
88
- exports(): {
89
- selected: T[];
90
- positions: TableState<T_1>["positions"];
91
- data: T[];
92
- href: ((item: T) => string) | undefined;
93
- columns: Record<string, ColumnState<T, unknown, ColumnProps<T, unknown>>>;
94
- };
89
+ exports(): {};
95
90
  }
96
91
  interface $$IsomorphicComponent {
97
92
  new <T extends Record<PropertyKey, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-tably",
3
- "version": "1.0.0-next.10",
3
+ "version": "1.0.0-next.11",
4
4
  "repository": "github:refzlund/svelte-tably",
5
5
  "homepage": "https://github.com/Refzlund/svelte-tably",
6
6
  "bugs": {