svelte-tably 1.0.0-next.14 → 1.0.0-next.16

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
@@ -18,9 +18,10 @@ A high performant, feature rich, dynamic table
18
18
  - [x] select
19
19
  - [x] filtering
20
20
  - [x] reorderable table
21
- - [ ] row context-menu
21
+ - [x] Row context menus
22
22
  - [x] Expandable rows
23
23
  - [x] to CSV
24
+ - [ ] Auto: Table based on data, sortable
24
25
 
25
26
  ### Usage Notes
26
27
 
@@ -38,8 +39,8 @@ A high performant, feature rich, dynamic table
38
39
  </script>
39
40
 
40
41
  <Table {data} panel={activePanel} select bind:selected>
41
- {#snippet content({ Column, Panel, state, data })}
42
- <Column id='name' sticky>
42
+ {#snippet content({ Column, Panel, Expandable, Row, state, data })}
43
+ <Column id='name' sticky sort>
43
44
  {#snippet header()}
44
45
  Name
45
46
  {/snippet}
@@ -52,9 +53,10 @@ A high performant, feature rich, dynamic table
52
53
  {data.length}
53
54
  {/snippet}
54
55
  </Column>
55
- <Column ...>
56
- ...
57
- </Column>
56
+
57
+ <!-- Simplified -->
58
+ <Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
59
+
58
60
  <!-- If you want to sort/filter a virtual value, that does not exist in the data -->
59
61
  <Column id='virtual' value={row => row.age > 18}>
60
62
  ...
@@ -64,6 +66,21 @@ A high performant, feature rich, dynamic table
64
66
  ...
65
67
  </Column>
66
68
 
69
+ <Expandable click={false}>
70
+ {#snippet content(item, ctx)}
71
+ ...
72
+ {/snippet}
73
+ </Expandable>
74
+
75
+ <Row onclick={...} oncontextmenu={...}>
76
+ {#snippet contextHeader()}
77
+ ...
78
+ {/snippet}
79
+ {#snippet context(item, ctx)}
80
+ ...
81
+ {/snippet}
82
+ </Row>
83
+
67
84
  <Panel id='columns'>
68
85
  <!-- Anything you might like -->
69
86
  </Panel>
@@ -10,8 +10,9 @@
10
10
 
11
11
  <script lang="ts">
12
12
 
13
- import { fromProps } from '../utility.svelte.js'
14
- import { ColumnState, type ColumnProps } from './column.svelte.js'
13
+ import { fromProps, type AnyRecord } from '../utility.svelte.js'
14
+ import type { Snippet } from 'svelte'
15
+ import { ColumnState, type ColumnProps, type HeaderCtx, type ColumnSnippets } from './column.svelte.js'
15
16
 
16
17
  type T = $$Generic<Record<PropertyKey, any>>
17
18
  type V = $$Generic
@@ -21,4 +22,20 @@
21
22
 
22
23
  new ColumnState<T, V>(properties)
23
24
 
24
- </script>
25
+ </script>
26
+
27
+ <script module lang='ts'>
28
+
29
+ declare const defaultHeader: <T extends AnyRecord>(anchor: unknown, title: () => string, ctx: HeaderCtx<T>) => ReturnType<Snippet>
30
+
31
+ export function getDefaultHeader<T extends AnyRecord,V>(title: string) {
32
+ return (
33
+ (anchor: unknown, ctx: HeaderCtx<T>) => defaultHeader<T>(anchor, () => title, ctx)
34
+ ) as Exclude<ColumnSnippets<T, V>['header'], string>
35
+ }
36
+
37
+ </script>
38
+
39
+ {#snippet defaultHeader(title: string, ctx: HeaderCtx<T>)}
40
+ {title}
41
+ {/snippet}
@@ -1,3 +1,6 @@
1
+ export declare function getDefaultHeader<T extends AnyRecord, V>(title: string): Exclude<ColumnSnippets<T, V>["header"], string>;
2
+ import { type AnyRecord } from '../utility.svelte.js';
3
+ import { type ColumnSnippets } from './column.svelte.js';
1
4
  declare class __sveltets_Render<T extends Record<PropertyKey, any>, V> {
2
5
  props(): ColumnProps<T_1, V_1>;
3
6
  events(): {};
@@ -1,6 +1,7 @@
1
1
  import {} from 'svelte';
2
2
  import { TableState } from '../table/table.svelte.js';
3
3
  import { assign, pick } from '../utility.svelte.js';
4
+ import { getDefaultHeader } from './Column.svelte';
4
5
  export class ColumnState {
5
6
  #props = {};
6
7
  id = $derived(this.#props.id);
@@ -9,11 +10,11 @@ export class ColumnState {
9
10
  */
10
11
  table;
11
12
  snippets = $derived({
12
- header: this.#props.header,
13
+ header: typeof this.#props.header === 'string' ? getDefaultHeader(this.#props.header) : this.#props.header,
13
14
  /** Title is the header-snippet, with header-ctx: `{ header: false }` */
14
15
  title: (...args) => {
15
16
  const getData = () => this.table.data.current;
16
- return this.#props.header?.(...[args[0], () => ({
17
+ return this.snippets.header?.(...[args[0], () => ({
17
18
  get header() { return false; },
18
19
  get data() {
19
20
  return getData();
@@ -8,30 +8,6 @@
8
8
 
9
9
  -->
10
10
 
11
- <script module lang='ts'>
12
-
13
- export class PanelTween {
14
- #tween = new Tween(0, { duration: 300, easing: sineInOut })
15
- current = $derived(this.#tween.current)
16
- transitioning = $state(false)
17
-
18
- /** bind:clientWidth */
19
- width = $state(0)
20
-
21
- set target(value: number) {
22
- this.transitioning = true
23
- this.#tween.set(value).then(() => this.transitioning = false)
24
- }
25
-
26
- constructor(cb: () => string | undefined, added = 0) {
27
- $effect.pre(() => {
28
- this.target = cb() ? this.width + added : 0
29
- })
30
- }
31
- }
32
-
33
- </script>
34
-
35
11
  <script lang='ts' generics='T extends Record<PropertyKey, unknown>'>
36
12
 
37
13
  import { Tween } from 'svelte/motion'
@@ -1,12 +1,3 @@
1
- export declare class PanelTween {
2
- #private;
3
- current: number;
4
- transitioning: boolean;
5
- /** bind:clientWidth */
6
- width: number;
7
- set target(value: number);
8
- constructor(cb: () => string | undefined, added?: number);
9
- }
10
1
  declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
11
2
  props(): PanelProps<T_1>;
12
3
  events(): {};
@@ -0,0 +1,24 @@
1
+ <!-- @component
2
+
3
+ This is a description, \
4
+ on how to use this.
5
+
6
+ @example
7
+ <Component />
8
+
9
+ -->
10
+
11
+ <script lang='ts'>
12
+
13
+ import { RowState, type RowProps } from './row.svelte.js'
14
+ import type { AnyRecord } from '../utility.svelte.js'
15
+ import { fromProps } from '../utility.svelte.js'
16
+
17
+ type T = $$Generic<AnyRecord>
18
+
19
+ let { ...restProps }: RowProps<T> = $props()
20
+
21
+ const properties = fromProps(restProps)
22
+ new RowState<T>(properties)
23
+
24
+ </script>
@@ -0,0 +1,25 @@
1
+ import type { AnyRecord } from '../utility.svelte.js';
2
+ declare class __sveltets_Render<T extends AnyRecord> {
3
+ props(): RowProps<T_1>;
4
+ events(): {};
5
+ slots(): {};
6
+ bindings(): "";
7
+ exports(): {};
8
+ }
9
+ interface $$IsomorphicComponent {
10
+ new <T extends AnyRecord>(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']>> & {
11
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
12
+ } & ReturnType<__sveltets_Render<T>['exports']>;
13
+ <T extends AnyRecord>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
14
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
15
+ }
16
+ /**
17
+ * This is a description, \
18
+ * on how to use this.
19
+ *
20
+ * @example
21
+ * <Component />
22
+ */
23
+ declare const Row: $$IsomorphicComponent;
24
+ type Row<T extends AnyRecord> = InstanceType<typeof Row<T>>;
25
+ export default Row;
@@ -0,0 +1,28 @@
1
+ import { TableState } from '../table/table.svelte.js';
2
+ export class RowState {
3
+ #table;
4
+ #props = {};
5
+ snippets = $derived({
6
+ context: this.#props.context,
7
+ contextHeader: this.#props.contextHeader
8
+ });
9
+ events = $derived({
10
+ onclick: this.#props.onclick,
11
+ oncontextmenu: this.#props.oncontextmenu
12
+ });
13
+ options = $derived({
14
+ context: {
15
+ hover: this.#props.contextOptions?.hover ?? true,
16
+ width: this.#props.contextOptions?.width ?? 'max-content'
17
+ }
18
+ });
19
+ constructor(props) {
20
+ this.#props = props;
21
+ this.#table = TableState.getContext();
22
+ if (!this.#table) {
23
+ throw new Error('svelte-tably: Expandable must be associated with a Table');
24
+ }
25
+ this.#table.row = this;
26
+ $effect(() => () => this.#table.row === this && (this.#table.row = undefined));
27
+ }
28
+ }
@@ -13,13 +13,13 @@
13
13
  </script>
14
14
 
15
15
  <script lang="ts">
16
- import { untrack, type Snippet } from 'svelte'
16
+ import { type Snippet } from 'svelte'
17
17
  import { fly } from 'svelte/transition'
18
18
  import { sineInOut } from 'svelte/easing'
19
19
  import reorder, { type ItemState } from 'runic-reorder'
20
20
  import { Virtualization } from './virtualization.svelte.js'
21
21
  import { TableState, type HeaderSelectCtx, type RowCtx, type RowSelectCtx, type TableProps } from './table.svelte.js'
22
- import Panel, { PanelTween } from '../panel/Panel.svelte'
22
+ import Panel from '../panel/Panel.svelte'
23
23
  import Column from '../column/Column.svelte'
24
24
  import { assignDescriptors, fromProps, mounted } from '../utility.svelte.js'
25
25
  import { conditional } from '../conditional.svelte.js'
@@ -27,6 +27,8 @@
27
27
  import Expandable from '../expandable/Expandable.svelte'
28
28
  import { SizeTween } from '../size-tween.svelte.js'
29
29
  import { on } from 'svelte/events'
30
+ import Row from '../row/Row.svelte'
31
+
30
32
 
31
33
  type T = $$Generic<Record<PropertyKey, unknown>>
32
34
 
@@ -40,6 +42,7 @@
40
42
  }
41
43
  Panel: typeof Panel<T>
42
44
  Expandable: typeof Expandable<T>
45
+ Row: typeof Row<T>
43
46
  readonly table: TableState<T>
44
47
  readonly data: T[]
45
48
  }
@@ -95,18 +98,28 @@
95
98
  /** grid-template-columns for widths */
96
99
  const style = $derived.by(() => {
97
100
  if (!mount.isMounted) return ''
98
- const templateColumns = `
99
- #${table.id} > .headers,
100
- tr.row[data-svelte-tably='${table.id}'],
101
- #${table.id} > tfoot > tr,
102
- #${table.id} > .content > .virtual.bottom {
103
- grid-template-columns: ${columns
101
+
102
+ const context = table.row?.snippets.context ? table.row?.options.context.width : ''
103
+
104
+ const templateColumns = columns
104
105
  .map((column, i, arr) => {
105
106
  const width = getWidth(column.id)
106
107
  if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
107
108
  return `${width}px`
108
109
  })
109
- .join(' ')};
110
+ .join(' ') + context
111
+
112
+ const theadTempla3teColumns = `
113
+ #${table.id} > thead > tr,
114
+ #${table.id} > tfoot > tr {
115
+ grid-template-columns: ${templateColumns};
116
+ }
117
+ `
118
+
119
+ const tbodyTemplateColumns = `
120
+ [data-area-class='${table.id}'] tr.row,
121
+ #${table.id} > tbody::after {
122
+ grid-template-columns: ${templateColumns};
110
123
  }
111
124
  `
112
125
 
@@ -124,12 +137,12 @@
124
137
  .join('')
125
138
 
126
139
  const columnStyling = columns.map(column => !column.options.style ? '' : `
127
- #${table.id} .column[data-column='${column.id}'] {
140
+ [data-area-class='${table.id}'] .column[data-column='${column.id}'] {
128
141
  ${column.options.style}
129
142
  }
130
143
  `).join('')
131
144
 
132
- return templateColumns + stickyLeft + columnStyling
145
+ return theadTempla3teColumns + tbodyTemplateColumns + stickyLeft + columnStyling
133
146
  })
134
147
 
135
148
  function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
@@ -231,6 +244,27 @@
231
244
 
232
245
 
233
246
  let expandedRow = $state([]) as T[]
247
+ let expandTick = false
248
+ function toggleExpand(item: T, value?: boolean) {
249
+ if(expandTick) return
250
+ expandTick = true
251
+ requestAnimationFrame(() => expandTick = false)
252
+
253
+ let indexOf = expandedRow.indexOf(item)
254
+ if(value === undefined) {
255
+ value = indexOf === -1
256
+ }
257
+ if(!value) {
258
+ expandedRow.splice(indexOf, 1)
259
+ return
260
+ }
261
+ if(table.expandable?.options.multiple === true) {
262
+ expandedRow.push(item)
263
+ }
264
+ else {
265
+ expandedRow[0] = item
266
+ }
267
+ }
234
268
 
235
269
  function addRowColumnEvents(
236
270
  node: HTMLTableColElement,
@@ -243,6 +277,18 @@
243
277
  }
244
278
  }
245
279
 
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)))
286
+ }
287
+ if(table.row?.events.oncontextmenu) {
288
+ $effect(() => on(node, 'contextmenu', e => table.row?.events.oncontextmenu!(e, ctx)))
289
+ }
290
+ }
291
+
246
292
  </script>
247
293
 
248
294
  <!---------------------------------------------------->
@@ -392,25 +438,12 @@
392
438
  {/each}
393
439
  {/snippet}
394
440
 
441
+ {#snippet defaultRow(item: T, ctx: RowColumnCtx<T, any>)}
442
+ {ctx.value}
443
+ {/snippet}
444
+
395
445
  {#snippet rowSnippet(item: T, itemState?: ItemState<T>)}
396
446
  {@const index = itemState?.index ?? 0}
397
- {@const toggleExpand = (value?: boolean) => {
398
- let indexOf = expandedRow.indexOf(item)
399
- if(value !== undefined) {
400
- value = indexOf === -1
401
- }
402
-
403
- if(!value) {
404
- expandedRow.splice(indexOf, 1)
405
- return
406
- }
407
- if(table.expandable?.options.multiple === true) {
408
- expandedRow.push(item)
409
- }
410
- else {
411
- expandedRow[0] = item
412
- }
413
- }}
414
447
 
415
448
  {@const ctx: RowCtx<T> = {
416
449
  get index() {
@@ -434,16 +467,14 @@
434
467
  return expandedRow.includes(item)
435
468
  },
436
469
  set expanded(value) {
437
- toggleExpand(value)
470
+ toggleExpand(item, value)
438
471
  }
439
472
  }}
440
473
 
441
474
  <tr
442
475
  aria-rowindex={index + 1}
443
- data-svelte-tably={table.id}
444
476
  style:opacity={itemState?.positioning ? 0 : 1}
445
477
  class='row'
446
- class:hover={hoveredRow === item}
447
478
  class:dragging={itemState?.dragging}
448
479
  class:selected={table.selected?.includes(item)}
449
480
  class:first={index === 0}
@@ -452,18 +483,19 @@
452
483
  {...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
453
484
  onpointerenter={() => (hoveredRow = item)}
454
485
  onpointerleave={() => (hoveredRow = null)}
486
+ use:addRowEvents={ctx}
455
487
  onclick={(e) => {
456
488
  if (table.expandable?.options.click === true) {
457
489
  let target = e.target as HTMLElement
458
490
  if(['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
459
491
  return
460
492
  }
461
- toggleExpand()
493
+ ctx.expanded = !ctx.expanded
462
494
  }
463
495
  }}
464
496
  >
465
497
  {@render columnsSnippet(
466
- (column) => column.snippets.row,
498
+ (column) => column.snippets.row ?? defaultRow,
467
499
  (column) => {
468
500
  return [
469
501
  item,
@@ -476,6 +508,17 @@
476
508
  },
477
509
  'row'
478
510
  )}
511
+ {#if table.row?.snippets.context}
512
+ {#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
513
+ <td
514
+ class='context-col'
515
+ class:hover={!table.row?.snippets.contextHeader && table.row?.options.context.hover}
516
+ class:hidden={table.row?.options.context.hover && table.row?.snippets.contextHeader && hoveredRow !== item}
517
+ >
518
+ {@render table.row?.snippets.context?.(item, ctx)}
519
+ </td>
520
+ {/if}
521
+ {/if}
479
522
  </tr>
480
523
 
481
524
  {@const expandableTween = new SizeTween(
@@ -506,20 +549,30 @@
506
549
  >
507
550
  {#if columns.some(v => v.snippets.header)}
508
551
  <thead class='headers' bind:this={elements.headers}>
509
- {@render columnsSnippet(
510
- (column) => column.snippets.header,
511
- () => [{
512
- get header() { return true },
513
- get data() { return data.current }
514
- }],
515
- 'header'
516
- )}
552
+ <tr style='min-width: {tbody.width}px'>
553
+ {@render columnsSnippet(
554
+ (column) => column.snippets.header,
555
+ () => [{
556
+ get header() { return true },
557
+ get data() { return data.current }
558
+ }],
559
+ 'header'
560
+ )}
561
+ {#if table.row?.snippets.contextHeader}
562
+ <th
563
+ class='context-col'
564
+ >
565
+ {@render table.row?.snippets.contextHeader()}
566
+ </th>
567
+ {/if}
568
+ </tr>
569
+ <tr style='width:400px;background:none;pointer-events:none;'></tr>
517
570
  </thead>
518
571
  {/if}
519
572
 
520
573
  <tbody
521
574
  class='content'
522
- use:reorderArea={{ axis: 'y' }}
575
+ use:reorderArea={{ axis: 'y', class: table.id }}
523
576
  bind:this={virtualization.viewport.element}
524
577
  onscrollcapture={onscroll}
525
578
  bind:clientHeight={virtualization.viewport.height}
@@ -555,6 +608,7 @@
555
608
  'statusbar'
556
609
  )}
557
610
  </tr>
611
+ <tr style='width:400px;background:none;pointer-events:none;'></tr>
558
612
  </tfoot>
559
613
  {/if}
560
614
 
@@ -591,7 +645,7 @@
591
645
  {/snippet}
592
646
 
593
647
  {#snippet rowSelected(ctx: RowSelectCtx<T>)}
594
- <input type='checkbox' bind:checked={ctx.isSelected} />
648
+ <input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
595
649
  {/snippet}
596
650
 
597
651
  {#if table.options.select || table.options.reorderable || table.expandable}
@@ -673,9 +727,11 @@
673
727
  }
674
728
  })}
675
729
  {/if}
676
- {#if expandable && (row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover'))}
677
- <button class='expand-row' onclick={() => row.expanded = !row.expanded}>
678
- {@render chevronSnippet(row.expanded ? 180 : 90)}
730
+ {#if expandable && expandable?.options.chevron !== 'never'}
731
+ <button class='expand-row' tabindex='-1' onclick={() => row.expanded = !row.expanded}>
732
+ {#if row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover')}
733
+ {@render chevronSnippet(row.expanded ? 180 : 90)}
734
+ {/if}
679
735
  </button>
680
736
  {/if}
681
737
  </div>
@@ -688,6 +744,7 @@
688
744
  Column,
689
745
  Panel,
690
746
  Expandable,
747
+ Row,
691
748
  get table() {
692
749
  return table
693
750
  },
@@ -704,11 +761,49 @@
704
761
  background-color: inherit;
705
762
  }
706
763
 
764
+ .context-col {
765
+ display: flex;
766
+ align-items: center;
767
+ justify-content: center;
768
+ position: sticky;
769
+ right: 0;
770
+ height: 100%;
771
+ z-index: 3;
772
+ padding: 0;
773
+
774
+ &.hover {
775
+ position: absolute;
776
+ }
777
+ &.hidden {
778
+ pointer-events: none;
779
+ user-select: none;
780
+ border-left: none;
781
+ background: none;
782
+ > :global(*) {
783
+ opacity: 0;
784
+ }
785
+ }
786
+ }
787
+
788
+ :global(:root) {
789
+ --tably-color: hsl(0, 0%, 0%);
790
+ --tably-bg: hsl(0, 0%, 100%);
791
+ --tably-statusbar: hsl(0, 0%, 98%);
792
+
793
+ --tably-border: hsl(0, 0%, 90%);
794
+ --tably-border-grid: hsl(0, 0%, 98%);
795
+
796
+ --tably-padding-x: 1rem;
797
+ --tably-padding-y: 0.5rem;
798
+
799
+ --tably-radius: 0.25rem;
800
+ }
801
+
707
802
  .svelte-tably {
708
803
  position: relative;
709
804
  overflow: visible;
710
805
  }
711
-
806
+
712
807
  .expandable {
713
808
  position: relative;
714
809
 
@@ -734,7 +829,7 @@
734
829
  cursor: pointer;
735
830
  background-color: transparent;
736
831
  color: inherit;
737
- width: 22px;
832
+ width: 20px;
738
833
  height: 100%;
739
834
 
740
835
  > svg {
@@ -763,12 +858,15 @@
763
858
  justify-items: end;
764
859
  margin: 0;
765
860
  margin-left: auto;
766
- margin-right: var(--tably-padding-x, 1rem);
767
861
  > svg {
768
862
  transition: transform 0.15s ease;
769
863
  }
770
864
  }
771
865
 
866
+ th:not(:last-child) .sorting-icon {
867
+ margin-right: var(--tably-padding-x);
868
+ }
869
+
772
870
  .__fixed {
773
871
  display: flex;
774
872
  align-items: center;
@@ -782,13 +880,6 @@
782
880
  width: 100%;
783
881
  }
784
882
 
785
- .first .__fixed {
786
- top: var(--tably-padding-y, 0.5rem);
787
- }
788
- .last .__fixed {
789
- bottom: var(--tably-padding-y, 0.5rem);
790
- }
791
-
792
883
  tbody::before,
793
884
  tbody::after,
794
885
  selects::before,
@@ -840,17 +931,6 @@
840
931
  }
841
932
  }
842
933
 
843
- .headers,
844
- .statusbar {
845
- /* So that the scrollbar doesn't cause the headers/statusbar to shift */
846
- padding-right: 11px;
847
- }
848
-
849
- .table {
850
- color: var(--tably-color, hsl(0, 0%, 0%));
851
- background-color: var(--tably-bg, hsl(0, 0%, 100%));
852
- }
853
-
854
934
  .sticky {
855
935
  position: sticky;
856
936
  /* right: 100px; */
@@ -858,16 +938,23 @@
858
938
  }
859
939
 
860
940
  .sticky.border {
861
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
941
+ border-right: 1px solid var(--tably-border);
862
942
  }
863
943
 
864
- .headers > .column {
865
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
944
+ .headers > tr > .column {
866
945
  overflow: hidden;
867
- padding: var(--tably-padding-y, 0.5rem) 0;
946
+ padding: var(--tably-padding-y) 0;
868
947
  cursor: default;
869
948
  user-select: none;
870
949
 
950
+ &.sticky.border {
951
+ border-right-color: var(--tably-border);
952
+ }
953
+
954
+ &:last-child {
955
+ border-right: none;
956
+ }
957
+
871
958
  &.sortable {
872
959
  cursor: pointer;
873
960
  }
@@ -879,32 +966,41 @@
879
966
 
880
967
  .table {
881
968
  display: grid;
882
- height: 100%;
969
+ height: auto;
970
+ max-height: 100%;
883
971
  position: relative;
884
972
 
973
+ color: var(--tably-color);
974
+ background-color: var(--tably-bg);
975
+
885
976
  grid-template-areas:
886
- 'headers panel'
887
- 'rows panel'
888
- 'statusbar panel';
977
+ 'headers panel'
978
+ 'rows panel'
979
+ 'statusbar panel';
889
980
 
890
981
  grid-template-columns: auto min-content;
891
982
  grid-template-rows: auto 1fr auto;
892
983
 
893
- border: 1px solid var(--tably-border, hsl(0, 0%, 90%));
894
- border-radius: var(--tably-radius, 0.25rem);
895
-
896
- max-height: 100%;
984
+ border: 1px solid var(--tably-border);
985
+ border-radius: var(--tably-radius);
897
986
  }
898
987
 
899
988
  .headers {
989
+ display: flex;
900
990
  grid-area: headers;
901
991
  z-index: 2;
902
992
  overflow: hidden;
903
993
  }
904
994
 
905
- .headers > .column {
995
+ .headers > tr > .column {
906
996
  width: auto !important;
907
- border-bottom: 1px solid var(--tably-border, hsl(0, 0%, 90%));
997
+ border-bottom: 1px solid var(--tably-border);
998
+ }
999
+ .headers > tr {
1000
+ > .column, > .context-col {
1001
+ border-bottom: 1px solid var(--tably-border);
1002
+ border-left: 1px solid var(--tably-border-grid);
1003
+ }
908
1004
  }
909
1005
 
910
1006
  .content {
@@ -914,21 +1010,21 @@
914
1010
  grid-area: rows;
915
1011
  scrollbar-width: thin;
916
1012
  overflow: auto;
917
- /* height: 100%; */
918
1013
  }
919
1014
 
920
1015
  .statusbar {
1016
+ display: flex;
921
1017
  grid-area: statusbar;
922
1018
  overflow: hidden;
923
- background-color: var(--tably-statusbar, hsl(0, 0%, 98%));
1019
+ background-color: var(--tably-statusbar);
924
1020
  }
925
1021
 
926
1022
  .statusbar > tr > .column {
927
- border-top: 1px solid var(--tably-border, hsl(0, 0%, 90%));
928
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
1023
+ border-top: 1px solid var(--tably-border);
1024
+ padding: calc(var(--tably-padding-y) / 2) 0;
929
1025
  }
930
1026
 
931
- .headers,
1027
+ .headers > tr,
932
1028
  .row,
933
1029
  .statusbar > tr {
934
1030
  position: relative;
@@ -938,25 +1034,22 @@
938
1034
 
939
1035
  & > .column {
940
1036
  display: flex;
941
- padding-left: var(--tably-padding-x, 1rem);
1037
+ padding-left: var(--tably-padding-x);
942
1038
  overflow: hidden;
943
1039
  }
944
1040
 
945
- & > *:last-child {
1041
+ & > *:last-child:not(.context-col) {
946
1042
  width: 100%;
947
- padding-right: var(--tably-padding-x, 1rem);
1043
+ padding-right: var(--tably-padding-x);
948
1044
  }
949
1045
  }
950
1046
 
951
- .row:first-child:not(.dragging) > * {
952
- padding-top: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
953
- }
954
- .row:last-child:not(.dragging) > * {
955
- padding-bottom: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
1047
+ .row > .column {
1048
+ padding: var(--tably-padding-y) 0;
956
1049
  }
957
-
958
1050
  .row > * {
959
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
1051
+ border-left: 1px solid var(--tably-border-grid);
1052
+ border-bottom: 1px solid var(--tably-border-grid);
960
1053
  }
961
1054
 
962
1055
  .panel {
@@ -964,7 +1057,7 @@
964
1057
  grid-area: panel;
965
1058
  height: 100%;
966
1059
  overflow: hidden;
967
- border-left: 1px solid var(--tably-border, hsl(0, 0%, 90%));
1060
+ border-left: 1px solid var(--tably-border);
968
1061
 
969
1062
  z-index: 4;
970
1063
 
@@ -976,7 +1069,7 @@
976
1069
  width: min-content;
977
1070
  overflow: auto;
978
1071
  scrollbar-width: thin;
979
- padding: var(--tably-padding-y, 0.5rem) 0;
1072
+ padding: var(--tably-padding-y) 0;
980
1073
  }
981
1074
  }
982
1075
  </style>
@@ -10,6 +10,7 @@ export class TableState {
10
10
  columns = $state({});
11
11
  panels = $state({});
12
12
  expandable = $state();
13
+ row = $state();
13
14
  /** Currently selected items */
14
15
  get selected() { return this.#props.selected ??= []; }
15
16
  set selected(items) { this.#props.selected = items; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-tably",
3
- "version": "1.0.0-next.14",
3
+ "version": "1.0.0-next.16",
4
4
  "repository": "github:refzlund/svelte-tably",
5
5
  "homepage": "https://github.com/Refzlund/svelte-tably",
6
6
  "bugs": {