luna-plus 0.0.10 → 0.0.13

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.
@@ -67,14 +67,6 @@
67
67
  }
68
68
  };
69
69
 
70
- /** 键盘事件 */
71
- const handleKeyDown = (e: KeyboardEvent): void => {
72
- if (e.key === "Enter" || e.key === " ") {
73
- e.preventDefault();
74
- handleClose();
75
- }
76
- };
77
-
78
70
  /** ESC 键处理 */
79
71
  const handleEscKey = (e: KeyboardEvent): void => {
80
72
  if (closeOnEsc && e.key === "Escape") {
@@ -99,7 +91,6 @@
99
91
  <div
100
92
  class="lm-dialog-wrapper"
101
93
  onclick={handleModalClick}
102
- onkeydown={handleKeyDown}
103
94
  role="presentation"
104
95
  >
105
96
  {#if modal}
@@ -9,6 +9,17 @@
9
9
  CellClickEvent,
10
10
  SelectionChangeEvent,
11
11
  TableActionButton,
12
+ SpanMethodResult,
13
+ SelectEvent,
14
+ SelectAllEvent,
15
+ CellMouseEvent,
16
+ RowDblclickEvent,
17
+ RowContextmenuEvent,
18
+ HeaderClickEvent,
19
+ HeaderDragendEvent,
20
+ FilterChangeEvent,
21
+ ExpandChangeEvent,
22
+ ScrollEvent,
12
23
  } from "./types";
13
24
  import Checkbox from "../Checkbox/Checkbox.svelte";
14
25
  import Loading from "../Loading/Loading.svelte";
@@ -47,13 +58,43 @@
47
58
  showHeader = true,
48
59
  selectedRowKeys = [],
49
60
  loading = false,
61
+ // 新增属性
62
+ showSummary = false,
63
+ sumText = "合计",
64
+ summaryMethod,
65
+ spanMethod,
66
+ selectOnIndeterminate = true,
67
+ indent = 16,
68
+ lazy = false,
69
+ load,
70
+ treeProps = { hasChildren: "hasChildren", children: "children", checkStrictly: false },
71
+ tableLayout = "fixed",
72
+ scrollbarAlwaysOn = false,
73
+ flexible = false,
74
+ showOverflowTooltip: globalShowOverflowTooltip,
75
+ tooltipFormatter,
76
+ rowExpandable,
50
77
  class: cls = "",
51
78
  empty,
79
+ append,
80
+ // 事件
52
81
  onsortchange,
53
82
  onselectionchange,
54
83
  onrowclick,
55
84
  oncellclick,
56
85
  oncurrentchange,
86
+ onselect,
87
+ onselectall,
88
+ oncellmouseenter,
89
+ oncellmouseleave,
90
+ onrowdblclick,
91
+ onrowcontextmenu,
92
+ onheaderclick,
93
+ onheadercontextmenu,
94
+ onheaderdragend,
95
+ onfilterchange,
96
+ onexpandchange,
97
+ onscroll,
57
98
  ...attrs
58
99
  }: TableProps & {
59
100
  onsortchange?: (e: SortChangeEvent) => void;
@@ -61,6 +102,18 @@
61
102
  onrowclick?: (e: RowClickEvent) => void;
62
103
  oncellclick?: (e: CellClickEvent) => void;
63
104
  oncurrentchange?: (row: any) => void;
105
+ onselect?: (e: SelectEvent) => void;
106
+ onselectall?: (e: SelectAllEvent) => void;
107
+ oncellmouseenter?: (e: CellMouseEvent) => void;
108
+ oncellmouseleave?: (e: CellMouseEvent) => void;
109
+ onrowdblclick?: (e: RowDblclickEvent) => void;
110
+ onrowcontextmenu?: (e: RowContextmenuEvent) => void;
111
+ onheaderclick?: (e: HeaderClickEvent) => void;
112
+ onheadercontextmenu?: (e: HeaderClickEvent) => void;
113
+ onheaderdragend?: (e: HeaderDragendEvent) => void;
114
+ onfilterchange?: (e: FilterChangeEvent) => void;
115
+ onexpandchange?: (e: ExpandChangeEvent) => void;
116
+ onscroll?: (e: ScrollEvent) => void;
64
117
  } = $props();
65
118
 
66
119
  // State - use effects to sync with props
@@ -243,9 +296,9 @@
243
296
  newSelected.add(key);
244
297
  }
245
298
  selectedKeys = newSelected;
246
- onselectionchange?.({
247
- selection: data.filter((r, i) => selectedKeys.has(getRowKey(r, i))),
248
- });
299
+ const selection = data.filter((r, i) => selectedKeys.has(getRowKey(r, i)));
300
+ onselect?.({ selection, row });
301
+ onselectionchange?.({ selection });
249
302
  };
250
303
 
251
304
  // Handle select all
@@ -258,16 +311,17 @@
258
311
  selectedKeys.has(getRowKey(row, index)),
259
312
  );
260
313
 
261
- if (allSelected) {
314
+ // 处理 selectOnIndeterminate
315
+ if (allSelected || (!selectOnIndeterminate && isIndeterminate)) {
262
316
  selectedKeys = new Set();
263
317
  } else {
264
318
  selectedKeys = new Set(
265
319
  selectableRows.map((row, index) => getRowKey(row, index)),
266
320
  );
267
321
  }
268
- onselectionchange?.({
269
- selection: data.filter((r, i) => selectedKeys.has(getRowKey(r, i))),
270
- });
322
+ const selection = data.filter((r, i) => selectedKeys.has(getRowKey(r, i)));
323
+ onselectall?.({ selection });
324
+ onselectionchange?.({ selection });
271
325
  };
272
326
 
273
327
  // Check if all selected
@@ -303,6 +357,16 @@
303
357
  onrowclick?.({ row, column, event });
304
358
  };
305
359
 
360
+ // Handle row double click
361
+ const handleRowDblclick = (row: any, column: TableColumn, event: MouseEvent) => {
362
+ onrowdblclick?.({ row, column, event });
363
+ };
364
+
365
+ // Handle row context menu
366
+ const handleRowContextmenu = (row: any, column: TableColumn, event: MouseEvent) => {
367
+ onrowcontextmenu?.({ row, column, event });
368
+ };
369
+
306
370
  // Handle cell click
307
371
  const handleCellClick = (
308
372
  row: any,
@@ -313,6 +377,16 @@
313
377
  oncellclick?.({ row, column, cell, event });
314
378
  };
315
379
 
380
+ // Handle header click
381
+ const handleHeaderClick = (column: TableColumn, event: MouseEvent) => {
382
+ onheaderclick?.({ column, event });
383
+ };
384
+
385
+ // Handle header context menu
386
+ const handleHeaderContextmenu = (column: TableColumn, event: MouseEvent) => {
387
+ onheadercontextmenu?.({ column, event });
388
+ };
389
+
316
390
  // Get row class
317
391
  const getRowClass = (row: any, index: number): string => {
318
392
  const classes: string[] = ["lm-table__row"];
@@ -434,6 +508,11 @@
434
508
  return element.scrollWidth > element.clientWidth;
435
509
  };
436
510
 
511
+ // 获取单元格是否显示 tooltip(支持全局和列级配置)
512
+ const shouldShowOverflowTooltip = (column: TableColumn): boolean => {
513
+ return column.showOverflowTooltip ?? globalShowOverflowTooltip ?? false;
514
+ };
515
+
437
516
  const handleCellMouseEnter = (
438
517
  event: MouseEvent,
439
518
  row: any,
@@ -441,16 +520,25 @@
441
520
  rowIndex: number,
442
521
  colIndex: number,
443
522
  ) => {
444
- if (!column.showOverflowTooltip) return;
445
-
446
523
  const target = event.currentTarget as HTMLElement;
524
+
525
+ // 触发单元格鼠标进入事件
526
+ oncellmouseenter?.({ row, column, cell: target, event });
527
+
528
+ if (!shouldShowOverflowTooltip(column)) return;
447
529
  const cellContent = target.querySelector(
448
530
  ".lm-table__cell-text",
449
531
  ) as HTMLElement;
450
532
 
451
533
  if (!cellContent || !checkOverflow(cellContent)) return;
452
534
 
453
- const content = formatCellValue(row, column, rowIndex);
535
+ // 支持 tooltipFormatter
536
+ let content: string | number;
537
+ if (tooltipFormatter) {
538
+ content = tooltipFormatter(row, column, getCellValue(row, column), rowIndex);
539
+ } else {
540
+ content = formatCellValue(row, column, rowIndex);
541
+ }
454
542
  if (!content) return;
455
543
 
456
544
  // Clear existing timeout
@@ -474,7 +562,15 @@
474
562
  }, 300);
475
563
  };
476
564
 
477
- const handleCellMouseLeave = () => {
565
+ const handleCellMouseLeaveInternal = (
566
+ event: MouseEvent,
567
+ row: any,
568
+ column: TableColumn,
569
+ ) => {
570
+ const target = event.currentTarget as HTMLElement;
571
+ // 触发单元格鼠标离开事件
572
+ oncellmouseleave?.({ row, column, cell: target, event });
573
+
478
574
  if (tooltipTimeout) {
479
575
  clearTimeout(tooltipTimeout);
480
576
  tooltipTimeout = null;
@@ -483,9 +579,56 @@
483
579
  hoveredTooltipCell = null;
484
580
  };
485
581
 
582
+ // 计算合计行
583
+ const summaryData = $derived.by(() => {
584
+ if (!showSummary) return [];
585
+
586
+ if (summaryMethod) {
587
+ return summaryMethod({ columns, data: sortedData });
588
+ }
589
+
590
+ // 默认合计方法
591
+ return columns.map((column, index) => {
592
+ if (index === 0) return sumText;
593
+ if (!column.prop) return "";
594
+
595
+ const values = sortedData.map(row => getCellValue(row, column));
596
+ const numericValues = values.filter(v => !isNaN(Number(v)));
597
+
598
+ if (numericValues.length === 0) return "";
599
+
600
+ const sum = numericValues.reduce((acc, val) => acc + Number(val), 0);
601
+ return sum;
602
+ });
603
+ });
604
+
605
+ // 获取单元格合并信息
606
+ const getSpanInfo = (row: any, column: TableColumn, rowIndex: number, colIndex: number): { rowspan: number; colspan: number } => {
607
+ if (!spanMethod) return { rowspan: 1, colspan: 1 };
608
+
609
+ const result = spanMethod({ row, column, rowIndex, columnIndex: colIndex });
610
+
611
+ if (!result) return { rowspan: 1, colspan: 1 };
612
+
613
+ if (Array.isArray(result)) {
614
+ return { rowspan: result[0], colspan: result[1] };
615
+ }
616
+
617
+ return { rowspan: result.rowspan ?? 1, colspan: result.colspan ?? 1 };
618
+ };
619
+
620
+ // 处理滚动事件
621
+ const handleScroll = (event: Event) => {
622
+ const target = event.target as HTMLElement;
623
+ onscroll?.({
624
+ scrollLeft: target.scrollLeft,
625
+ scrollTop: target.scrollTop,
626
+ });
627
+ };
628
+
486
629
  // Table classes
487
630
  const tableClasses = $derived(
488
- `lm-table${border ? " lm-table--border" : ""}${stripe ? " lm-table--striped" : ""}${size !== "default" ? ` lm-table--${size}` : ""}${fit ? " lm-table--fit" : ""}${highlightCurrentRow ? " lm-table--highlight-current-row" : ""}${loading ? " is-loading" : ""}${cls ? ` ${cls}` : ""}`,
631
+ `lm-table${border ? " lm-table--border" : ""}${stripe ? " lm-table--striped" : ""}${size !== "default" ? ` lm-table--${size}` : ""}${fit ? " lm-table--fit" : ""}${highlightCurrentRow ? " lm-table--highlight-current-row" : ""}${loading ? " is-loading" : ""}${flexible ? " lm-table--flexible" : ""}${scrollbarAlwaysOn ? " lm-table--scrollbar-always-on" : ""}${cls ? ` ${cls}` : ""}`,
489
632
  );
490
633
 
491
634
  // Table style
@@ -514,8 +657,8 @@
514
657
 
515
658
  <div {...attrs} bind:this={tableRef} class={tableClasses} style={tableStyle}>
516
659
  <Loading visible={loading} lock={false}>
517
- <div class="lm-table__inner-wrapper">
518
- <table class="lm-table__table" cellspacing="0" cellpadding="0" border="0">
660
+ <div class="lm-table__inner-wrapper" onscroll={handleScroll}>
661
+ <table class="lm-table__table" style:table-layout={tableLayout} cellspacing="0" cellpadding="0" border="0">
519
662
  <colgroup>
520
663
  {#each columns as column}
521
664
  <col style={getColumnWidthStyle(column)} />
@@ -527,7 +670,11 @@
527
670
  {#each columns as column, colIndex}
528
671
  <th
529
672
  class={getHeaderCellClass(column, colIndex)}
530
- onclick={() => handleSort(column)}
673
+ onclick={(e) => {
674
+ handleSort(column);
675
+ handleHeaderClick(column, e);
676
+ }}
677
+ oncontextmenu={(e) => handleHeaderContextmenu(column, e)}
531
678
  >
532
679
  <div class="lm-table__cell-wrapper">
533
680
  {#if column.type === "selection"}
@@ -588,98 +735,126 @@
588
735
  <tr
589
736
  class={getRowClass(row, rowIndex)}
590
737
  style={getRowStyleString(row, rowIndex)}
738
+ ondblclick={(e) => handleRowDblclick(row, columns[0], e)}
739
+ oncontextmenu={(e) => handleRowContextmenu(row, columns[0], e)}
591
740
  >
592
741
  {#each columns as column, colIndex}
593
- <td
594
- class={getCellClass(row, column, rowIndex, colIndex)}
595
- onclick={(e) => {
596
- handleRowClick(row, column, e);
597
- handleCellClick(
598
- row,
599
- column,
600
- e.currentTarget as HTMLElement,
601
- e,
602
- );
603
- }}
604
- onmouseenter={(e) =>
605
- handleCellMouseEnter(e, row, column, rowIndex, colIndex)}
606
- onmouseleave={handleCellMouseLeave}
607
- >
608
- <div
609
- class="lm-table__cell-wrapper"
610
- class:has-overflow-tooltip={column.showOverflowTooltip}
611
- >
612
- {#if column.type === "selection"}
613
- {@const selectable =
614
- !column.selectable ||
615
- column.selectable(row, rowIndex)}
616
- <Checkbox
617
- checked={selectedKeys.has(getRowKey(row, rowIndex))}
618
- disabled={!selectable}
619
- onchange={() => handleSelect(row, rowIndex)}
620
- size="small"
621
- />
622
- {:else if column.type === "index"}
623
- <span class="lm-table__cell-text"
624
- >{getIndexValue(column, rowIndex)}</span
625
- >
626
- {:else if column.type === "actions"}
627
- {@const visibleActions = getVisibleActions(
628
- column.actions,
629
- row,
630
- rowIndex,
631
- )}
632
- <Space size="small">
633
- {#each visibleActions as action, actionIndex (actionIndex)}
634
- <Button
635
- type={action.type || "primary"}
636
- plain
637
- size="small"
638
- disabled={isActionDisabled(action, row, rowIndex)}
639
- onclick={(e: MouseEvent) => {
640
- e.stopPropagation();
641
- action.onClick?.(row, rowIndex);
642
- }}
643
- >
644
- {action.label}
645
- </Button>
646
- {/each}
647
- </Space>
648
- {:else if column.render}
649
- {@render column.render({
742
+ {@const spanInfo = getSpanInfo(row, column, rowIndex, colIndex)}
743
+ {#if spanInfo.rowspan !== 0 && spanInfo.colspan !== 0}
744
+ <td
745
+ class={getCellClass(row, column, rowIndex, colIndex)}
746
+ rowspan={spanInfo.rowspan > 1 ? spanInfo.rowspan : undefined}
747
+ colspan={spanInfo.colspan > 1 ? spanInfo.colspan : undefined}
748
+ onclick={(e) => {
749
+ handleRowClick(row, column, e);
750
+ handleCellClick(
650
751
  row,
651
752
  column,
652
- $index: rowIndex,
653
- cellValue: getCellValue(row, column),
654
- })}
655
- {:else if column.cellComponent}
656
- {@const CellComp = column.cellComponent}
657
- <span
658
- class="lm-table__cell-text"
659
- class:is-ellipsis={column.showOverflowTooltip}
660
- >
661
- <CellComp
662
- {...getCellComponentProps(row, column, rowIndex)}
753
+ e.currentTarget as HTMLElement,
754
+ e,
755
+ );
756
+ }}
757
+ onmouseenter={(e) =>
758
+ handleCellMouseEnter(e, row, column, rowIndex, colIndex)}
759
+ onmouseleave={(e) =>
760
+ handleCellMouseLeaveInternal(e, row, column)}
761
+ >
762
+ <div
763
+ class="lm-table__cell-wrapper"
764
+ class:has-overflow-tooltip={shouldShowOverflowTooltip(column)}
765
+ >
766
+ {#if column.type === "selection"}
767
+ {@const selectable =
768
+ !column.selectable ||
769
+ column.selectable(row, rowIndex)}
770
+ <Checkbox
771
+ checked={selectedKeys.has(getRowKey(row, rowIndex))}
772
+ disabled={!selectable}
773
+ onchange={() => handleSelect(row, rowIndex)}
774
+ size="small"
775
+ />
776
+ {:else if column.type === "index"}
777
+ <span class="lm-table__cell-text"
778
+ >{getIndexValue(column, rowIndex)}</span
779
+ >
780
+ {:else if column.type === "actions"}
781
+ {@const visibleActions = getVisibleActions(
782
+ column.actions,
783
+ row,
784
+ rowIndex,
785
+ )}
786
+ <Space size="small">
787
+ {#each visibleActions as action, actionIndex (actionIndex)}
788
+ <Button
789
+ type={action.type || "primary"}
790
+ plain
791
+ size="small"
792
+ disabled={isActionDisabled(action, row, rowIndex)}
793
+ onclick={(e: MouseEvent) => {
794
+ e.stopPropagation();
795
+ action.onClick?.(row, rowIndex);
796
+ }}
797
+ >
798
+ {action.label}
799
+ </Button>
800
+ {/each}
801
+ </Space>
802
+ {:else if column.render}
803
+ {@render column.render({
804
+ row,
805
+ column,
806
+ $index: rowIndex,
807
+ cellValue: getCellValue(row, column),
808
+ })}
809
+ {:else if column.cellComponent}
810
+ {@const CellComp = column.cellComponent}
811
+ <span
812
+ class="lm-table__cell-text"
813
+ class:is-ellipsis={shouldShowOverflowTooltip(column)}
814
+ >
815
+ <CellComp
816
+ {...getCellComponentProps(row, column, rowIndex)}
817
+ >
818
+ {formatCellValue(row, column, rowIndex)}
819
+ </CellComp>
820
+ </span>
821
+ {:else}
822
+ <span
823
+ class="lm-table__cell-text"
824
+ class:is-ellipsis={shouldShowOverflowTooltip(column)}
663
825
  >
664
826
  {formatCellValue(row, column, rowIndex)}
665
- </CellComp>
666
- </span>
667
- {:else}
668
- <span
669
- class="lm-table__cell-text"
670
- class:is-ellipsis={column.showOverflowTooltip}
671
- >
672
- {formatCellValue(row, column, rowIndex)}
673
- </span>
674
- {/if}
675
- </div>
676
- </td>
827
+ </span>
828
+ {/if}
829
+ </div>
830
+ </td>
831
+ {/if}
677
832
  {/each}
678
833
  </tr>
679
834
  {/each}
680
835
  {/if}
681
836
  </tbody>
837
+ {#if showSummary && sortedData.length > 0}
838
+ <tfoot class="lm-table__footer">
839
+ <tr class="lm-table__row">
840
+ {#each columns as column, colIndex}
841
+ <td class="lm-table__cell lm-table__summary-cell">
842
+ <div class="lm-table__cell-wrapper">
843
+ <span class="lm-table__cell-text">
844
+ {summaryData[colIndex] ?? ""}
845
+ </span>
846
+ </div>
847
+ </td>
848
+ {/each}
849
+ </tr>
850
+ </tfoot>
851
+ {/if}
682
852
  </table>
853
+ {#if append}
854
+ <div class="lm-table__append-wrapper">
855
+ {@render append()}
856
+ </div>
857
+ {/if}
683
858
  </div>
684
859
  </Loading>
685
860
 
@@ -1,10 +1,39 @@
1
- import type { TableProps, SortChangeEvent, RowClickEvent, CellClickEvent, SelectionChangeEvent } from './types';
1
+ import type {
2
+ TableProps,
3
+ SortChangeEvent,
4
+ RowClickEvent,
5
+ CellClickEvent,
6
+ SelectionChangeEvent,
7
+ SelectEvent,
8
+ SelectAllEvent,
9
+ CellMouseEvent,
10
+ RowDblclickEvent,
11
+ RowContextmenuEvent,
12
+ HeaderClickEvent,
13
+ HeaderDragendEvent,
14
+ FilterChangeEvent,
15
+ ExpandChangeEvent,
16
+ ScrollEvent
17
+ } from './types';
18
+
2
19
  type $$ComponentProps = TableProps & {
3
20
  onsortchange?: (e: SortChangeEvent) => void;
4
21
  onselectionchange?: (e: SelectionChangeEvent) => void;
5
22
  onrowclick?: (e: RowClickEvent) => void;
6
23
  oncellclick?: (e: CellClickEvent) => void;
7
24
  oncurrentchange?: (row: any) => void;
25
+ onselect?: (e: SelectEvent) => void;
26
+ onselectall?: (e: SelectAllEvent) => void;
27
+ oncellmouseenter?: (e: CellMouseEvent) => void;
28
+ oncellmouseleave?: (e: CellMouseEvent) => void;
29
+ onrowdblclick?: (e: RowDblclickEvent) => void;
30
+ onrowcontextmenu?: (e: RowContextmenuEvent) => void;
31
+ onheaderclick?: (e: HeaderClickEvent) => void;
32
+ onheadercontextmenu?: (e: HeaderClickEvent) => void;
33
+ onheaderdragend?: (e: HeaderDragendEvent) => void;
34
+ onfilterchange?: (e: FilterChangeEvent) => void;
35
+ onexpandchange?: (e: ExpandChangeEvent) => void;
36
+ onscroll?: (e: ScrollEvent) => void;
8
37
  };
9
38
  declare const Table: import("svelte").Component<$$ComponentProps, {}, "">;
10
39
  type Table = ReturnType<typeof Table>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luna-plus",
3
- "version": "0.0.10",
3
+ "version": "0.0.13",
4
4
  "description": "A modern Svelte 5 component library with 60+ components",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",