svelte-tably 1.0.0-next.15 → 1.0.0-next.17

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
+ - [x] Auto: Table based on data, sortable
24
25
 
25
26
  ### Usage Notes
26
27
 
@@ -37,9 +38,12 @@ A high performant, feature rich, dynamic table
37
38
  let selected = $state([]) as typeof data
38
39
  </script>
39
40
 
41
+ <!-- Auto: Generate Columns for you -->
42
+ <Table auto {data} resizeable={false} filters=[...] />
43
+
40
44
  <Table {data} panel={activePanel} select bind:selected>
41
- {#snippet content({ Column, Panel, state, data })}
42
- <Column id='name' sticky>
45
+ {#snippet content({ Column, Panel, Expandable, Row, state, data })}
46
+ <Column id='name' sticky sort>
43
47
  {#snippet header()}
44
48
  Name
45
49
  {/snippet}
@@ -52,9 +56,10 @@ A high performant, feature rich, dynamic table
52
56
  {data.length}
53
57
  {/snippet}
54
58
  </Column>
55
- <Column ...>
56
- ...
57
- </Column>
59
+
60
+ <!-- Simplified -->
61
+ <Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
62
+
58
63
  <!-- If you want to sort/filter a virtual value, that does not exist in the data -->
59
64
  <Column id='virtual' value={row => row.age > 18}>
60
65
  ...
@@ -64,6 +69,21 @@ A high performant, feature rich, dynamic table
64
69
  ...
65
70
  </Column>
66
71
 
72
+ <Expandable click={false}>
73
+ {#snippet content(item, ctx)}
74
+ ...
75
+ {/snippet}
76
+ </Expandable>
77
+
78
+ <Row onclick={...} oncontextmenu={...}>
79
+ {#snippet contextHeader()}
80
+ ...
81
+ {/snippet}
82
+ {#snippet context(item, ctx)}
83
+ ...
84
+ {/snippet}
85
+ </Row>
86
+
67
87
  <Panel id='columns'>
68
88
  <!-- Anything you might like -->
69
89
  </Panel>
@@ -80,14 +100,15 @@ For quick styling
80
100
 
81
101
  | CSS Variable | Description | Default |
82
102
  | - | - | - |
83
- | --tably-bg | background-color | `hsl(0, 0%, 100%)` |
84
- | --tably-color | color | `hsl(0, 0%, 0%)` |
85
- | --tably-border | border | `hsl(0, 0%, 90%)` |
103
+ | --tably-bg | Background color | `hsl(0, 0%, 100%)` |
104
+ | --tably-color | Text color | `hsl(0, 0%, 0%)` |
105
+ | --tably-border | Border for sticky columns and header | `hsl(0, 0%, 90%)` |
106
+ | --tably-border-grid | Border for the table-grid | `hsl(0, 0%, 98%)` |
86
107
  | --tably-statusbar | background-color for the statusbar | `hsl(0, 0%, 98%)` |
87
108
  | --tably-padding-y | Padding above/below each column | `.5rem` |
88
109
  | --tably-padding-x | Padding left of each column | `1rem` |
89
110
  | --tably-radius | Table radius | `.25rem` |
90
111
 
91
- Advanced styling can be done via `:global .svelte-tably`
92
-
93
-
112
+ > [!NOTE]
113
+ > Advanced styling can be done via `:global(.svelte-tably)`
114
+ > `table > thead > tr > th, table > tbody > tr > td, table > tfoot > tr > td`
@@ -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,20 +13,22 @@
13
13
  </script>
14
14
 
15
15
  <script lang="ts">
16
- import { tick, 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
- import { assignDescriptors, fromProps, mounted } from '../utility.svelte.js'
24
+ import { assignDescriptors, capitalize, fromProps, mounted, segmentize } from '../utility.svelte.js'
25
25
  import { conditional } from '../conditional.svelte.js'
26
26
  import { ColumnState, type RowColumnCtx } from '../column/column.svelte.js'
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
  }
@@ -52,7 +55,7 @@
52
55
  panel: _panel = $bindable(),
53
56
  data: _data = $bindable([]),
54
57
  ...restProps
55
- }: TableProps<T> & { content: ContentSnippet } = $props()
58
+ }: TableProps<T> & { content?: ContentSnippet } = $props()
56
59
 
57
60
  const properties = fromProps(restProps, {
58
61
  selected: [() => _selected, v => _selected = v],
@@ -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) {
@@ -264,6 +277,18 @@
264
277
  }
265
278
  }
266
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
+
267
292
  </script>
268
293
 
269
294
  <!---------------------------------------------------->
@@ -284,14 +309,18 @@
284
309
  <tr>
285
310
  {#each renderedColumns as column}
286
311
  <td>
287
- {@render column.snippets.row?.(row, {
288
- index: i,
289
- value: column.options.value?.(row),
290
- isHovered: false,
291
- itemState: { index: i, dragging: false, positioning: false } as ItemState<any>,
292
- selected: false,
293
- expanded: false
294
- })}
312
+ {#if column.snippets.row}
313
+ {@render column.snippets.row(row, {
314
+ index: i,
315
+ value: column.options.value?.(row),
316
+ isHovered: false,
317
+ itemState: { index: i, dragging: false, positioning: false } as ItemState<any>,
318
+ selected: false,
319
+ expanded: false
320
+ })}
321
+ {:else}
322
+ {column.options.value?.(row)}
323
+ {/if}
295
324
  </td>
296
325
  {/each}
297
326
  </tr>
@@ -321,7 +350,7 @@
321
350
  {/snippet}
322
351
 
323
352
  {#snippet dragSnippet()}
324
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
353
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style='opacity: .3'>
325
354
  <path
326
355
  fill="currentColor"
327
356
  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"
@@ -413,6 +442,10 @@
413
442
  {/each}
414
443
  {/snippet}
415
444
 
445
+ {#snippet defaultRow(item: T, ctx: RowColumnCtx<T, any>)}
446
+ {ctx.value}
447
+ {/snippet}
448
+
416
449
  {#snippet rowSnippet(item: T, itemState?: ItemState<T>)}
417
450
  {@const index = itemState?.index ?? 0}
418
451
 
@@ -444,10 +477,8 @@
444
477
 
445
478
  <tr
446
479
  aria-rowindex={index + 1}
447
- data-svelte-tably={table.id}
448
480
  style:opacity={itemState?.positioning ? 0 : 1}
449
481
  class='row'
450
- class:hover={hoveredRow === item}
451
482
  class:dragging={itemState?.dragging}
452
483
  class:selected={table.selected?.includes(item)}
453
484
  class:first={index === 0}
@@ -456,6 +487,7 @@
456
487
  {...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
457
488
  onpointerenter={() => (hoveredRow = item)}
458
489
  onpointerleave={() => (hoveredRow = null)}
490
+ use:addRowEvents={ctx}
459
491
  onclick={(e) => {
460
492
  if (table.expandable?.options.click === true) {
461
493
  let target = e.target as HTMLElement
@@ -467,7 +499,7 @@
467
499
  }}
468
500
  >
469
501
  {@render columnsSnippet(
470
- (column) => column.snippets.row,
502
+ (column) => column.snippets.row ?? defaultRow,
471
503
  (column) => {
472
504
  return [
473
505
  item,
@@ -480,6 +512,17 @@
480
512
  },
481
513
  'row'
482
514
  )}
515
+ {#if table.row?.snippets.context}
516
+ {#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
517
+ <td
518
+ class='context-col'
519
+ 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}
521
+ >
522
+ {@render table.row?.snippets.context?.(item, ctx)}
523
+ </td>
524
+ {/if}
525
+ {/if}
483
526
  </tr>
484
527
 
485
528
  {@const expandableTween = new SizeTween(
@@ -510,20 +553,30 @@
510
553
  >
511
554
  {#if columns.some(v => v.snippets.header)}
512
555
  <thead class='headers' bind:this={elements.headers}>
513
- {@render columnsSnippet(
514
- (column) => column.snippets.header,
515
- () => [{
516
- get header() { return true },
517
- get data() { return data.current }
518
- }],
519
- 'header'
520
- )}
556
+ <tr style='min-width: {tbody.width}px'>
557
+ {@render columnsSnippet(
558
+ (column) => column.snippets.header,
559
+ () => [{
560
+ get header() { return true },
561
+ get data() { return data.current }
562
+ }],
563
+ 'header'
564
+ )}
565
+ {#if table.row?.snippets.contextHeader}
566
+ <th
567
+ class='context-col'
568
+ >
569
+ {@render table.row?.snippets.contextHeader()}
570
+ </th>
571
+ {/if}
572
+ </tr>
573
+ <tr style='width:400px;background:none;pointer-events:none;'></tr>
521
574
  </thead>
522
575
  {/if}
523
576
 
524
577
  <tbody
525
578
  class='content'
526
- use:reorderArea={{ axis: 'y' }}
579
+ use:reorderArea={{ axis: 'y', class: table.id }}
527
580
  bind:this={virtualization.viewport.element}
528
581
  onscrollcapture={onscroll}
529
582
  bind:clientHeight={virtualization.viewport.height}
@@ -559,6 +612,7 @@
559
612
  'statusbar'
560
613
  )}
561
614
  </tr>
615
+ <tr style='width:400px;background:none;pointer-events:none;'></tr>
562
616
  </tfoot>
563
617
  {/if}
564
618
 
@@ -595,7 +649,7 @@
595
649
  {/snippet}
596
650
 
597
651
  {#snippet rowSelected(ctx: RowSelectCtx<T>)}
598
- <input type='checkbox' bind:checked={ctx.isSelected} />
652
+ <input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
599
653
  {/snippet}
600
654
 
601
655
  {#if table.options.select || table.options.reorderable || table.expandable}
@@ -612,10 +666,10 @@
612
666
  id='__fixed'
613
667
  {table}
614
668
  fixed
615
- width={Math.max(56, 0
669
+ width={Math.max(48, 0
616
670
  + (select && show !== 'never' ? 34 : 0)
617
671
  + (reorderable ? 34 : 0)
618
- + (expandable?.options.chevron !== 'never' ? 34 : 0)
672
+ + (expandable && expandable?.options.chevron !== 'never' ? 34 : 0)
619
673
  )}
620
674
  resizeable={false}
621
675
  >
@@ -677,9 +731,11 @@
677
731
  }
678
732
  })}
679
733
  {/if}
680
- {#if expandable && (row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover'))}
681
- <button class='expand-row' onclick={() => row.expanded = !row.expanded}>
682
- {@render chevronSnippet(row.expanded ? 180 : 90)}
734
+ {#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')}
737
+ {@render chevronSnippet(row.expanded ? 180 : 90)}
738
+ {/if}
683
739
  </button>
684
740
  {/if}
685
741
  </div>
@@ -688,10 +744,26 @@
688
744
  {/if}
689
745
  {/if}
690
746
 
747
+ {#if table.options.auto}
748
+ {#each Object.keys(data.current[0] || {}) as key}
749
+ <Column
750
+ id={key}
751
+ value={r => r[key]}
752
+ 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
+ }
758
+ />
759
+ {/each}
760
+ {/if}
761
+
691
762
  {@render content?.({
692
763
  Column,
693
764
  Panel,
694
765
  Expandable,
766
+ Row,
695
767
  get table() {
696
768
  return table
697
769
  },
@@ -708,11 +780,49 @@
708
780
  background-color: inherit;
709
781
  }
710
782
 
783
+ .context-col {
784
+ display: flex;
785
+ align-items: center;
786
+ justify-content: center;
787
+ position: sticky;
788
+ right: 0;
789
+ height: 100%;
790
+ z-index: 3;
791
+ padding: 0;
792
+
793
+ &.hover {
794
+ position: absolute;
795
+ }
796
+ &.hidden {
797
+ pointer-events: none;
798
+ user-select: none;
799
+ border-left: none;
800
+ background: none;
801
+ > :global(*) {
802
+ opacity: 0;
803
+ }
804
+ }
805
+ }
806
+
807
+ :global(:root) {
808
+ --tably-color: hsl(0, 0%, 0%);
809
+ --tably-bg: hsl(0, 0%, 100%);
810
+ --tably-statusbar: hsl(0, 0%, 98%);
811
+
812
+ --tably-border: hsl(0, 0%, 90%);
813
+ --tably-border-grid: hsl(0, 0%, 98%);
814
+
815
+ --tably-padding-x: 1rem;
816
+ --tably-padding-y: 0.5rem;
817
+
818
+ --tably-radius: 0.25rem;
819
+ }
820
+
711
821
  .svelte-tably {
712
822
  position: relative;
713
823
  overflow: visible;
714
824
  }
715
-
825
+
716
826
  .expandable {
717
827
  position: relative;
718
828
 
@@ -738,7 +848,7 @@
738
848
  cursor: pointer;
739
849
  background-color: transparent;
740
850
  color: inherit;
741
- width: 22px;
851
+ width: 20px;
742
852
  height: 100%;
743
853
 
744
854
  > svg {
@@ -767,12 +877,15 @@
767
877
  justify-items: end;
768
878
  margin: 0;
769
879
  margin-left: auto;
770
- margin-right: var(--tably-padding-x, 1rem);
771
880
  > svg {
772
881
  transition: transform 0.15s ease;
773
882
  }
774
883
  }
775
884
 
885
+ th:not(:last-child) .sorting-icon {
886
+ margin-right: var(--tably-padding-x);
887
+ }
888
+
776
889
  .__fixed {
777
890
  display: flex;
778
891
  align-items: center;
@@ -786,13 +899,6 @@
786
899
  width: 100%;
787
900
  }
788
901
 
789
- .first .__fixed {
790
- top: var(--tably-padding-y, 0.5rem);
791
- }
792
- .last .__fixed {
793
- bottom: var(--tably-padding-y, 0.5rem);
794
- }
795
-
796
902
  tbody::before,
797
903
  tbody::after,
798
904
  selects::before,
@@ -844,17 +950,6 @@
844
950
  }
845
951
  }
846
952
 
847
- .headers,
848
- .statusbar {
849
- /* So that the scrollbar doesn't cause the headers/statusbar to shift */
850
- padding-right: 11px;
851
- }
852
-
853
- .table {
854
- color: var(--tably-color, hsl(0, 0%, 0%));
855
- background-color: var(--tably-bg, hsl(0, 0%, 100%));
856
- }
857
-
858
953
  .sticky {
859
954
  position: sticky;
860
955
  /* right: 100px; */
@@ -862,16 +957,23 @@
862
957
  }
863
958
 
864
959
  .sticky.border {
865
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
960
+ border-right: 1px solid var(--tably-border);
866
961
  }
867
962
 
868
- .headers > .column {
869
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
963
+ .headers > tr > .column {
870
964
  overflow: hidden;
871
- padding: var(--tably-padding-y, 0.5rem) 0;
965
+ padding: var(--tably-padding-y) 0;
872
966
  cursor: default;
873
967
  user-select: none;
874
968
 
969
+ &.sticky.border {
970
+ border-right-color: var(--tably-border);
971
+ }
972
+
973
+ &:last-child {
974
+ border-right: none;
975
+ }
976
+
875
977
  &.sortable {
876
978
  cursor: pointer;
877
979
  }
@@ -883,32 +985,41 @@
883
985
 
884
986
  .table {
885
987
  display: grid;
886
- height: 100%;
988
+ height: auto;
989
+ max-height: 100%;
887
990
  position: relative;
888
991
 
992
+ color: var(--tably-color);
993
+ background-color: var(--tably-bg);
994
+
889
995
  grid-template-areas:
890
- 'headers panel'
891
- 'rows panel'
892
- 'statusbar panel';
996
+ 'headers panel'
997
+ 'rows panel'
998
+ 'statusbar panel';
893
999
 
894
1000
  grid-template-columns: auto min-content;
895
1001
  grid-template-rows: auto 1fr auto;
896
1002
 
897
- border: 1px solid var(--tably-border, hsl(0, 0%, 90%));
898
- border-radius: var(--tably-radius, 0.25rem);
899
-
900
- max-height: 100%;
1003
+ border: 1px solid var(--tably-border);
1004
+ border-radius: var(--tably-radius);
901
1005
  }
902
1006
 
903
1007
  .headers {
1008
+ display: flex;
904
1009
  grid-area: headers;
905
1010
  z-index: 2;
906
1011
  overflow: hidden;
907
1012
  }
908
1013
 
909
- .headers > .column {
1014
+ .headers > tr > .column {
910
1015
  width: auto !important;
911
- border-bottom: 1px solid var(--tably-border, hsl(0, 0%, 90%));
1016
+ border-bottom: 1px solid var(--tably-border);
1017
+ }
1018
+ .headers > tr {
1019
+ > .column, > .context-col {
1020
+ border-bottom: 1px solid var(--tably-border);
1021
+ border-left: 1px solid var(--tably-border-grid);
1022
+ }
912
1023
  }
913
1024
 
914
1025
  .content {
@@ -918,21 +1029,21 @@
918
1029
  grid-area: rows;
919
1030
  scrollbar-width: thin;
920
1031
  overflow: auto;
921
- /* height: 100%; */
922
1032
  }
923
1033
 
924
1034
  .statusbar {
1035
+ display: flex;
925
1036
  grid-area: statusbar;
926
1037
  overflow: hidden;
927
- background-color: var(--tably-statusbar, hsl(0, 0%, 98%));
1038
+ background-color: var(--tably-statusbar);
928
1039
  }
929
1040
 
930
1041
  .statusbar > tr > .column {
931
- border-top: 1px solid var(--tably-border, hsl(0, 0%, 90%));
932
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
1042
+ border-top: 1px solid var(--tably-border);
1043
+ padding: calc(var(--tably-padding-y) / 2) 0;
933
1044
  }
934
1045
 
935
- .headers,
1046
+ .headers > tr,
936
1047
  .row,
937
1048
  .statusbar > tr {
938
1049
  position: relative;
@@ -942,25 +1053,29 @@
942
1053
 
943
1054
  & > .column {
944
1055
  display: flex;
945
- padding-left: var(--tably-padding-x, 1rem);
1056
+ padding-left: var(--tably-padding-x);
946
1057
  overflow: hidden;
947
1058
  }
948
1059
 
949
- & > *:last-child {
1060
+ & > *:last-child:not(.context-col) {
950
1061
  width: 100%;
951
- padding-right: var(--tably-padding-x, 1rem);
1062
+ padding-right: var(--tably-padding-x);
952
1063
  }
953
1064
  }
954
1065
 
955
- .row:first-child:not(.dragging) > * {
956
- padding-top: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
1066
+ .row > .column {
1067
+ background-color: var(--tably-bg);
1068
+ padding: var(--tably-padding-y) 0;
957
1069
  }
958
- .row:last-child:not(.dragging) > * {
959
- padding-bottom: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
1070
+
1071
+ :global(#runic-drag .row) {
1072
+ border: 1px solid var(--tably-border-grid);
1073
+ border-top: 2px solid var(--tably-border-grid);
960
1074
  }
961
1075
 
962
1076
  .row > * {
963
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
1077
+ border-left: 1px solid var(--tably-border-grid);
1078
+ border-bottom: 1px solid var(--tably-border-grid);
964
1079
  }
965
1080
 
966
1081
  .panel {
@@ -968,7 +1083,7 @@
968
1083
  grid-area: panel;
969
1084
  height: 100%;
970
1085
  overflow: hidden;
971
- border-left: 1px solid var(--tably-border, hsl(0, 0%, 90%));
1086
+ border-left: 1px solid var(--tably-border);
972
1087
 
973
1088
  z-index: 4;
974
1089
 
@@ -980,7 +1095,7 @@
980
1095
  width: min-content;
981
1096
  overflow: auto;
982
1097
  scrollbar-width: thin;
983
- padding: var(--tably-padding-y, 0.5rem) 0;
1098
+ padding: var(--tably-padding-y) 0;
984
1099
  }
985
1100
  }
986
1101
  </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; }
@@ -27,7 +28,8 @@ export class TableState {
27
28
  resizeable: this.#props.resizeable ?? true,
28
29
  reorderable: this.#props.reorderable ?? false,
29
30
  href: this.#props.href,
30
- select: this.#props.select ?? false
31
+ select: this.#props.select ?? false,
32
+ auto: this.#props.auto ?? false
31
33
  });
32
34
  add(state) {
33
35
  if (state instanceof ColumnState) {
@@ -14,4 +14,8 @@ type SetterRecord = Record<PropertyKey, [() => any, (v: any) => void]>;
14
14
  export declare function withSetters<T extends SetterRecord>(obj: T): T;
15
15
  export declare function fromProps<T extends AnyRecord, B extends SetterRecord>(props: T, boundProps?: B): Simplify<{ [K in keyof B]: ReturnType<B[K][0]>; } & { readonly [K in keyof T]: T[K]; }>;
16
16
  export declare function assignDescriptors<T extends AnyRecord, B extends AnyRecord>(target: T, source: B): T & B;
17
+ /** Capitalize by space */
18
+ export declare function capitalize(str: string): string;
19
+ /** Split words when going from lower case to uppercase; `someWords-split` -> `some Word Split...` */
20
+ export declare function segmentize(str: string): string;
17
21
  export {};
@@ -78,3 +78,27 @@ export function assignDescriptors(target, source) {
78
78
  }
79
79
  return target;
80
80
  }
81
+ /** Capitalize by space */
82
+ export function capitalize(str) {
83
+ let parts = str.split(' ');
84
+ let result = '';
85
+ for (let part of parts) {
86
+ result += part.charAt(0).toUpperCase() + part.slice(1) + ' ';
87
+ }
88
+ return result;
89
+ }
90
+ /** Split words when going from lower case to uppercase; `someWords-split` -> `some Word Split...` */
91
+ export function segmentize(str) {
92
+ let result = '';
93
+ for (let i = 0; i < str.length; i++) {
94
+ const char = str[i];
95
+ const prevChar = i > 0 ? str[i - 1] : '';
96
+ if ((char === '-' || char === char.toUpperCase()) && prevChar !== ' ' && prevChar !== prevChar.toUpperCase()) {
97
+ result += ' ';
98
+ if (char === '-')
99
+ continue;
100
+ }
101
+ result += char;
102
+ }
103
+ return result.trim();
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-tably",
3
- "version": "1.0.0-next.15",
3
+ "version": "1.0.0-next.17",
4
4
  "repository": "github:refzlund/svelte-tably",
5
5
  "homepage": "https://github.com/Refzlund/svelte-tably",
6
6
  "bugs": {