svelte-tably 1.0.0-next.18 → 1.0.0-next.20

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.
@@ -1,6 +1,6 @@
1
1
  import {} from 'svelte';
2
2
  import { TableState } from '../table/table.svelte.js';
3
- import { assign, pick } from '../utility.svelte.js';
3
+ import {} from '../utility.svelte.js';
4
4
  import { getDefaultHeader } from './Column.svelte';
5
5
  export class ColumnState {
6
6
  #props = {};
@@ -13,7 +13,7 @@ export class ColumnState {
13
13
  header: typeof this.#props.header === 'string' ? getDefaultHeader(this.#props.header) : this.#props.header,
14
14
  /** Title is the header-snippet, with header-ctx: `{ header: false }` */
15
15
  title: (...args) => {
16
- const getData = () => this.table.data.current;
16
+ const getData = () => this.table.dataState.current;
17
17
  return this.snippets.header?.(...[args[0], () => ({
18
18
  get header() { return false; },
19
19
  get data() {
@@ -43,7 +43,9 @@ export class ColumnState {
43
43
  resizeable: this.#props.resizeable ?? true,
44
44
  style: this.#props.style,
45
45
  class: this.#props.class,
46
- onclick: this.#props.onclick
46
+ onclick: this.#props.onclick,
47
+ padRow: this.#props.pad === 'row' || this.#props.pad === 'both',
48
+ padHeader: this.#props.pad === 'header' || this.#props.pad === 'both'
47
49
  });
48
50
  toggleVisiblity() {
49
51
  const index = this.table.positions.hidden.indexOf(this);
@@ -9,7 +9,6 @@
9
9
  -->
10
10
 
11
11
  <script module lang="ts">
12
-
13
12
  </script>
14
13
 
15
14
  <script lang="ts">
@@ -18,7 +17,13 @@
18
17
  import { sineInOut } from 'svelte/easing'
19
18
  import reorder, { type ItemState } from 'runic-reorder'
20
19
  import { Virtualization } from './virtualization.svelte.js'
21
- import { TableState, type HeaderSelectCtx, type RowCtx, type RowSelectCtx, type TableProps } from './table.svelte.js'
20
+ import {
21
+ TableState,
22
+ type HeaderSelectCtx,
23
+ type RowCtx,
24
+ type RowSelectCtx,
25
+ type TableProps
26
+ } from './table.svelte.js'
22
27
  import Panel from '../panel/Panel.svelte'
23
28
  import Column from '../column/Column.svelte'
24
29
  import { assignDescriptors, capitalize, fromProps, mounted, segmentize } from '../utility.svelte.js'
@@ -28,12 +33,13 @@
28
33
  import { SizeTween } from '../size-tween.svelte.js'
29
34
  import { on } from 'svelte/events'
30
35
  import Row from '../row/Row.svelte'
31
-
32
36
 
33
37
  type T = $$Generic<Record<PropertyKey, unknown>>
34
38
 
35
- type ConstructorReturnType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer K ? K : never
36
- type ConstructorParams<T extends new (...args: any[]) => any> = T extends new (...args: infer K) => any ? K : never
39
+ type ConstructorReturnType<T extends new (...args: any[]) => any> =
40
+ T extends new (...args: any[]) => infer K ? K : never
41
+ type ConstructorParams<T extends new (...args: any[]) => any> =
42
+ T extends new (...args: infer K) => any ? K : never
37
43
 
38
44
  type ContentCtx<T extends Record<PropertyKey, unknown>> = {
39
45
  Column: {
@@ -44,7 +50,6 @@
44
50
  Expandable: typeof Expandable<T>
45
51
  Row: typeof Row<T>
46
52
  readonly table: TableState<T>
47
- readonly data: T[]
48
53
  }
49
54
 
50
55
  type ContentSnippet = Snippet<[context: ContentCtx<T>]>
@@ -58,9 +63,9 @@
58
63
  }: TableProps<T> & { content?: ContentSnippet } = $props()
59
64
 
60
65
  const properties = fromProps(restProps, {
61
- selected: [() => _selected, v => _selected = v],
62
- panel: [() => _panel, v => _panel = v],
63
- data: [() => _data, v => _data = v]
66
+ selected: [() => _selected, (v) => (_selected = v)],
67
+ panel: [() => _panel, (v) => (_panel = v)],
68
+ data: [() => _data, (v) => (_data = v)]
64
69
  }) as TableProps<T>
65
70
 
66
71
  const mount = mounted()
@@ -71,15 +76,15 @@
71
76
  'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects',
72
77
  HTMLElement
73
78
  >
74
-
79
+
75
80
  const table = new TableState<T>(properties) as TableState<T>
76
- const data = table.data
77
-
81
+
78
82
  const virtualization = new Virtualization(table)
79
-
83
+
80
84
  const panelTween = new SizeTween(() => !!properties.panel)
81
85
 
82
86
  let hoveredRow: T | null = $state(null)
87
+ let hoveredColumn: ColumnState | null = $state(null)
83
88
 
84
89
  /** Order of columns */
85
90
  const fixed = $derived(table.positions.fixed)
@@ -99,15 +104,16 @@
99
104
  const style = $derived.by(() => {
100
105
  if (!mount.isMounted) return ''
101
106
 
102
- const context = table.row?.snippets.context ? table.row?.options.context.width : ''
107
+ const context = table.row?.snippets.context ? table.row?.options.context.width : ''
103
108
 
104
- const templateColumns = columns
105
- .map((column, i, arr) => {
106
- const width = getWidth(column.id)
107
- if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
108
- return `${width}px`
109
- })
110
- .join(' ') + context
109
+ const templateColumns =
110
+ columns
111
+ .map((column, i, arr) => {
112
+ const width = getWidth(column.id)
113
+ if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
114
+ return `${width}px`
115
+ })
116
+ .join(' ') + context
111
117
 
112
118
  const theadTempla3teColumns = `
113
119
  #${table.id} > thead > tr,
@@ -136,11 +142,17 @@
136
142
  })
137
143
  .join('')
138
144
 
139
- const columnStyling = columns.map(column => !column.options.style ? '' : `
145
+ const columnStyling = columns
146
+ .map((column) =>
147
+ !column.options.style ?
148
+ ''
149
+ : `
140
150
  [data-area-class='${table.id}'] .column[data-column='${column.id}'] {
141
151
  ${column.options.style}
142
152
  }
143
- `).join('')
153
+ `
154
+ )
155
+ .join('')
144
156
 
145
157
  return theadTempla3teColumns + tbodyTemplateColumns + stickyLeft + columnStyling
146
158
  })
@@ -155,15 +167,19 @@
155
167
 
156
168
  const observer = new MutationObserver(() => {
157
169
  const width = parseFloat(node.style.width)
158
- if(width === columnWidths[key]) return
170
+ if (width === columnWidths[key]) return
159
171
  columnWidths[key] = width
160
- if(!mouseup) {
172
+ if (!mouseup) {
161
173
  mouseup = true
162
- window.addEventListener('click', (e) => {
163
- e.preventDefault()
164
- e.stopPropagation()
165
- mouseup = false
166
- }, { once: true, capture: true })
174
+ window.addEventListener(
175
+ 'click',
176
+ (e) => {
177
+ e.preventDefault()
178
+ e.stopPropagation()
179
+ mouseup = false
180
+ },
181
+ { once: true, capture: true }
182
+ )
167
183
  }
168
184
  })
169
185
 
@@ -189,7 +205,6 @@
189
205
  elements.statusbar.scrollLeft = target.scrollLeft
190
206
  }
191
207
 
192
-
193
208
  // * --- CSV --- *
194
209
  let csv = $state(false) as false | { selected?: boolean }
195
210
  let csvElement = $state() as undefined | HTMLTableElement
@@ -202,11 +217,11 @@
202
217
  export async function toCSV(opts: CSVOptions = {}) {
203
218
  csv = { selected: !!opts.selected }
204
219
  let resolve: (value: HTMLTableElement) => void
205
- const promise = new Promise<HTMLTableElement>(r => resolve = r)
220
+ const promise = new Promise<HTMLTableElement>((r) => (resolve = r))
206
221
 
207
222
  const clean = $effect.root(() => {
208
223
  $effect(() => {
209
- if(csvElement) {
224
+ if (csvElement) {
210
225
  resolve(csvElement)
211
226
  }
212
227
  })
@@ -215,86 +230,80 @@
215
230
  let table = await promise
216
231
  clean()
217
232
 
218
- const separator = opts.semicolon ? ";" : ","
233
+ const separator = opts.semicolon ? ';' : ','
219
234
  const rows = Array.from(table.rows)
220
235
  const csvRows = []
221
236
 
222
237
  for (const row of rows) {
223
238
  const cells = Array.from(row.cells)
224
- const csvCells = cells.map(cell => {
239
+ const csvCells = cells.map((cell) => {
225
240
  let text = cell.textContent?.trim() || ''
226
241
 
227
242
  // Escape double quotes and wrap in quotes if needed
228
- if(text.includes('"')) {
243
+ if (text.includes('"')) {
229
244
  text = text.replace(/"/g, '""')
230
245
  }
231
- if(text.includes(separator) || text.includes('"') || text.includes('\n')) {
246
+ if (text.includes(separator) || text.includes('"') || text.includes('\n')) {
232
247
  text = `"${text}"`
233
248
  }
234
-
249
+
235
250
  return text
236
251
  })
237
252
  csvRows.push(csvCells.join(separator))
238
253
  }
239
254
 
240
255
  csv = false
241
- return csvRows.join("\n")
256
+ return csvRows.join('\n')
242
257
  }
243
258
  // * --- CSV --- *
244
259
 
245
-
246
260
  let expandedRow = $state([]) as T[]
247
261
  let expandTick = false
248
262
  function toggleExpand(item: T, value?: boolean) {
249
- if(expandTick) return
263
+ if (expandTick) return
250
264
  expandTick = true
251
- requestAnimationFrame(() => expandTick = false)
265
+ requestAnimationFrame(() => (expandTick = false))
252
266
 
253
267
  let indexOf = expandedRow.indexOf(item)
254
- if(value === undefined) {
268
+ if (value === undefined) {
255
269
  value = indexOf === -1
256
270
  }
257
- if(!value) {
271
+ if (!value) {
258
272
  expandedRow.splice(indexOf, 1)
259
273
  return
260
274
  }
261
- if(table.expandable?.options.multiple === true) {
275
+ if (table.expandable?.options.multiple === true) {
262
276
  expandedRow.push(item)
263
- }
264
- else {
277
+ } else {
265
278
  expandedRow[0] = item
266
279
  }
267
280
  }
268
281
 
269
282
  function addRowColumnEvents(
270
- node: HTMLTableColElement,
283
+ node: HTMLTableColElement,
271
284
  opts: ['header' | 'row' | 'statusbar', ColumnState, () => RowColumnCtx<T, any>]
272
285
  ) {
273
286
  const [where, column, value] = opts
274
- if(where !== 'row') return
275
- if(column.options.onclick) {
276
- $effect(() => on(node, 'click', e => column.options.onclick!(e, value())))
287
+ if (where !== 'row') return
288
+ if (column.options.onclick) {
289
+ $effect(() => on(node, 'click', (e) => column.options.onclick!(e, value())))
277
290
  }
278
291
  }
279
292
 
280
- function addRowEvents(
281
- node: HTMLTableRowElement,
282
- ctx: RowCtx<T>
283
- ) {
284
- if(table.row?.events.onclick) {
285
- $effect(() => on(node, 'click', e => table.row?.events.onclick!(e, ctx)))
293
+ function addRowEvents(node: HTMLTableRowElement, ctx: RowCtx<T>) {
294
+ if (table.row?.events.onclick) {
295
+ $effect(() => on(node, 'click', (e) => table.row?.events.onclick!(e, ctx)))
286
296
  }
287
- if(table.row?.events.oncontextmenu) {
288
- $effect(() => on(node, 'contextmenu', e => table.row?.events.oncontextmenu!(e, ctx)))
297
+ if (table.row?.events.oncontextmenu) {
298
+ $effect(() => on(node, 'contextmenu', (e) => table.row?.events.oncontextmenu!(e, ctx)))
289
299
  }
290
300
  }
291
-
292
301
  </script>
293
302
 
294
303
  <!---------------------------------------------------->
295
304
 
296
305
  {#if csv !== false}
297
- {@const renderedColumns = columns.filter(v => v.id !== '__fixed')}
306
+ {@const renderedColumns = columns.filter((v) => v.id !== '__fixed')}
298
307
  <table bind:this={csvElement} hidden>
299
308
  <thead>
300
309
  <tr>
@@ -304,7 +313,7 @@
304
313
  </tr>
305
314
  </thead>
306
315
  <tbody>
307
- {#each data.current as row, i}
316
+ {#each table.data as row, i}
308
317
  {#if (csv.selected && table.selected.includes(row)) || !csv.selected}
309
318
  <tr>
310
319
  {#each renderedColumns as column}
@@ -313,10 +322,15 @@
313
322
  {@render column.snippets.row(row, {
314
323
  index: i,
315
324
  value: column.options.value?.(row),
316
- isHovered: false,
317
- itemState: { index: i, dragging: false, positioning: false } as ItemState<any>,
325
+ columnHovered: false,
326
+ rowHovered: false,
327
+ itemState: {
328
+ index: i,
329
+ dragging: false,
330
+ positioning: false
331
+ } as ItemState<any>,
318
332
  selected: false,
319
- expanded: false
333
+ expanded: false,
320
334
  })}
321
335
  {:else}
322
336
  {column.options.value?.(row)}
@@ -350,7 +364,7 @@
350
364
  {/snippet}
351
365
 
352
366
  {#snippet dragSnippet()}
353
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style='opacity: .3'>
367
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style="opacity: .3">
354
368
  <path
355
369
  fill="currentColor"
356
370
  d="M5.5 5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m0 4.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m1.5 3a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0M10.5 5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3M12 8a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0m-1.5 6a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3"
@@ -376,14 +390,17 @@
376
390
  class:fixed={true}
377
391
  use:addRowColumnEvents={[where, column, () => args[1]]}
378
392
  data-column={column.id}
393
+ class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
379
394
  class:header={isHeader}
380
395
  class:sortable
381
- use:conditional={[isHeader, (node) => data.sortAction(node, column.id)]}
396
+ use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
397
+ onpointerenter={() => (hoveredColumn = column)}
398
+ onpointerleave={() => (hoveredColumn = null)}
382
399
  >
383
400
  {@render renderable(column)?.(args[0], args[1])}
384
- {#if isHeader && data.sortby === column.id && sortable}
401
+ {#if isHeader && table.dataState.sortby === column.id && sortable}
385
402
  <span class='sorting-icon'>
386
- {@render chevronSnippet(data.sortReverse ? 0 : 180)}
403
+ {@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
387
404
  </span>
388
405
  {/if}
389
406
  </svelte:element>
@@ -401,16 +418,19 @@
401
418
  use:addRowColumnEvents={[where, column, () => args[1]]}
402
419
  use:observeColumnWidth={isHeader}
403
420
  data-column={column.id}
421
+ class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
404
422
  class:header={isHeader}
405
423
  class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
406
424
  class:border={i == sticky.length - 1}
407
425
  class:sortable
408
- use:conditional={[isHeader, (node) => data.sortAction(node, column.id)]}
426
+ use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
427
+ onpointerenter={() => (hoveredColumn = column)}
428
+ onpointerleave={() => (hoveredColumn = null)}
409
429
  >
410
430
  {@render renderable(column)?.(args[0], args[1])}
411
- {#if isHeader && data.sortby === column.id && sortable}
431
+ {#if isHeader && table.dataState.sortby === column.id && sortable}
412
432
  <span class='sorting-icon'>
413
- {@render chevronSnippet(data.sortReverse ? 0 : 180)}
433
+ {@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
414
434
  </span>
415
435
  {/if}
416
436
  </svelte:element>
@@ -425,16 +445,19 @@
425
445
  class={column.options.class ?? ''}
426
446
  class:column={true}
427
447
  data-column={column.id}
448
+ class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
428
449
  use:addRowColumnEvents={[where, column, () => args[1]]}
429
450
  use:observeColumnWidth={isHeader}
430
451
  class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
431
452
  class:sortable
432
- use:conditional={[isHeader, (node) => data.sortAction(node, column.id)]}
453
+ use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
454
+ onpointerenter={() => (hoveredColumn = column)}
455
+ onpointerleave={() => (hoveredColumn = null)}
433
456
  >
434
457
  {@render renderable(column)?.(args[0], args[1])}
435
- {#if isHeader && data.sortby === column.id && sortable}
458
+ {#if isHeader && table.dataState.sortby === column.id && sortable}
436
459
  <span class='sorting-icon'>
437
- {@render chevronSnippet(data.sortReverse ? 0 : 180)}
460
+ {@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
438
461
  </span>
439
462
  {/if}
440
463
  </svelte:element>
@@ -453,7 +476,7 @@
453
476
  get index() {
454
477
  return index
455
478
  },
456
- get isHovered() {
479
+ get rowHovered() {
457
480
  return hoveredRow === item
458
481
  },
459
482
  get selected() {
@@ -478,20 +501,19 @@
478
501
  <tr
479
502
  aria-rowindex={index + 1}
480
503
  style:opacity={itemState?.positioning ? 0 : 1}
481
- class='row'
504
+ class="row"
482
505
  class:dragging={itemState?.dragging}
483
506
  class:selected={table.selected?.includes(item)}
484
507
  class:first={index === 0}
485
508
  class:last={index === virtualization.area.length - 1}
486
- {...(table.options.href ? { href: table.options.href(item) } : {})}
487
- {...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
509
+ {...itemState?.dragging ? { 'data-svelte-tably': table.id } : {}}
488
510
  onpointerenter={() => (hoveredRow = item)}
489
511
  onpointerleave={() => (hoveredRow = null)}
490
512
  use:addRowEvents={ctx}
491
513
  onclick={(e) => {
492
514
  if (table.expandable?.options.click === true) {
493
515
  let target = e.target as HTMLElement
494
- if(['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
516
+ if (['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
495
517
  return
496
518
  }
497
519
  ctx.expanded = !ctx.expanded
@@ -503,11 +525,17 @@
503
525
  (column) => {
504
526
  return [
505
527
  item,
506
- assignDescriptors({
507
- get value() {
508
- return column.options.value ? column.options.value(item) : undefined
509
- }
510
- }, ctx)
528
+ assignDescriptors(
529
+ {
530
+ get value() {
531
+ return column.options.value ? column.options.value(item) : undefined
532
+ },
533
+ get columnHovered() {
534
+ return hoveredColumn === column
535
+ }
536
+ },
537
+ ctx
538
+ )
511
539
  ]
512
540
  },
513
541
  'row'
@@ -515,9 +543,11 @@
515
543
  {#if table.row?.snippets.context}
516
544
  {#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
517
545
  <td
518
- class='context-col'
546
+ class="context-col"
519
547
  class:hover={!table.row?.snippets.contextHeader && table.row?.options.context.hover}
520
- class:hidden={table.row?.options.context.hover && table.row?.snippets.contextHeader && hoveredRow !== item}
548
+ class:hidden={table.row?.options.context.hover &&
549
+ table.row?.snippets.contextHeader &&
550
+ hoveredRow !== item}
521
551
  >
522
552
  {@render table.row?.snippets.context?.(item, ctx)}
523
553
  </td>
@@ -525,19 +555,15 @@
525
555
  {/if}
526
556
  </tr>
527
557
 
528
- {@const expandableTween = new SizeTween(
529
- () => table.expandable && expandedRow.includes(item),
530
- { min: 1, duration: table.expandable?.options.slide.duration, easing: table.expandable?.options.slide.easing })}
558
+ {@const expandableTween = new SizeTween(() => table.expandable && expandedRow.includes(item), {
559
+ min: 1,
560
+ duration: table.expandable?.options.slide.duration,
561
+ easing: table.expandable?.options.slide.easing
562
+ })}
531
563
  {#if expandableTween.current > 0}
532
- <tr class='expandable' style='height: {expandableTween.current}px'>
533
- <td
534
- colspan={columns.length}
535
- style='height: {expandableTween.current}px'
536
- >
537
- <div
538
- bind:offsetHeight={expandableTween.size}
539
- style='width: {tbody.width - 3}px'
540
- >
564
+ <tr class="expandable" style="height: {expandableTween.current}px">
565
+ <td colspan={columns.length} style="height: {expandableTween.current}px">
566
+ <div bind:offsetHeight={expandableTween.size} style="width: {tbody.width - 3}px">
541
567
  {@render table.expandable!.snippets.content?.(item, ctx)}
542
568
  </div>
543
569
  </td>
@@ -547,35 +573,39 @@
547
573
 
548
574
  <table
549
575
  id={table.id}
550
- class='table svelte-tably'
551
- style='--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px;'
552
- aria-rowcount={data.current.length}
576
+ class="table svelte-tably"
577
+ style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px;"
578
+ aria-rowcount={table.data.length}
553
579
  >
554
- {#if columns.some(v => v.snippets.header)}
555
- <thead class='headers' bind:this={elements.headers}>
556
- <tr style='min-width: {tbody.width}px'>
580
+ {#if columns.some((v) => v.snippets.header)}
581
+ <thead class="headers" bind:this={elements.headers}>
582
+ <tr style="min-width: {tbody.width}px">
557
583
  {@render columnsSnippet(
558
584
  (column) => column.snippets.header,
559
- () => [{
560
- get header() { return true },
561
- get data() { return data.current }
562
- }],
585
+ () => [
586
+ {
587
+ get header() {
588
+ return true
589
+ },
590
+ get data() {
591
+ return table.data
592
+ }
593
+ }
594
+ ],
563
595
  'header'
564
596
  )}
565
597
  {#if table.row?.snippets.contextHeader}
566
- <th
567
- class='context-col'
568
- >
598
+ <th class="context-col">
569
599
  {@render table.row?.snippets.contextHeader()}
570
600
  </th>
571
601
  {/if}
572
602
  </tr>
573
- <tr style='width:400px;background:none;pointer-events:none;'></tr>
603
+ <tr style="width:400px;background:none;pointer-events:none;"></tr>
574
604
  </thead>
575
605
  {/if}
576
606
 
577
607
  <tbody
578
- class='content'
608
+ class="content"
579
609
  use:reorderArea={{ axis: 'y', class: table.id }}
580
610
  bind:this={virtualization.viewport.element}
581
611
  onscrollcapture={onscroll}
@@ -588,7 +618,7 @@
588
618
  return virtualization.area
589
619
  },
590
620
  get modify() {
591
- return data.origin
621
+ return table.dataState.origin
592
622
  },
593
623
  get startIndex() {
594
624
  return virtualization.topIndex
@@ -601,28 +631,29 @@
601
631
  {/if}
602
632
  </tbody>
603
633
 
604
- {#if columns.some(v => v.snippets.statusbar)}
605
- <tfoot class='statusbar' bind:this={elements.statusbar}>
634
+ {#if columns.some((v) => v.snippets.statusbar)}
635
+ <tfoot class="statusbar" bind:this={elements.statusbar}>
606
636
  <tr>
607
637
  {@render columnsSnippet(
608
638
  (column) => column.snippets.statusbar,
609
- () => [{
610
- get data() { return data.current }
611
- }],
639
+ () => [
640
+ {
641
+ get data() {
642
+ return table.data
643
+ }
644
+ }
645
+ ],
612
646
  'statusbar'
613
647
  )}
614
648
  </tr>
615
- <tr style='width:400px;background:none;pointer-events:none;'></tr>
649
+ <tr style="width:400px;background:none;pointer-events:none;"></tr>
616
650
  </tfoot>
617
651
  {/if}
618
652
 
619
- <caption
620
- class='panel'
621
- style='width: {panelTween.current}px;'
622
- >
653
+ <caption class="panel" style="width: {panelTween.current}px;">
623
654
  {#if properties.panel && properties.panel in table.panels}
624
655
  <div
625
- class='panel-content'
656
+ class="panel-content"
626
657
  bind:offsetWidth={panelTween.size}
627
658
  in:fly={{ x: 100, easing: sineInOut, duration: 300 }}
628
659
  out:fly={{ x: 100, duration: 200, easing: sineInOut }}
@@ -632,24 +663,31 @@
632
663
  return table
633
664
  },
634
665
  get data() {
635
- return data.current
666
+ return table.data
636
667
  }
637
668
  })}
638
669
  </div>
639
670
  {/if}
640
671
  </caption>
641
- <caption class='backdrop' aria-hidden={properties.panel && table.panels[properties.panel]?.backdrop ? false : true}>
642
- <button aria-label='Panel backdrop' class='btn-backdrop' tabindex='-1' onclick={() => (properties.panel = undefined)}
672
+ <caption
673
+ class="backdrop"
674
+ aria-hidden={properties.panel && table.panels[properties.panel]?.backdrop ? false : true}
675
+ >
676
+ <button
677
+ aria-label="Panel backdrop"
678
+ class="btn-backdrop"
679
+ tabindex="-1"
680
+ onclick={() => (properties.panel = undefined)}
643
681
  ></button>
644
682
  </caption>
645
683
  </table>
646
684
 
647
685
  {#snippet headerSelected(ctx: HeaderSelectCtx<T>)}
648
- <input type='checkbox' indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
686
+ <input type="checkbox" indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
649
687
  {/snippet}
650
688
 
651
689
  {#snippet rowSelected(ctx: RowSelectCtx<T>)}
652
- <input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
690
+ <input type="checkbox" bind:checked={ctx.isSelected} tabindex="-1" />
653
691
  {/snippet}
654
692
 
655
693
  {#if table.options.select || table.options.reorderable || table.expandable}
@@ -663,29 +701,31 @@
663
701
  } = typeof select === 'boolean' ? {} : select}
664
702
  {#if show !== 'never' || reorderable || expandable?.options.chevron !== 'never'}
665
703
  <Column
666
- id='__fixed'
704
+ id="__fixed"
667
705
  {table}
668
706
  fixed
669
- width={Math.max(48, 0
670
- + (select && show !== 'never' ? 34 : 0)
671
- + (reorderable ? 34 : 0)
672
- + (expandable && expandable?.options.chevron !== 'never' ? 34 : 0)
707
+ width={Math.max(
708
+ 48,
709
+ 0 +
710
+ (select && show !== 'never' ? 34 : 0) +
711
+ (reorderable ? 34 : 0) +
712
+ (expandable && expandable?.options.chevron !== 'never' ? 34 : 0)
673
713
  )}
674
714
  resizeable={false}
675
715
  >
676
716
  {#snippet header()}
677
- <div class='__fixed'>
717
+ <div class="__fixed">
678
718
  {#if reorderable}
679
- <span style='width: 16px; display: flex; align-items: center;'></span>
719
+ <span style="width: 16px; display: flex; align-items: center;"></span>
680
720
  {/if}
681
721
  {#if select}
682
722
  {@render headerSnippet({
683
723
  get isSelected() {
684
- return data.current.length === table.selected?.length && data.current.length > 0
724
+ return table.data.length === table.selected?.length && table.data.length > 0
685
725
  },
686
726
  set isSelected(value) {
687
727
  if (value) {
688
- table.selected = data.current
728
+ table.selected = table.data
689
729
  } else {
690
730
  table.selected = []
691
731
  }
@@ -696,7 +736,7 @@
696
736
  get indeterminate() {
697
737
  return (
698
738
  (table.selected?.length || 0) > 0 &&
699
- data.current.length !== table.selected?.length
739
+ table.data.length !== table.selected?.length
700
740
  )
701
741
  }
702
742
  })}
@@ -704,15 +744,15 @@
704
744
  </div>
705
745
  {/snippet}
706
746
  {#snippet row(item, row)}
707
- <div class='__fixed'>
747
+ <div class="__fixed">
708
748
  {#if reorderable && row.itemState}
709
- <span style='width: 16px; display: flex; align-items: center;' use:row.itemState.handle>
710
- {#if (row.isHovered && !row.itemState.area.isTarget) || row.itemState.dragging}
749
+ <span style="width: 16px; display: flex; align-items: center;" use:row.itemState.handle>
750
+ {#if (row.rowHovered && !row.itemState.area.isTarget) || row.itemState.dragging}
711
751
  {@render dragSnippet()}
712
752
  {/if}
713
753
  </span>
714
754
  {/if}
715
- {#if select && (row.selected || show === 'always' || (row.isHovered && show === 'hover') || row.expanded)}
755
+ {#if select && (row.selected || show === 'always' || (row.rowHovered && show === 'hover') || row.expanded)}
716
756
  {@render rowSnippet({
717
757
  get isSelected() {
718
758
  return row.selected
@@ -727,13 +767,13 @@
727
767
  return item
728
768
  },
729
769
  get data() {
730
- return data.current
770
+ return table.data
731
771
  }
732
772
  })}
733
773
  {/if}
734
774
  {#if expandable && expandable?.options.chevron !== 'never'}
735
- <button class='expand-row' tabindex='-1' onclick={() => row.expanded = !row.expanded}>
736
- {#if row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover')}
775
+ <button class="expand-row" tabindex="-1" onclick={() => (row.expanded = !row.expanded)}>
776
+ {#if row.expanded || expandable.options.chevron === 'always' || (row.rowHovered && expandable.options.chevron === 'hover')}
737
777
  {@render chevronSnippet(row.expanded ? 180 : 90)}
738
778
  {/if}
739
779
  </button>
@@ -745,16 +785,14 @@
745
785
  {/if}
746
786
 
747
787
  {#if table.options.auto}
748
- {#each Object.keys(data.current[0] || {}) as key}
788
+ {#each Object.keys(table.data[0] || {}) as key}
749
789
  <Column
750
790
  id={key}
751
- value={r => r[key]}
791
+ value={(r) => r[key]}
752
792
  header={capitalize(segmentize(key))}
753
- sort={
754
- typeof data.current[0]?.[key] === 'number'
755
- ? (a, b) => a - b
756
- : (a, b) => String(a).localeCompare(String(b))
757
- }
793
+ sort={typeof table.data[0]?.[key] === 'number' ?
794
+ (a, b) => a - b
795
+ : (a, b) => String(a).localeCompare(String(b))}
758
796
  />
759
797
  {/each}
760
798
  {/if}
@@ -766,9 +804,6 @@
766
804
  Row,
767
805
  get table() {
768
806
  return table
769
- },
770
- get data() {
771
- return data.current
772
807
  }
773
808
  })}
774
809
 
@@ -822,10 +857,10 @@
822
857
  position: relative;
823
858
  overflow: visible;
824
859
  }
825
-
860
+
826
861
  .expandable {
827
862
  position: relative;
828
-
863
+
829
864
  & > td {
830
865
  position: sticky;
831
866
  left: 1px;
@@ -1016,7 +1051,8 @@
1016
1051
  border-bottom: 1px solid var(--tably-border);
1017
1052
  }
1018
1053
  .headers > tr {
1019
- > .column, > .context-col {
1054
+ > .column,
1055
+ > .context-col {
1020
1056
  border-bottom: 1px solid var(--tably-border);
1021
1057
  border-left: 1px solid var(--tably-border-grid);
1022
1058
  }
@@ -1053,19 +1089,28 @@
1053
1089
 
1054
1090
  & > .column {
1055
1091
  display: flex;
1056
- padding-left: var(--tably-padding-x);
1057
1092
  overflow: hidden;
1093
+
1094
+ &:not(.pad), &.pad > :global(*:first-child) {
1095
+ padding-left: var(--tably-padding-x);
1096
+ }
1058
1097
  }
1059
1098
 
1060
1099
  & > *:last-child:not(.context-col) {
1061
1100
  width: 100%;
1062
- padding-right: var(--tably-padding-x);
1101
+
1102
+ &:not(.pad), &.pad > :global(*:first-child) {
1103
+ padding-right: var(--tably-padding-x);
1104
+ }
1063
1105
  }
1064
1106
  }
1065
1107
 
1066
1108
  .row > .column {
1067
1109
  background-color: var(--tably-bg);
1068
- padding: var(--tably-padding-y) 0;
1110
+ &:not(.pad), &.pad > :global(*:first-child) {
1111
+ padding-top: var(--tably-padding-y);
1112
+ padding-bottom: var(--tably-padding-y);
1113
+ }
1069
1114
  }
1070
1115
 
1071
1116
  :global(#runic-drag .row) {
@@ -1084,7 +1129,7 @@
1084
1129
  height: 100%;
1085
1130
  overflow: hidden;
1086
1131
  border-left: 1px solid var(--tably-border);
1087
-
1132
+
1088
1133
  z-index: 4;
1089
1134
 
1090
1135
  > .panel-content {
@@ -41,7 +41,7 @@ export class Data {
41
41
  if (sort === true) {
42
42
  sort = (a, b) => String(a).localeCompare(String(b));
43
43
  }
44
- if (this.#table.data.sortReverse) {
44
+ if (this.sortReverse) {
45
45
  this.sorted = this.origin.toSorted((a, b) => sort(value(b), value(a)));
46
46
  }
47
47
  else {
@@ -6,7 +6,8 @@ import { assign, boundAssign, boundPick, pick } from '../utility.svelte.js';
6
6
  export class TableState {
7
7
  #props = {};
8
8
  id = $state();
9
- data;
9
+ dataState = $state({});
10
+ data = $derived(this.dataState.current ?? []);
10
11
  columns = $state({});
11
12
  panels = $state({});
12
13
  expandable = $state();
@@ -27,7 +28,6 @@ export class TableState {
27
28
  filters: this.#props.reorderable ? false : (this.#props.filters ?? []),
28
29
  resizeable: this.#props.resizeable ?? true,
29
30
  reorderable: this.#props.reorderable ?? false,
30
- href: this.#props.href,
31
31
  select: this.#props.select ?? false,
32
32
  auto: this.#props.auto ?? false
33
33
  });
@@ -43,7 +43,7 @@ export class TableState {
43
43
  this.positions.hidden = this.positions.hidden.filter((column) => column !== state);
44
44
  };
45
45
  if (state.defaults.sortby)
46
- this.data.sortBy(key);
46
+ this.dataState.sortBy(key);
47
47
  if (state.options.fixed) {
48
48
  this.positions.fixed.push(state);
49
49
  return clean;
@@ -71,7 +71,7 @@ export class TableState {
71
71
  constructor(tableProps) {
72
72
  this.#props = tableProps;
73
73
  this.id = tableProps.id ?? Array.from({ length: 12 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join('');
74
- this.data = new Data(this, tableProps);
74
+ this.dataState = new Data(this, tableProps);
75
75
  setContext('svelte-tably', this);
76
76
  }
77
77
  }
@@ -19,7 +19,7 @@ export class Virtualization {
19
19
  constructor(table) {
20
20
  let ticked = $state(false);
21
21
  $effect.pre(() => {
22
- table.data.origin;
22
+ table.dataState.origin;
23
23
  untrack(() => {
24
24
  ticked = false;
25
25
  requestAnimationFrame(() => ticked = true);
@@ -28,7 +28,7 @@ export class Virtualization {
28
28
  $effect(() => {
29
29
  if (!ticked)
30
30
  return;
31
- table.data.current;
31
+ table.dataState.current;
32
32
  untrack(() => {
33
33
  if (!this.viewport.element) {
34
34
  this.#heightPerItem = 8;
@@ -50,8 +50,8 @@ export class Virtualization {
50
50
  return;
51
51
  this.scrollTop;
52
52
  this.#heightPerItem;
53
- table.data.current.length;
54
- table.data.current;
53
+ table.dataState.current.length;
54
+ table.dataState.current;
55
55
  untrack(() => {
56
56
  if (!waitAnimationFrame) {
57
57
  setTimeout(() => {
@@ -59,7 +59,7 @@ export class Virtualization {
59
59
  let virtualTop = Math.max(this.scrollTop - this.#spacing, 0);
60
60
  virtualTop -= virtualTop % this.#heightPerItem;
61
61
  this.#virtualTop = virtualTop;
62
- let virtualBottom = this.#heightPerItem * table.data.current.length - virtualTop - this.#spacing * 4;
62
+ let virtualBottom = this.#heightPerItem * table.dataState.current.length - virtualTop - this.#spacing * 4;
63
63
  virtualBottom = Math.max(virtualBottom, 0);
64
64
  this.#virtualBottom = virtualBottom;
65
65
  }, 1000 / 60);
@@ -70,16 +70,16 @@ export class Virtualization {
70
70
  $effect(() => {
71
71
  if (!ticked)
72
72
  return;
73
- table.data.sortReverse;
74
- table.data.sortby;
73
+ table.dataState.sortReverse;
74
+ table.dataState.sortby;
75
75
  this.#heightPerItem;
76
76
  this.#virtualTop;
77
- table.data.current.length;
78
- table.data.current;
77
+ table.dataState.current.length;
78
+ table.dataState.current;
79
79
  untrack(() => {
80
80
  this.#topIndex = Math.round(this.#virtualTop / this.#heightPerItem || 0);
81
81
  const end = this.#topIndex + this.#renderItemLength;
82
- this.#area = table.data.current.slice(this.#topIndex, end);
82
+ this.#area = table.dataState.current.slice(this.#topIndex, end);
83
83
  });
84
84
  });
85
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-tably",
3
- "version": "1.0.0-next.18",
3
+ "version": "1.0.0-next.20",
4
4
  "repository": "github:refzlund/svelte-tably",
5
5
  "homepage": "https://github.com/Refzlund/svelte-tably",
6
6
  "bugs": {