intable 0.0.38 → 0.0.40

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
@@ -1,10 +1,97 @@
1
+ # Intable
2
+
3
+ > 🎯 一个组件搞定所有表格场景 — SolidJS / React / Vue 框架通用
4
+
5
+ [![npm](https://img.shields.io/npm/v/intable)](https://www.npmjs.com/package/intable)
6
+ [![license](https://img.shields.io/github/license/huodoushigemi/intable)](LICENSE)
7
+ [![stars](https://img.shields.io/github/stars/huodoushigemi/intable)](https://github.com/huodoushigemi/intable/stargazers)
8
+
1
9
  ---
2
- name: intable
3
- description: A high-performance, plugin-based Excel-like table component for SolidJS with virtual scroll, cell editing, validation, copy/paste, row grouping, tree data, column/row drag, merge cells, diff view, and multi-framework support (Vue, React).
10
+
11
+ ## [🚀 在线体验 Demo](https://huodoushigemi.github.io/intable)
12
+
4
13
  ---
5
14
 
6
- # intable
15
+ ## 🎯 核心功能一览
7
16
 
8
- ## [🚀 在线体验 Demo](https://huodoushigemi.github.io/intable)
17
+ - **虚拟滚动** — 百万行数据流畅运行
18
+ - ✅ **单元格编辑** — text / number / date / select / checkbox / range / color
19
+ - ✅ **列筛选排序** — 内置 FilterPlugin / SortPlugin
20
+ - ✅ **Excel 复制粘贴** — 多单元格选区 TSV 兼容
21
+ - ✅ **撤销重做** — Ctrl+Z / Ctrl+Y
22
+ - ✅ **Excel 导入导出** — 一键导出 xlsx
23
+ - ✅ **数据校验** — Zod schema 集成
24
+ - ✅ **变更比对** — DiffPlugin 高亮追踪
25
+
26
+ ---
27
+
28
+ ## 🤖 AI 时代的工作流
29
+
30
+ **安装 Skill:**
31
+
32
+ ```bash
33
+ npx skills add huodoushigemi/intable -y --skill intable
34
+ ```
35
+
36
+ **效果:** 描述需求(如"加一个大数据量的可编辑表格"),Copilot 自动给出正确代码。
37
+
38
+ ---
39
+
40
+ 📈 真实项目对比,以「用户管理」模块为例:
41
+
42
+ | 指标 | 使用前 | 使用后 |
43
+ |------|--------|--------|
44
+ | 代码行数 | 500+ | **200-** |
45
+ | AI 消耗 | 100% | **<70%** |
46
+
47
+
48
+ ---
49
+
50
+ ## 🚀 快速开始
51
+
52
+ ```bash
53
+ # SolidJS
54
+ pnpm add intable
55
+
56
+ # React
57
+ pnpm add @intable/react
58
+
59
+ # Vue 3
60
+ pnpm add @intable/vue
61
+ ```
62
+
63
+ **创建一个表格,只需几行代码:**
64
+
65
+ ```tsx
66
+ import Intable from 'intable'
67
+ // import Intable from '@intable/react'
68
+ // import Intable from '@intable/vue'
69
+
70
+ <Intable
71
+ columns={[
72
+ { id: 'name', name: '姓名', width: 120 },
73
+ { id: 'age', name: '年龄', width: 80 },
74
+ ]}
75
+ data={[
76
+ { id: 1, name: 'Alice', age: 28 },
77
+ { id: 2, name: 'Bob', age: 32 },
78
+ ]}
79
+ border
80
+ />
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 🌟 为什么选择 Intable
86
+
87
+ 1. **插件化架构** — 责任链 `rewriteProps` 管道,插件完全可组合、可替换
88
+ 2. **框架通用** — SolidJS / React / Vue 共享同一套插件 API
89
+ 3. **性能优先** — 虚拟滚动,行/列双向渲染,百万行不卡顿
90
+ 4. **AI 集成** — Copilot Skill 让 AI 写出正确用法
91
+ 5. **渐进增强** — 从简单表格到复杂企业级需求,平滑升级
92
+
93
+ ---
94
+
95
+ ## License
9
96
 
10
- ## [📃 文档 Docs](https://github.com/huodoushigemi/intable/blob/main/.github/instructions/intable-scenarios.instructions.md)
97
+ MIT
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ import './plugins/AggregatePlugin';
21
21
  import './plugins/AutoFillPlugin';
22
22
  import './plugins/FilterPlugin';
23
23
  import './plugins/TooltipPlugin';
24
+ import './plugins/KeyEachPlugin';
24
25
  export declare const Ctx: import("solid-js").Context<{
25
26
  props: TableProps2;
26
27
  store: TableStore;
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ import { AutoFillPlugin } from "./plugins/AutoFillPlugin.js";
23
23
  import { FilterPlugin } from "./plugins/FilterPlugin.js";
24
24
  import { ImportExportPlugin } from "./plugins/ImportExportPlugin.js";
25
25
  import { TooltipPlugin } from "./plugins/TooltipPlugin.js";
26
+ import { KeyEachPlugin } from "./plugins/KeyEachPlugin.js";
26
27
  import loading_bold_default from "./loading-bold.js";
27
28
  import { createComponent, insert, memo, mergeProps, spread, template, use } from "solid-js/web";
28
29
  import { $PROXY, For, batch, createComputed, createContext, createEffect, createMemo, createRenderEffect, createSignal, getOwner, mapArray, mergeProps as mergeProps$1, on, onCleanup, onMount, runWithOwner, untrack, useContext } from "solid-js";
@@ -426,6 +427,7 @@ const defaultsPlugins = [
426
427
  AggregatePlugin,
427
428
  AutoFillPlugin,
428
429
  ImportExportPlugin,
429
- TooltipPlugin
430
+ TooltipPlugin,
431
+ KeyEachPlugin
430
432
  ];
431
433
  export { Ctx, Intable, src_default as default, defaultsPlugins };
@@ -3,7 +3,7 @@ import { Checkbox, Files } from "./RenderPlugin/components.js";
3
3
  import Textarea_default from "../components/Textarea.js";
4
4
  import { Ctx } from "../index.js";
5
5
  import { addEventListener, createComponent, delegateEvents, effect, insert, memo, mergeProps, spread, template, use } from "solid-js/web";
6
- import { createComputed, createEffect, createMemo, createRoot, createSignal, on, onCleanup, useContext } from "solid-js";
6
+ import { createEffect, createMemo, createRoot, createSignal, on, onCleanup, useContext } from "solid-js";
7
7
  import { createMutable } from "solid-js/store";
8
8
  import { combineProps } from "@solid-primitives/props";
9
9
  import { createAsyncMemo } from "@solid-primitives/memo";
@@ -13,112 +13,106 @@ const EditablePlugin = {
13
13
  name: "editable",
14
14
  store: () => ({ editors: { ...editors } }),
15
15
  rewriteProps: {
16
- editable: ({ editable: e }, { store: y }) => (x) => {
17
- let S = [x.col.editable, e].filter((e) => e != null);
18
- return !!S.length && S.every((e) => unFn(e, x)) && !x.data[y.internal] && !x.col[y.internal];
16
+ editable: ({ editable: e }, { store: _ }) => (y) => {
17
+ let b = [y.col.editable, e].filter((e) => e != null);
18
+ return !!b.length && b.every((e) => unFn(e, y)) && !y.data[_.internal] && !y.col[_.internal];
19
19
  },
20
- Td: ({ Td: e }, { store: y }) => (x) => {
21
- let S, { props: C } = useContext(Ctx), [E, D] = createSignal(!1), k = "", A = () => unFn(C.editable, x), M = () => (([e, y]) => x.x == e && x.y == y)(y.selected.start || []), N = () => M() && A() && !E(), [P, F] = createSignal(!1), I = createAsyncMemo(() => {
22
- if (E()) {
23
- let e = !1, b = x.data[x.col.id], S = ((e) => typeof e == "string" ? y.editors[e] : e)(x.col.editor ?? x.col.type ?? (x.col.enum ? "select" : "text")), w = {
24
- props: x.col.editorProps,
25
- col: x.col,
26
- eventKey: k,
27
- data: x.data,
28
- value: b,
20
+ Td: ({ Td: e }, { store: _ }) => (y) => {
21
+ let b, { props: x } = useContext(Ctx), [T, E] = createSignal(!1), O = "", k = () => unFn(x.editable, y), j = () => (([e, _]) => y.x == e && y.y == _)(_.selected.start || []), M = () => j() && k() && !T(), [N, P] = createSignal(!1), F = createAsyncMemo(() => {
22
+ if (T()) {
23
+ let e = createMemo(() => JSON.stringify(_.selected ?? "{}"));
24
+ createEffect(on(e, () => E(!1), { defer: !0 })), R.w = b.getBoundingClientRect().width, R.h = b.getBoundingClientRect().height;
25
+ let v = !1, S = y.data[y.col.id], C = ((e) => typeof e == "string" ? _.editors[e] : e)(y.col.editor ?? y.col.type ?? (y.col.enum ? "select" : "text")), w = {
26
+ props: y.col.editorProps,
27
+ col: y.col,
28
+ eventKey: O,
29
+ data: y.data,
30
+ value: S,
29
31
  ok: async () => {
30
- await L(T.getValue()), D(!1);
32
+ await I(D.getValue()), E(!1);
31
33
  },
32
- cancel: () => (e = !0, D(!1)),
33
- onChange: (e) => E() && L(e).catch(() => {})
34
- }, T = S(w);
35
- return onCleanup(() => {
36
- !e && T.getValue() !== b && y.commands.rowChange({
37
- ...C.data[x.y],
38
- [x.col.id]: T.getValue()
39
- }), e || L(T.getValue()).catch(() => {}), k = "", T.destroy();
40
- }), [w, T];
34
+ cancel: () => (v = !0, E(!1)),
35
+ onChange: (e) => T() && I(e).catch(() => {})
36
+ }, D = C(w);
37
+ return queueMicrotask(() => D.focus?.()), onCleanup(() => {
38
+ !v && D.getValue() !== S && _.commands.rowChange({
39
+ ...x.data[y.y],
40
+ [y.col.id]: D.getValue()
41
+ }), v || I(D.getValue()).catch(() => {}), O = "", D.destroy();
42
+ }), [w, D];
41
43
  }
42
44
  });
43
- async function L(e) {
45
+ async function I(e) {
44
46
  try {
45
- F(!0), await y.validateCell(e, x.data, x.col);
47
+ P(!0), await _.validateCell(e, y.data, y.col);
46
48
  } finally {
47
- F(!1);
49
+ P(!1);
48
50
  }
49
51
  }
50
- createEffect(() => {
51
- I()?.[1]?.focus?.();
52
- }), createEffect(() => {
53
- if (E()) {
54
- let e = createMemo(() => JSON.stringify(y.selected ?? "{}"));
55
- createEffect(on(e, () => D(!1), { defer: !0 }));
56
- }
57
- });
58
- let R, z = createMutable({
52
+ let L, R = createMutable({
59
53
  w: 0,
60
54
  h: 0
61
55
  });
62
- return createComputed(() => E() && (z.w = S.getBoundingClientRect().width, z.h = S.getBoundingClientRect().height)), x = combineProps(x, {
63
- ref: (e) => S = e,
56
+ return y = combineProps(y, {
57
+ ref: (e) => b = e,
64
58
  get class() {
65
- return E() ? "is-editing" : "";
59
+ return T() ? "is-editing" : "";
66
60
  },
67
61
  get style() {
68
- return E() ? `width: ${z.w}px; height: ${z.h}px; padding: 0; ` : "";
62
+ return T() ? `width: ${R.w}px; height: ${R.h}px; padding: 0; ` : "";
69
63
  },
70
- onClick: () => R?.focus?.(),
71
- onDblClick: () => D(A()),
72
- onKeyDown: (e) => e.key == "Escape" && I()?.[0].cancel()
73
- }), createComponent(e, mergeProps(x, { get children() {
74
- return [memo(() => memo(() => !!I()?.[1]?.el)() ? (() => {
64
+ onClick: () => L?.focus?.(),
65
+ onDblClick: () => E(k()),
66
+ onKeyDown: (e) => e.key == "Escape" && F()?.[0].cancel()
67
+ }), createComponent(e, mergeProps(y, { get children() {
68
+ return [memo(() => memo(() => !!F()?.[1]?.el)() ? (() => {
75
69
  var e = _tmpl$();
76
70
  return addEventListener(e, "keydown", (e) => {
77
- e.stopPropagation(), e.key == "Enter" && !e.shiftKey && (I()?.[0].ok(), e.preventDefault()), e.key == "Escape" && I()?.[0].cancel();
78
- }), addEventListener(e, "pointerdown", (e) => e.stopPropagation()), insert(e, () => I()?.[1]?.el, null), insert(e, (() => {
79
- var e = memo(() => !!P());
71
+ e.stopPropagation(), e.key == "Enter" && !e.shiftKey && (F()?.[0].ok(), e.preventDefault()), e.key == "Escape" && F()?.[0].cancel();
72
+ }), addEventListener(e, "pointerdown", (e) => e.stopPropagation()), insert(e, () => F()?.[1]?.el, null), insert(e, (() => {
73
+ var e = memo(() => !!N());
80
74
  return () => e() && _tmpl$2();
81
75
  })(), null), e;
82
- })() : x.children), memo(() => memo(() => !!N())() && (() => {
76
+ })() : y.children), memo(() => memo(() => !!M())() && (() => {
83
77
  var e = _tmpl$3();
84
78
  return e.addEventListener("compositionend", () => {
85
- D(!0);
79
+ E(!0);
86
80
  }), e.$$input = (e) => {
87
- k = e.target.value, D(!e.isComposing);
81
+ O = e.target.value, E(!e.isComposing);
88
82
  }, e.$$keydown = (e) => {
89
83
  e.key == " " && e.preventDefault();
90
84
  }, use((e) => {
91
- R = e, delay(0).then(() => e.focus({ preventScroll: !0 }));
85
+ L = e, delay(0).then(() => e.focus({ preventScroll: !0 }));
92
86
  }, e), e;
93
87
  })())];
94
88
  } }));
95
89
  }
96
90
  }
97
91
  };
98
- var createEditor = (e, b, x) => ({ eventKey: S, value: C, col: w, ok: T, cancel: E, props: D, onChange: O }) => createRoot((E) => {
99
- let [k, A] = createSignal(S || C), j;
92
+ var createEditor = (e, v, y) => ({ eventKey: b, value: x, col: S, ok: C, cancel: T, props: E, onChange: D }) => createRoot((T) => {
93
+ let [O, k] = createSignal(b || x), A;
100
94
  return createComponent(e, mergeProps({
101
- ref: (e) => j = e,
95
+ ref: (e) => A = e,
102
96
  class: "relative block px-2 size-full z-9 box-border resize-none outline-0",
103
97
  get value() {
104
- return k();
98
+ return O();
105
99
  },
106
- onInput: (e) => (A(e instanceof Event ? e.target.value : e), O?.(k())),
107
- onChange: (e) => (A(e instanceof Event ? e.target.value : e), O?.(k()), x && T()),
100
+ onInput: (e) => (k(e instanceof Event ? e.target.value : e), D?.(O())),
101
+ onChange: (e) => (k(e instanceof Event ? e.target.value : e), D?.(O()), y && C()),
108
102
  get options() {
109
- return memo(() => !!w.enum)() ? resolveOptions(w.enum ?? []) : void 0;
103
+ return memo(() => !!S.enum)() ? resolveOptions(S.enum ?? []) : void 0;
110
104
  }
111
- }, b, D)), setTimeout(() => {
112
- x && j?.showPicker?.();
105
+ }, v, E)), setTimeout(() => {
106
+ y && A?.showPicker?.();
113
107
  }, 0), {
114
- el: j,
115
- getValue: k,
116
- focus: () => j.focus(),
117
- destroy: E
108
+ el: A,
109
+ getValue: O,
110
+ focus: () => A.focus(),
111
+ destroy: T
118
112
  };
119
113
  }), Input = (e) => (() => {
120
- var y = _tmpl$4();
121
- return spread(y, e, !1, !1), y;
114
+ var _ = _tmpl$4();
115
+ return spread(_, e, !1, !1), _;
122
116
  })();
123
117
  const editors = {
124
118
  text: createEditor(Input),
@@ -136,27 +130,27 @@ const editors = {
136
130
  color: createEditor(Input, { type: "color" }),
137
131
  tel: createEditor(Input, { type: "tel" }),
138
132
  password: createEditor(Input, { type: "password" }),
139
- file: createEditor((y) => createComponent(Files, mergeProps(y, {
133
+ file: createEditor((_) => createComponent(Files, mergeProps(_, {
140
134
  class: "relative z-9 outline-2 outline-blue min-h-a! h-a! p-1 bg-#fff",
141
- onAdd: () => chooseFile({ multiple: !0 }).then((e) => y.onChange([...y.value || [], ...e.map((e) => ({
135
+ onAdd: () => chooseFile({ multiple: !0 }).then((e) => _.onChange([..._.value || [], ...e.map((e) => ({
142
136
  name: e.name,
143
137
  size: e.size
144
138
  }))]))
145
139
  }))),
146
140
  checkbox: createEditor((e) => (() => {
147
- var y = _tmpl$5(), b = e.ref;
148
- return typeof b == "function" ? use(b, y) : e.ref = y, insert(y, createComponent(Checkbox, mergeProps(e, {
141
+ var _ = _tmpl$5(), v = e.ref;
142
+ return typeof v == "function" ? use(v, _) : e.ref = _, insert(_, createComponent(Checkbox, mergeProps(e, {
149
143
  ref: () => {},
150
144
  onInput: () => {},
151
145
  class: "mx-3!"
152
- }))), y;
146
+ }))), _;
153
147
  })()),
154
148
  select: createEditor((e) => (() => {
155
- var y = _tmpl$6();
156
- return y.firstChild, spread(y, e, !1, !0), insert(y, () => e.options?.map((y) => (() => {
157
- var b = _tmpl$7();
158
- return insert(b, () => y.label), effect(() => b.selected = y.value === e.value), effect(() => b.value = y.value), b;
159
- })()), null), y;
149
+ var _ = _tmpl$6();
150
+ return _.firstChild, spread(_, e, !1, !0), insert(_, () => e.options?.map((_) => (() => {
151
+ var v = _tmpl$7();
152
+ return insert(v, () => _.label), effect(() => v.selected = _.value === e.value), effect(() => v.value = _.value), v;
153
+ })()), null), _;
160
154
  })(), {}, !0)
161
155
  };
162
156
  delegateEvents(["keydown", "input"]);
@@ -32,7 +32,7 @@ const ExpandPlugin = {
32
32
  }, h)), effect(() => setAttribute(m, "colspan", h.props.columns?.length)), m;
33
33
  })();
34
34
  } })) : m,
35
- Td: ({ Td: e }, { store: m }) => (h) => (h = combineProps(h, { onClick: () => h.col.id == m.expandCol.id && m.commands.expand.toggle(h.data) }), createComponent(e, h)),
35
+ Td: ({ Td: e }, { store: m }) => m.props.expand?.enable ? (h) => (h = combineProps(h, { onClick: () => h.col.id == m.expandCol.id && m.commands.expand.toggle(h.data) }), createComponent(e, h)) : e,
36
36
  data: ({ data: e }, { store: m }) => m.commands.expand.value.length ? e?.flatMap((e) => m.commands.expand.has(e) ? [e, { [m.expandCol.id]: e }] : e) : e
37
37
  }
38
38
  };
@@ -0,0 +1,15 @@
1
+ import type { Plugin } from '..';
2
+ declare module '..' {
3
+ interface TableProps {
4
+ useRowKey?: boolean;
5
+ useColKey?: boolean;
6
+ }
7
+ interface TableColumn {
8
+ }
9
+ interface TableStore {
10
+ [COLUMNS]: TableColumn[];
11
+ }
12
+ }
13
+ declare const COLUMNS: unique symbol;
14
+ export declare const KeyEachPlugin: Plugin;
15
+ export {};
@@ -0,0 +1,29 @@
1
+ import { change2 } from "../utils.js";
2
+ import { createComponent } from "solid-js/web";
3
+ import { For, createMemo, untrack } from "solid-js";
4
+ import { keyBy } from "es-toolkit";
5
+ var COLUMNS = Symbol("columns");
6
+ const KeyEachPlugin = {
7
+ name: "keyEach",
8
+ priority: -Infinity,
9
+ rewriteProps: {
10
+ columns: ({ columns: r }, { store: i }) => {
11
+ if (!i.props.useColKey) return r;
12
+ let a = untrack(() => keyBy(i[COLUMNS] ?? [], (e) => e.id));
13
+ return i[COLUMNS] = r.map((r) => a[r.id] ? change2(a[r.id], r) : { ...r }), i[COLUMNS];
14
+ },
15
+ EachRows: ({ EachRows: e }, { store: o }) => o.props.useRowKey ? (e) => {
16
+ let s = createMemo(() => ({
17
+ list: e.each.map((e) => e[o.props.rowKey]),
18
+ map: keyBy(e.each, (e) => e[o.props.rowKey])
19
+ }));
20
+ return createComponent(For, {
21
+ get each() {
22
+ return s().list;
23
+ },
24
+ children: (r, i) => e.children(() => s().map[r], i)
25
+ });
26
+ } : e
27
+ }
28
+ };
29
+ export { KeyEachPlugin };
@@ -49,7 +49,7 @@ const RowGroupPlugin = {
49
49
  }
50
50
  return d;
51
51
  },
52
- Td: ({ Td: d }, { store: _ }) => (v) => {
52
+ Td: ({ Td: d }, { store: _ }) => _.props?.rowGroup?.fields ? (v) => {
53
53
  if (!v.data?.[GROUP]) return d(v);
54
54
  let { props: y } = useContext(Ctx), b = createMemo(() => _.rowGroup.isExpand(v.data));
55
55
  return createComponent(d, mergeProps(v, { get children() {
@@ -64,7 +64,7 @@ const RowGroupPlugin = {
64
64
  }), null), insert(l, () => v.data[GROUP].value, null), effect((e) => style(l, `padding-left: ${(v.data[GROUP].path.length - 1) * 16}px`, e)), l;
65
65
  })() : v.children;
66
66
  } }));
67
- }
67
+ } : d
68
68
  }
69
69
  };
70
70
  var GROUP = Symbol("row-group"), expandData = (e, l, u = []) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intable",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "files": [