@tanstack/alpine-table 9.0.0-beta.25 → 9.0.0-beta.27

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.
@@ -4,9 +4,27 @@ const require_reactivity = require('./reactivity.cjs');
4
4
  let _tanstack_table_core = require("@tanstack/table-core");
5
5
  let alpinejs = require("alpinejs");
6
6
  alpinejs = require_runtime.__toESM(alpinejs, 1);
7
+ let _tanstack_store = require("@tanstack/store");
7
8
 
8
9
  //#region src/createTable.ts
9
- function createTable(tableOptions) {
10
+ /**
11
+ * Creates an Alpine-reactive table instance.
12
+ *
13
+ * Reactivity is bridged through a single version counter that every proxied
14
+ * table read registers as a dependency, so by default ANY state change
15
+ * re-evaluates every Alpine binding that touches the table. Pass a `selector`
16
+ * to gate that: the counter then only bumps when the selected slice of state
17
+ * changes (shallow compare). Use `() => ({})` to opt out of state-driven
18
+ * re-evaluation entirely and handle high-frequency state (e.g. column
19
+ * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.
20
+ * Options changes (e.g. new `data`) always re-evaluate.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const table = createTable(options, (state) => ({ sorting: state.sorting }))
25
+ * ```
26
+ */
27
+ function createTable(tableOptions, selector) {
10
28
  const table = (0, _tanstack_table_core.constructTable)({
11
29
  ...tableOptions,
12
30
  features: {
@@ -23,7 +41,13 @@ function createTable(tableOptions) {
23
41
  table.flexRender = require_flexRender.flexRender;
24
42
  table.FlexRender = require_flexRender.FlexRender;
25
43
  const reactivity = alpinejs.default.reactive({ _ver: 0 });
44
+ let lastSelected = selector ? selector(table.store.state) : void 0;
26
45
  table.store.subscribe(() => {
46
+ if (selector) {
47
+ const nextSelected = selector(table.store.state);
48
+ if ((0, _tanstack_store.shallow)(lastSelected, nextSelected)) return;
49
+ lastSelected = nextSelected;
50
+ }
27
51
  reactivity._ver++;
28
52
  });
29
53
  let initialized = false;
@@ -39,6 +63,7 @@ function createTable(tableOptions) {
39
63
  initialized = true;
40
64
  });
41
65
  const proxyCache = /* @__PURE__ */ new WeakMap();
66
+ const wrapperCache = /* @__PURE__ */ new WeakMap();
42
67
  const toReactiveProxy = (value) => {
43
68
  if (typeof value !== "object" || value === null) return value;
44
69
  if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet || value instanceof Date || value instanceof RegExp || value instanceof Promise) return value;
@@ -47,10 +72,24 @@ function createTable(tableOptions) {
47
72
  const proxy = new Proxy(value, { get(target, prop, receiver) {
48
73
  if (prop === "__v_skip") return true;
49
74
  const resolvedValue = Reflect.get(target, prop, receiver);
50
- if (typeof resolvedValue === "function") return (...args) => {
51
- reactivity._ver;
52
- return toReactiveProxy(resolvedValue.apply(target, args));
53
- };
75
+ if (typeof resolvedValue === "function") {
76
+ let targetWrappers = wrapperCache.get(target);
77
+ if (!targetWrappers) {
78
+ targetWrappers = /* @__PURE__ */ new Map();
79
+ wrapperCache.set(target, targetWrappers);
80
+ }
81
+ const cached = targetWrappers.get(prop);
82
+ if (cached && cached.original === resolvedValue) return cached.wrapper;
83
+ const wrapper = (...args) => {
84
+ reactivity._ver;
85
+ return toReactiveProxy(resolvedValue.apply(target, args));
86
+ };
87
+ targetWrappers.set(prop, {
88
+ original: resolvedValue,
89
+ wrapper
90
+ });
91
+ return wrapper;
92
+ }
54
93
  reactivity._ver;
55
94
  return toReactiveProxy(resolvedValue);
56
95
  } });
@@ -1 +1 @@
1
- {"version":3,"file":"createTable.cjs","names":["alpineReactivity","flexRender","FlexRender","Alpine"],"sources":["../src/createTable.ts"],"sourcesContent":["import Alpine from 'alpinejs'\nimport { constructTable } from '@tanstack/table-core'\nimport { FlexRender, flexRender } from './flexRender'\nimport { alpineReactivity } from './reactivity'\nimport type {\n RowData,\n Table,\n TableFeatures,\n TableOptions,\n} from '@tanstack/table-core'\n\nexport type AlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = Table<TFeatures, TData> & {\n /**\n * A lower-level helper to render the content of a cell, header, or footer from a render function and its context.\n */\n flexRender: typeof flexRender\n\n /**\n * A convenience helper to render a cell, header, or footer object. Call from `x-html`, e.g. `FlexRender({ header })`.\n */\n FlexRender: typeof FlexRender\n}\n\nexport function createTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n>(tableOptions: TableOptions<TFeatures, TData>): AlpineTable<TFeatures, TData> {\n const mergedOptions: TableOptions<TFeatures, TData> = {\n ...tableOptions,\n features: {\n coreReactivityFeature: alpineReactivity(),\n ...tableOptions.features,\n },\n mergeOptions: (\n defaultOptions: TableOptions<TFeatures, TData>,\n newOptions: Partial<TableOptions<TFeatures, TData>>,\n ) => {\n return {\n ...defaultOptions,\n ...newOptions,\n }\n },\n }\n\n const table = constructTable(mergedOptions) as unknown as AlpineTable<\n TFeatures,\n TData\n >\n\n table.flexRender = flexRender\n table.FlexRender = FlexRender\n\n const reactivity = Alpine.reactive({ _ver: 0 })\n\n table.store.subscribe(() => {\n reactivity._ver++\n })\n\n // Reactively sync options when external Alpine-reactive getters change (e.g.\n // a `get data()` backed by `Alpine.reactive`). Reading the option getters\n // inside the effect registers the dependencies, so the effect re-runs when\n // they change and re-applies the live values via `setOptions`.\n //\n // `setOptions` writes to the options store, not the state store, so a `data`\n // (or other option) change does not emit on `table.store` and would not bump\n // `_ver` on its own. We bump `_ver` here so the template re-pulls derived\n // APIs like `getRowModel()`, which recompute from the new options. The effect\n // never reads `_ver`, so writing it does not re-trigger this effect.\n let initialized = false\n Alpine.effect(() => {\n const state = tableOptions.state as Record<string, unknown> | undefined\n if (state) {\n for (const key in state) {\n void state[key]\n }\n }\n void tableOptions.data\n\n table.setOptions((prev) => ({\n ...prev,\n ...tableOptions,\n }))\n\n if (initialized) {\n reactivity._ver++\n }\n initialized = true\n })\n\n const proxyCache = new WeakMap<object, object>()\n\n const toReactiveProxy = <TValue>(value: TValue): TValue => {\n if (typeof value !== 'object' || value === null) {\n return value\n }\n\n // Built-in exotic objects (Map, Set, Date, etc.) rely on internal slots and\n // throw \"incompatible receiver\" when their getters/methods run with a Proxy\n // as the receiver (e.g. `getFacetedUniqueValues().size`). Return them as-is;\n // the read that produced them already tracked `_ver` at the call site.\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Promise\n ) {\n return value\n }\n\n const cachedProxy = proxyCache.get(value)\n if (cachedProxy) {\n return cachedProxy as TValue\n }\n\n const proxy = new Proxy(value, {\n get(target, prop, receiver) {\n if (prop === '__v_skip') {\n return true\n }\n\n const resolvedValue = Reflect.get(target, prop, receiver)\n\n if (typeof resolvedValue === 'function') {\n return (...args: Array<unknown>) => {\n void reactivity._ver\n return toReactiveProxy(\n (resolvedValue as Function).apply(target, args),\n )\n }\n }\n\n void reactivity._ver\n return toReactiveProxy(resolvedValue)\n },\n })\n\n proxyCache.set(value, proxy)\n return proxy\n }\n\n return toReactiveProxy(table)\n}\n"],"mappings":";;;;;;;;AA0BA,SAAgB,YAGd,cAA6E;CAkB7E,MAAM,iDAAuB;EAhB3B,GAAG;EACH,UAAU;GACR,uBAAuBA,oCAAiB;GACxC,GAAG,aAAa;EAClB;EACA,eACE,gBACA,eACG;GACH,OAAO;IACL,GAAG;IACH,GAAG;GACL;EACF;CAGuC,CAAC;CAK1C,MAAM,aAAaC;CACnB,MAAM,aAAaC;CAEnB,MAAM,aAAaC,iBAAO,SAAS,EAAE,MAAM,EAAE,CAAC;CAE9C,MAAM,MAAM,gBAAgB;EAC1B,WAAW;CACb,CAAC;CAYD,IAAI,cAAc;CAClB,iBAAO,aAAa;EAClB,MAAM,QAAQ,aAAa;EAC3B,IAAI,OACF,KAAK,MAAM,OAAO,OAChB,AAAK,MAAM;EAGf,AAAK,aAAa;EAElB,MAAM,YAAY,UAAU;GAC1B,GAAG;GACH,GAAG;EACL,EAAE;EAEF,IAAI,aACF,WAAW;EAEb,cAAc;CAChB,CAAC;CAED,MAAM,6BAAa,IAAI,QAAwB;CAE/C,MAAM,mBAA2B,UAA0B;EACzD,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;EAOT,IACE,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,WACjB,iBAAiB,WACjB,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,SAEjB,OAAO;EAGT,MAAM,cAAc,WAAW,IAAI,KAAK;EACxC,IAAI,aACF,OAAO;EAGT,MAAM,QAAQ,IAAI,MAAM,OAAO,EAC7B,IAAI,QAAQ,MAAM,UAAU;GAC1B,IAAI,SAAS,YACX,OAAO;GAGT,MAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,QAAQ;GAExD,IAAI,OAAO,kBAAkB,YAC3B,QAAQ,GAAG,SAAyB;IAClC,AAAK,WAAW;IAChB,OAAO,gBACJ,cAA2B,MAAM,QAAQ,IAAI,CAChD;GACF;GAGF,AAAK,WAAW;GAChB,OAAO,gBAAgB,aAAa;EACtC,EACF,CAAC;EAED,WAAW,IAAI,OAAO,KAAK;EAC3B,OAAO;CACT;CAEA,OAAO,gBAAgB,KAAK;AAC9B"}
1
+ {"version":3,"file":"createTable.cjs","names":["alpineReactivity","flexRender","FlexRender","Alpine"],"sources":["../src/createTable.ts"],"sourcesContent":["import Alpine from 'alpinejs'\nimport { shallow } from '@tanstack/store'\nimport { constructTable } from '@tanstack/table-core'\nimport { FlexRender, flexRender } from './flexRender'\nimport { alpineReactivity } from './reactivity'\nimport type {\n RowData,\n Table,\n TableFeatures,\n TableOptions,\n TableState,\n} from '@tanstack/table-core'\n\nexport type AlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = Table<TFeatures, TData> & {\n /**\n * A lower-level helper to render the content of a cell, header, or footer from a render function and its context.\n */\n flexRender: typeof flexRender\n\n /**\n * A convenience helper to render a cell, header, or footer object. Call from `x-html`, e.g. `FlexRender({ header })`.\n */\n FlexRender: typeof FlexRender\n}\n\n/**\n * Creates an Alpine-reactive table instance.\n *\n * Reactivity is bridged through a single version counter that every proxied\n * table read registers as a dependency, so by default ANY state change\n * re-evaluates every Alpine binding that touches the table. Pass a `selector`\n * to gate that: the counter then only bumps when the selected slice of state\n * changes (shallow compare). Use `() => ({})` to opt out of state-driven\n * re-evaluation entirely and handle high-frequency state (e.g. column\n * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.\n * Options changes (e.g. new `data`) always re-evaluate.\n *\n * @example\n * ```ts\n * const table = createTable(options, (state) => ({ sorting: state.sorting }))\n * ```\n */\nexport function createTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n>(\n tableOptions: TableOptions<TFeatures, TData>,\n selector?: (state: TableState<TFeatures>) => unknown,\n): AlpineTable<TFeatures, TData> {\n const mergedOptions: TableOptions<TFeatures, TData> = {\n ...tableOptions,\n features: {\n coreReactivityFeature: alpineReactivity(),\n ...tableOptions.features,\n },\n mergeOptions: (\n defaultOptions: TableOptions<TFeatures, TData>,\n newOptions: Partial<TableOptions<TFeatures, TData>>,\n ) => {\n return {\n ...defaultOptions,\n ...newOptions,\n }\n },\n }\n\n const table = constructTable(mergedOptions) as unknown as AlpineTable<\n TFeatures,\n TData\n >\n\n table.flexRender = flexRender\n table.FlexRender = FlexRender\n\n const reactivity = Alpine.reactive({ _ver: 0 })\n\n // With a selector, only bump the version counter (and thereby re-evaluate\n // table-reading Alpine bindings) when the selected state actually changes.\n // No selector keeps the previous behavior of re-evaluating on every state\n // change.\n let lastSelected = selector ? selector(table.store.state) : undefined\n\n table.store.subscribe(() => {\n if (selector) {\n const nextSelected = selector(table.store.state)\n if (shallow(lastSelected as any, nextSelected as any)) {\n return\n }\n lastSelected = nextSelected\n }\n reactivity._ver++\n })\n\n // Reactively sync options when external Alpine-reactive getters change (e.g.\n // a `get data()` backed by `Alpine.reactive`). Reading the option getters\n // inside the effect registers the dependencies, so the effect re-runs when\n // they change and re-applies the live values via `setOptions`.\n //\n // `setOptions` writes to the options store, not the state store, so a `data`\n // (or other option) change does not emit on `table.store` and would not bump\n // `_ver` on its own. We bump `_ver` here so the template re-pulls derived\n // APIs like `getRowModel()`, which recompute from the new options. The effect\n // never reads `_ver`, so writing it does not re-trigger this effect.\n let initialized = false\n Alpine.effect(() => {\n const state = tableOptions.state as Record<string, unknown> | undefined\n if (state) {\n for (const key in state) {\n void state[key]\n }\n }\n void tableOptions.data\n\n table.setOptions((prev) => ({\n ...prev,\n ...tableOptions,\n }))\n\n if (initialized) {\n reactivity._ver++\n }\n initialized = true\n })\n\n const proxyCache = new WeakMap<object, object>()\n\n // Cache method wrappers per (target, prop) so repeated reads of the same\n // function property (e.g. `cell.renderValue` across thousands of cells)\n // reuse one closure instead of allocating a new one per access.\n const wrapperCache = new WeakMap<\n object,\n Map<PropertyKey, { original: Function; wrapper: Function }>\n >()\n\n const toReactiveProxy = <TValue>(value: TValue): TValue => {\n if (typeof value !== 'object' || value === null) {\n return value\n }\n\n // Built-in exotic objects (Map, Set, Date, etc.) rely on internal slots and\n // throw \"incompatible receiver\" when their getters/methods run with a Proxy\n // as the receiver (e.g. `getFacetedUniqueValues().size`). Return them as-is;\n // the read that produced them already tracked `_ver` at the call site.\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Promise\n ) {\n return value\n }\n\n const cachedProxy = proxyCache.get(value)\n if (cachedProxy) {\n return cachedProxy as TValue\n }\n\n const proxy = new Proxy(value, {\n get(target, prop, receiver) {\n if (prop === '__v_skip') {\n return true\n }\n\n const resolvedValue = Reflect.get(target, prop, receiver)\n\n if (typeof resolvedValue === 'function') {\n let targetWrappers = wrapperCache.get(target)\n if (!targetWrappers) {\n targetWrappers = new Map()\n wrapperCache.set(target, targetWrappers)\n }\n const cached = targetWrappers.get(prop)\n if (cached && cached.original === resolvedValue) {\n return cached.wrapper\n }\n const wrapper = (...args: Array<unknown>) => {\n void reactivity._ver\n return toReactiveProxy(\n (resolvedValue as Function).apply(target, args),\n )\n }\n targetWrappers.set(prop, { original: resolvedValue, wrapper })\n return wrapper\n }\n\n void reactivity._ver\n return toReactiveProxy(resolvedValue)\n },\n })\n\n proxyCache.set(value, proxy)\n return proxy\n }\n\n return toReactiveProxy(table)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,YAId,cACA,UAC+B;CAkB/B,MAAM,iDAAuB;EAhB3B,GAAG;EACH,UAAU;GACR,uBAAuBA,oCAAiB;GACxC,GAAG,aAAa;EAClB;EACA,eACE,gBACA,eACG;GACH,OAAO;IACL,GAAG;IACH,GAAG;GACL;EACF;CAGuC,CAAC;CAK1C,MAAM,aAAaC;CACnB,MAAM,aAAaC;CAEnB,MAAM,aAAaC,iBAAO,SAAS,EAAE,MAAM,EAAE,CAAC;CAM9C,IAAI,eAAe,WAAW,SAAS,MAAM,MAAM,KAAK,IAAI;CAE5D,MAAM,MAAM,gBAAgB;EAC1B,IAAI,UAAU;GACZ,MAAM,eAAe,SAAS,MAAM,MAAM,KAAK;GAC/C,iCAAY,cAAqB,YAAmB,GAClD;GAEF,eAAe;EACjB;EACA,WAAW;CACb,CAAC;CAYD,IAAI,cAAc;CAClB,iBAAO,aAAa;EAClB,MAAM,QAAQ,aAAa;EAC3B,IAAI,OACF,KAAK,MAAM,OAAO,OAChB,AAAK,MAAM;EAGf,AAAK,aAAa;EAElB,MAAM,YAAY,UAAU;GAC1B,GAAG;GACH,GAAG;EACL,EAAE;EAEF,IAAI,aACF,WAAW;EAEb,cAAc;CAChB,CAAC;CAED,MAAM,6BAAa,IAAI,QAAwB;CAK/C,MAAM,+BAAe,IAAI,QAGvB;CAEF,MAAM,mBAA2B,UAA0B;EACzD,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;EAOT,IACE,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,WACjB,iBAAiB,WACjB,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,SAEjB,OAAO;EAGT,MAAM,cAAc,WAAW,IAAI,KAAK;EACxC,IAAI,aACF,OAAO;EAGT,MAAM,QAAQ,IAAI,MAAM,OAAO,EAC7B,IAAI,QAAQ,MAAM,UAAU;GAC1B,IAAI,SAAS,YACX,OAAO;GAGT,MAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,QAAQ;GAExD,IAAI,OAAO,kBAAkB,YAAY;IACvC,IAAI,iBAAiB,aAAa,IAAI,MAAM;IAC5C,IAAI,CAAC,gBAAgB;KACnB,iCAAiB,IAAI,IAAI;KACzB,aAAa,IAAI,QAAQ,cAAc;IACzC;IACA,MAAM,SAAS,eAAe,IAAI,IAAI;IACtC,IAAI,UAAU,OAAO,aAAa,eAChC,OAAO,OAAO;IAEhB,MAAM,WAAW,GAAG,SAAyB;KAC3C,AAAK,WAAW;KAChB,OAAO,gBACJ,cAA2B,MAAM,QAAQ,IAAI,CAChD;IACF;IACA,eAAe,IAAI,MAAM;KAAE,UAAU;KAAe;IAAQ,CAAC;IAC7D,OAAO;GACT;GAEA,AAAK,WAAW;GAChB,OAAO,gBAAgB,aAAa;EACtC,EACF,CAAC;EAED,WAAW,IAAI,OAAO,KAAK;EAC3B,OAAO;CACT;CAEA,OAAO,gBAAgB,KAAK;AAC9B"}
@@ -1,5 +1,5 @@
1
1
  import { FlexRender, flexRender } from "./flexRender.cjs";
2
- import { RowData, Table, TableFeatures, TableOptions } from "@tanstack/table-core";
2
+ import { RowData, Table, TableFeatures, TableOptions, TableState } from "@tanstack/table-core";
3
3
 
4
4
  //#region src/createTable.d.ts
5
5
  type AlpineTable<TFeatures extends TableFeatures, TData extends RowData> = Table<TFeatures, TData> & {
@@ -12,7 +12,24 @@ type AlpineTable<TFeatures extends TableFeatures, TData extends RowData> = Table
12
12
  */
13
13
  FlexRender: typeof FlexRender;
14
14
  };
15
- declare function createTable<TFeatures extends TableFeatures, TData extends RowData>(tableOptions: TableOptions<TFeatures, TData>): AlpineTable<TFeatures, TData>;
15
+ /**
16
+ * Creates an Alpine-reactive table instance.
17
+ *
18
+ * Reactivity is bridged through a single version counter that every proxied
19
+ * table read registers as a dependency, so by default ANY state change
20
+ * re-evaluates every Alpine binding that touches the table. Pass a `selector`
21
+ * to gate that: the counter then only bumps when the selected slice of state
22
+ * changes (shallow compare). Use `() => ({})` to opt out of state-driven
23
+ * re-evaluation entirely and handle high-frequency state (e.g. column
24
+ * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.
25
+ * Options changes (e.g. new `data`) always re-evaluate.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const table = createTable(options, (state) => ({ sorting: state.sorting }))
30
+ * ```
31
+ */
32
+ declare function createTable<TFeatures extends TableFeatures, TData extends RowData>(tableOptions: TableOptions<TFeatures, TData>, selector?: (state: TableState<TFeatures>) => unknown): AlpineTable<TFeatures, TData>;
16
33
  //#endregion
17
34
  export { AlpineTable, createTable };
18
35
  //# sourceMappingURL=createTable.d.cts.map
@@ -1,5 +1,5 @@
1
1
  import { FlexRender, flexRender } from "./flexRender.js";
2
- import { RowData, Table, TableFeatures, TableOptions } from "@tanstack/table-core";
2
+ import { RowData, Table, TableFeatures, TableOptions, TableState } from "@tanstack/table-core";
3
3
 
4
4
  //#region src/createTable.d.ts
5
5
  type AlpineTable<TFeatures extends TableFeatures, TData extends RowData> = Table<TFeatures, TData> & {
@@ -12,7 +12,24 @@ type AlpineTable<TFeatures extends TableFeatures, TData extends RowData> = Table
12
12
  */
13
13
  FlexRender: typeof FlexRender;
14
14
  };
15
- declare function createTable<TFeatures extends TableFeatures, TData extends RowData>(tableOptions: TableOptions<TFeatures, TData>): AlpineTable<TFeatures, TData>;
15
+ /**
16
+ * Creates an Alpine-reactive table instance.
17
+ *
18
+ * Reactivity is bridged through a single version counter that every proxied
19
+ * table read registers as a dependency, so by default ANY state change
20
+ * re-evaluates every Alpine binding that touches the table. Pass a `selector`
21
+ * to gate that: the counter then only bumps when the selected slice of state
22
+ * changes (shallow compare). Use `() => ({})` to opt out of state-driven
23
+ * re-evaluation entirely and handle high-frequency state (e.g. column
24
+ * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.
25
+ * Options changes (e.g. new `data`) always re-evaluate.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const table = createTable(options, (state) => ({ sorting: state.sorting }))
30
+ * ```
31
+ */
32
+ declare function createTable<TFeatures extends TableFeatures, TData extends RowData>(tableOptions: TableOptions<TFeatures, TData>, selector?: (state: TableState<TFeatures>) => unknown): AlpineTable<TFeatures, TData>;
16
33
  //#endregion
17
34
  export { AlpineTable, createTable };
18
35
  //# sourceMappingURL=createTable.d.ts.map
@@ -2,9 +2,27 @@ import { FlexRender, flexRender } from "./flexRender.js";
2
2
  import { alpineReactivity } from "./reactivity.js";
3
3
  import { constructTable } from "@tanstack/table-core";
4
4
  import Alpine from "alpinejs";
5
+ import { shallow } from "@tanstack/store";
5
6
 
6
7
  //#region src/createTable.ts
7
- function createTable(tableOptions) {
8
+ /**
9
+ * Creates an Alpine-reactive table instance.
10
+ *
11
+ * Reactivity is bridged through a single version counter that every proxied
12
+ * table read registers as a dependency, so by default ANY state change
13
+ * re-evaluates every Alpine binding that touches the table. Pass a `selector`
14
+ * to gate that: the counter then only bumps when the selected slice of state
15
+ * changes (shallow compare). Use `() => ({})` to opt out of state-driven
16
+ * re-evaluation entirely and handle high-frequency state (e.g. column
17
+ * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.
18
+ * Options changes (e.g. new `data`) always re-evaluate.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const table = createTable(options, (state) => ({ sorting: state.sorting }))
23
+ * ```
24
+ */
25
+ function createTable(tableOptions, selector) {
8
26
  const table = constructTable({
9
27
  ...tableOptions,
10
28
  features: {
@@ -21,7 +39,13 @@ function createTable(tableOptions) {
21
39
  table.flexRender = flexRender;
22
40
  table.FlexRender = FlexRender;
23
41
  const reactivity = Alpine.reactive({ _ver: 0 });
42
+ let lastSelected = selector ? selector(table.store.state) : void 0;
24
43
  table.store.subscribe(() => {
44
+ if (selector) {
45
+ const nextSelected = selector(table.store.state);
46
+ if (shallow(lastSelected, nextSelected)) return;
47
+ lastSelected = nextSelected;
48
+ }
25
49
  reactivity._ver++;
26
50
  });
27
51
  let initialized = false;
@@ -37,6 +61,7 @@ function createTable(tableOptions) {
37
61
  initialized = true;
38
62
  });
39
63
  const proxyCache = /* @__PURE__ */ new WeakMap();
64
+ const wrapperCache = /* @__PURE__ */ new WeakMap();
40
65
  const toReactiveProxy = (value) => {
41
66
  if (typeof value !== "object" || value === null) return value;
42
67
  if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet || value instanceof Date || value instanceof RegExp || value instanceof Promise) return value;
@@ -45,10 +70,24 @@ function createTable(tableOptions) {
45
70
  const proxy = new Proxy(value, { get(target, prop, receiver) {
46
71
  if (prop === "__v_skip") return true;
47
72
  const resolvedValue = Reflect.get(target, prop, receiver);
48
- if (typeof resolvedValue === "function") return (...args) => {
49
- reactivity._ver;
50
- return toReactiveProxy(resolvedValue.apply(target, args));
51
- };
73
+ if (typeof resolvedValue === "function") {
74
+ let targetWrappers = wrapperCache.get(target);
75
+ if (!targetWrappers) {
76
+ targetWrappers = /* @__PURE__ */ new Map();
77
+ wrapperCache.set(target, targetWrappers);
78
+ }
79
+ const cached = targetWrappers.get(prop);
80
+ if (cached && cached.original === resolvedValue) return cached.wrapper;
81
+ const wrapper = (...args) => {
82
+ reactivity._ver;
83
+ return toReactiveProxy(resolvedValue.apply(target, args));
84
+ };
85
+ targetWrappers.set(prop, {
86
+ original: resolvedValue,
87
+ wrapper
88
+ });
89
+ return wrapper;
90
+ }
52
91
  reactivity._ver;
53
92
  return toReactiveProxy(resolvedValue);
54
93
  } });
@@ -1 +1 @@
1
- {"version":3,"file":"createTable.js","names":[],"sources":["../src/createTable.ts"],"sourcesContent":["import Alpine from 'alpinejs'\nimport { constructTable } from '@tanstack/table-core'\nimport { FlexRender, flexRender } from './flexRender'\nimport { alpineReactivity } from './reactivity'\nimport type {\n RowData,\n Table,\n TableFeatures,\n TableOptions,\n} from '@tanstack/table-core'\n\nexport type AlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = Table<TFeatures, TData> & {\n /**\n * A lower-level helper to render the content of a cell, header, or footer from a render function and its context.\n */\n flexRender: typeof flexRender\n\n /**\n * A convenience helper to render a cell, header, or footer object. Call from `x-html`, e.g. `FlexRender({ header })`.\n */\n FlexRender: typeof FlexRender\n}\n\nexport function createTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n>(tableOptions: TableOptions<TFeatures, TData>): AlpineTable<TFeatures, TData> {\n const mergedOptions: TableOptions<TFeatures, TData> = {\n ...tableOptions,\n features: {\n coreReactivityFeature: alpineReactivity(),\n ...tableOptions.features,\n },\n mergeOptions: (\n defaultOptions: TableOptions<TFeatures, TData>,\n newOptions: Partial<TableOptions<TFeatures, TData>>,\n ) => {\n return {\n ...defaultOptions,\n ...newOptions,\n }\n },\n }\n\n const table = constructTable(mergedOptions) as unknown as AlpineTable<\n TFeatures,\n TData\n >\n\n table.flexRender = flexRender\n table.FlexRender = FlexRender\n\n const reactivity = Alpine.reactive({ _ver: 0 })\n\n table.store.subscribe(() => {\n reactivity._ver++\n })\n\n // Reactively sync options when external Alpine-reactive getters change (e.g.\n // a `get data()` backed by `Alpine.reactive`). Reading the option getters\n // inside the effect registers the dependencies, so the effect re-runs when\n // they change and re-applies the live values via `setOptions`.\n //\n // `setOptions` writes to the options store, not the state store, so a `data`\n // (or other option) change does not emit on `table.store` and would not bump\n // `_ver` on its own. We bump `_ver` here so the template re-pulls derived\n // APIs like `getRowModel()`, which recompute from the new options. The effect\n // never reads `_ver`, so writing it does not re-trigger this effect.\n let initialized = false\n Alpine.effect(() => {\n const state = tableOptions.state as Record<string, unknown> | undefined\n if (state) {\n for (const key in state) {\n void state[key]\n }\n }\n void tableOptions.data\n\n table.setOptions((prev) => ({\n ...prev,\n ...tableOptions,\n }))\n\n if (initialized) {\n reactivity._ver++\n }\n initialized = true\n })\n\n const proxyCache = new WeakMap<object, object>()\n\n const toReactiveProxy = <TValue>(value: TValue): TValue => {\n if (typeof value !== 'object' || value === null) {\n return value\n }\n\n // Built-in exotic objects (Map, Set, Date, etc.) rely on internal slots and\n // throw \"incompatible receiver\" when their getters/methods run with a Proxy\n // as the receiver (e.g. `getFacetedUniqueValues().size`). Return them as-is;\n // the read that produced them already tracked `_ver` at the call site.\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Promise\n ) {\n return value\n }\n\n const cachedProxy = proxyCache.get(value)\n if (cachedProxy) {\n return cachedProxy as TValue\n }\n\n const proxy = new Proxy(value, {\n get(target, prop, receiver) {\n if (prop === '__v_skip') {\n return true\n }\n\n const resolvedValue = Reflect.get(target, prop, receiver)\n\n if (typeof resolvedValue === 'function') {\n return (...args: Array<unknown>) => {\n void reactivity._ver\n return toReactiveProxy(\n (resolvedValue as Function).apply(target, args),\n )\n }\n }\n\n void reactivity._ver\n return toReactiveProxy(resolvedValue)\n },\n })\n\n proxyCache.set(value, proxy)\n return proxy\n }\n\n return toReactiveProxy(table)\n}\n"],"mappings":";;;;;;AA0BA,SAAgB,YAGd,cAA6E;CAkB7E,MAAM,QAAQ,eAAe;EAhB3B,GAAG;EACH,UAAU;GACR,uBAAuB,iBAAiB;GACxC,GAAG,aAAa;EAClB;EACA,eACE,gBACA,eACG;GACH,OAAO;IACL,GAAG;IACH,GAAG;GACL;EACF;CAGuC,CAAC;CAK1C,MAAM,aAAa;CACnB,MAAM,aAAa;CAEnB,MAAM,aAAa,OAAO,SAAS,EAAE,MAAM,EAAE,CAAC;CAE9C,MAAM,MAAM,gBAAgB;EAC1B,WAAW;CACb,CAAC;CAYD,IAAI,cAAc;CAClB,OAAO,aAAa;EAClB,MAAM,QAAQ,aAAa;EAC3B,IAAI,OACF,KAAK,MAAM,OAAO,OAChB,AAAK,MAAM;EAGf,AAAK,aAAa;EAElB,MAAM,YAAY,UAAU;GAC1B,GAAG;GACH,GAAG;EACL,EAAE;EAEF,IAAI,aACF,WAAW;EAEb,cAAc;CAChB,CAAC;CAED,MAAM,6BAAa,IAAI,QAAwB;CAE/C,MAAM,mBAA2B,UAA0B;EACzD,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;EAOT,IACE,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,WACjB,iBAAiB,WACjB,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,SAEjB,OAAO;EAGT,MAAM,cAAc,WAAW,IAAI,KAAK;EACxC,IAAI,aACF,OAAO;EAGT,MAAM,QAAQ,IAAI,MAAM,OAAO,EAC7B,IAAI,QAAQ,MAAM,UAAU;GAC1B,IAAI,SAAS,YACX,OAAO;GAGT,MAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,QAAQ;GAExD,IAAI,OAAO,kBAAkB,YAC3B,QAAQ,GAAG,SAAyB;IAClC,AAAK,WAAW;IAChB,OAAO,gBACJ,cAA2B,MAAM,QAAQ,IAAI,CAChD;GACF;GAGF,AAAK,WAAW;GAChB,OAAO,gBAAgB,aAAa;EACtC,EACF,CAAC;EAED,WAAW,IAAI,OAAO,KAAK;EAC3B,OAAO;CACT;CAEA,OAAO,gBAAgB,KAAK;AAC9B"}
1
+ {"version":3,"file":"createTable.js","names":[],"sources":["../src/createTable.ts"],"sourcesContent":["import Alpine from 'alpinejs'\nimport { shallow } from '@tanstack/store'\nimport { constructTable } from '@tanstack/table-core'\nimport { FlexRender, flexRender } from './flexRender'\nimport { alpineReactivity } from './reactivity'\nimport type {\n RowData,\n Table,\n TableFeatures,\n TableOptions,\n TableState,\n} from '@tanstack/table-core'\n\nexport type AlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = Table<TFeatures, TData> & {\n /**\n * A lower-level helper to render the content of a cell, header, or footer from a render function and its context.\n */\n flexRender: typeof flexRender\n\n /**\n * A convenience helper to render a cell, header, or footer object. Call from `x-html`, e.g. `FlexRender({ header })`.\n */\n FlexRender: typeof FlexRender\n}\n\n/**\n * Creates an Alpine-reactive table instance.\n *\n * Reactivity is bridged through a single version counter that every proxied\n * table read registers as a dependency, so by default ANY state change\n * re-evaluates every Alpine binding that touches the table. Pass a `selector`\n * to gate that: the counter then only bumps when the selected slice of state\n * changes (shallow compare). Use `() => ({})` to opt out of state-driven\n * re-evaluation entirely and handle high-frequency state (e.g. column\n * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.\n * Options changes (e.g. new `data`) always re-evaluate.\n *\n * @example\n * ```ts\n * const table = createTable(options, (state) => ({ sorting: state.sorting }))\n * ```\n */\nexport function createTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n>(\n tableOptions: TableOptions<TFeatures, TData>,\n selector?: (state: TableState<TFeatures>) => unknown,\n): AlpineTable<TFeatures, TData> {\n const mergedOptions: TableOptions<TFeatures, TData> = {\n ...tableOptions,\n features: {\n coreReactivityFeature: alpineReactivity(),\n ...tableOptions.features,\n },\n mergeOptions: (\n defaultOptions: TableOptions<TFeatures, TData>,\n newOptions: Partial<TableOptions<TFeatures, TData>>,\n ) => {\n return {\n ...defaultOptions,\n ...newOptions,\n }\n },\n }\n\n const table = constructTable(mergedOptions) as unknown as AlpineTable<\n TFeatures,\n TData\n >\n\n table.flexRender = flexRender\n table.FlexRender = FlexRender\n\n const reactivity = Alpine.reactive({ _ver: 0 })\n\n // With a selector, only bump the version counter (and thereby re-evaluate\n // table-reading Alpine bindings) when the selected state actually changes.\n // No selector keeps the previous behavior of re-evaluating on every state\n // change.\n let lastSelected = selector ? selector(table.store.state) : undefined\n\n table.store.subscribe(() => {\n if (selector) {\n const nextSelected = selector(table.store.state)\n if (shallow(lastSelected as any, nextSelected as any)) {\n return\n }\n lastSelected = nextSelected\n }\n reactivity._ver++\n })\n\n // Reactively sync options when external Alpine-reactive getters change (e.g.\n // a `get data()` backed by `Alpine.reactive`). Reading the option getters\n // inside the effect registers the dependencies, so the effect re-runs when\n // they change and re-applies the live values via `setOptions`.\n //\n // `setOptions` writes to the options store, not the state store, so a `data`\n // (or other option) change does not emit on `table.store` and would not bump\n // `_ver` on its own. We bump `_ver` here so the template re-pulls derived\n // APIs like `getRowModel()`, which recompute from the new options. The effect\n // never reads `_ver`, so writing it does not re-trigger this effect.\n let initialized = false\n Alpine.effect(() => {\n const state = tableOptions.state as Record<string, unknown> | undefined\n if (state) {\n for (const key in state) {\n void state[key]\n }\n }\n void tableOptions.data\n\n table.setOptions((prev) => ({\n ...prev,\n ...tableOptions,\n }))\n\n if (initialized) {\n reactivity._ver++\n }\n initialized = true\n })\n\n const proxyCache = new WeakMap<object, object>()\n\n // Cache method wrappers per (target, prop) so repeated reads of the same\n // function property (e.g. `cell.renderValue` across thousands of cells)\n // reuse one closure instead of allocating a new one per access.\n const wrapperCache = new WeakMap<\n object,\n Map<PropertyKey, { original: Function; wrapper: Function }>\n >()\n\n const toReactiveProxy = <TValue>(value: TValue): TValue => {\n if (typeof value !== 'object' || value === null) {\n return value\n }\n\n // Built-in exotic objects (Map, Set, Date, etc.) rely on internal slots and\n // throw \"incompatible receiver\" when their getters/methods run with a Proxy\n // as the receiver (e.g. `getFacetedUniqueValues().size`). Return them as-is;\n // the read that produced them already tracked `_ver` at the call site.\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Promise\n ) {\n return value\n }\n\n const cachedProxy = proxyCache.get(value)\n if (cachedProxy) {\n return cachedProxy as TValue\n }\n\n const proxy = new Proxy(value, {\n get(target, prop, receiver) {\n if (prop === '__v_skip') {\n return true\n }\n\n const resolvedValue = Reflect.get(target, prop, receiver)\n\n if (typeof resolvedValue === 'function') {\n let targetWrappers = wrapperCache.get(target)\n if (!targetWrappers) {\n targetWrappers = new Map()\n wrapperCache.set(target, targetWrappers)\n }\n const cached = targetWrappers.get(prop)\n if (cached && cached.original === resolvedValue) {\n return cached.wrapper\n }\n const wrapper = (...args: Array<unknown>) => {\n void reactivity._ver\n return toReactiveProxy(\n (resolvedValue as Function).apply(target, args),\n )\n }\n targetWrappers.set(prop, { original: resolvedValue, wrapper })\n return wrapper\n }\n\n void reactivity._ver\n return toReactiveProxy(resolvedValue)\n },\n })\n\n proxyCache.set(value, proxy)\n return proxy\n }\n\n return toReactiveProxy(table)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,YAId,cACA,UAC+B;CAkB/B,MAAM,QAAQ,eAAe;EAhB3B,GAAG;EACH,UAAU;GACR,uBAAuB,iBAAiB;GACxC,GAAG,aAAa;EAClB;EACA,eACE,gBACA,eACG;GACH,OAAO;IACL,GAAG;IACH,GAAG;GACL;EACF;CAGuC,CAAC;CAK1C,MAAM,aAAa;CACnB,MAAM,aAAa;CAEnB,MAAM,aAAa,OAAO,SAAS,EAAE,MAAM,EAAE,CAAC;CAM9C,IAAI,eAAe,WAAW,SAAS,MAAM,MAAM,KAAK,IAAI;CAE5D,MAAM,MAAM,gBAAgB;EAC1B,IAAI,UAAU;GACZ,MAAM,eAAe,SAAS,MAAM,MAAM,KAAK;GAC/C,IAAI,QAAQ,cAAqB,YAAmB,GAClD;GAEF,eAAe;EACjB;EACA,WAAW;CACb,CAAC;CAYD,IAAI,cAAc;CAClB,OAAO,aAAa;EAClB,MAAM,QAAQ,aAAa;EAC3B,IAAI,OACF,KAAK,MAAM,OAAO,OAChB,AAAK,MAAM;EAGf,AAAK,aAAa;EAElB,MAAM,YAAY,UAAU;GAC1B,GAAG;GACH,GAAG;EACL,EAAE;EAEF,IAAI,aACF,WAAW;EAEb,cAAc;CAChB,CAAC;CAED,MAAM,6BAAa,IAAI,QAAwB;CAK/C,MAAM,+BAAe,IAAI,QAGvB;CAEF,MAAM,mBAA2B,UAA0B;EACzD,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;EAOT,IACE,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,WACjB,iBAAiB,WACjB,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,SAEjB,OAAO;EAGT,MAAM,cAAc,WAAW,IAAI,KAAK;EACxC,IAAI,aACF,OAAO;EAGT,MAAM,QAAQ,IAAI,MAAM,OAAO,EAC7B,IAAI,QAAQ,MAAM,UAAU;GAC1B,IAAI,SAAS,YACX,OAAO;GAGT,MAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,QAAQ;GAExD,IAAI,OAAO,kBAAkB,YAAY;IACvC,IAAI,iBAAiB,aAAa,IAAI,MAAM;IAC5C,IAAI,CAAC,gBAAgB;KACnB,iCAAiB,IAAI,IAAI;KACzB,aAAa,IAAI,QAAQ,cAAc;IACzC;IACA,MAAM,SAAS,eAAe,IAAI,IAAI;IACtC,IAAI,UAAU,OAAO,aAAa,eAChC,OAAO,OAAO;IAEhB,MAAM,WAAW,GAAG,SAAyB;KAC3C,AAAK,WAAW;KAChB,OAAO,gBACJ,cAA2B,MAAM,QAAQ,IAAI,CAChD;IACF;IACA,eAAe,IAAI,MAAM;KAAE,UAAU;KAAe;IAAQ,CAAC;IAC7D,OAAO;GACT;GAEA,AAAK,WAAW;GAChB,OAAO,gBAAgB,aAAa;EACtC,EACF,CAAC;EAED,WAAW,IAAI,OAAO,KAAK;EAC3B,OAAO;CACT;CAEA,OAAO,gBAAgB,KAAK;AAC9B"}
@@ -6,11 +6,11 @@ function createTableHook({ ...defaultTableOptions }) {
6
6
  function createAppColumnHelper() {
7
7
  return (0, _tanstack_table_core.createColumnHelper)();
8
8
  }
9
- function createAppTable(tableOptions) {
9
+ function createAppTable(tableOptions, selector) {
10
10
  return require_createTable.createTable({
11
11
  ...defaultTableOptions,
12
12
  ...tableOptions
13
- });
13
+ }, selector);
14
14
  }
15
15
  return {
16
16
  appFeatures: defaultTableOptions.features,
@@ -1 +1 @@
1
- {"version":3,"file":"createTableHook.cjs","names":["createTable"],"sources":["../src/createTableHook.ts"],"sourcesContent":["import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'\nimport { createTable } from './createTable'\nimport type { AlpineTable } from './createTable'\nimport type { RowData, TableFeatures, TableOptions } from '@tanstack/table-core'\n\nexport type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<\n TableOptions<TFeatures, any>,\n 'columns' | 'data' | 'state'\n>\n\nexport type AppAlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = AlpineTable<TFeatures, TData>\n\nexport type AppColumnHelper<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = ReturnType<typeof coreCreateColumnHelper<TFeatures, TData>>\n\nexport function createTableHook<TFeatures extends TableFeatures>({\n ...defaultTableOptions\n}: CreateTableHookOptions<TFeatures>) {\n function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<\n TFeatures,\n TData\n > {\n return coreCreateColumnHelper<TFeatures, TData>()\n }\n\n function createAppTable<TData extends RowData>(\n tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>,\n ): AppAlpineTable<TFeatures, TData> {\n // Merge default options with provided options (provided takes precedence)\n const mergedOptions = {\n ...defaultTableOptions,\n ...tableOptions,\n } as TableOptions<TFeatures, TData>\n\n return createTable<TFeatures, TData>(mergedOptions)\n }\n\n return {\n appFeatures: defaultTableOptions.features as TFeatures,\n createAppColumnHelper,\n createAppTable,\n }\n}\n"],"mappings":";;;;AAoBA,SAAgB,gBAAiD,EAC/D,GAAG,uBACiC;CACpC,SAAS,wBAGP;EACA,oDAAgD;CAClD;CAEA,SAAS,eACP,cACkC;EAOlC,OAAOA,gCAA8B;GAJnC,GAAG;GACH,GAAG;EAG4C,CAAC;CACpD;CAEA,OAAO;EACL,aAAa,oBAAoB;EACjC;EACA;CACF;AACF"}
1
+ {"version":3,"file":"createTableHook.cjs","names":["createTable"],"sources":["../src/createTableHook.ts"],"sourcesContent":["import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'\nimport { createTable } from './createTable'\nimport type { AlpineTable } from './createTable'\nimport type {\n RowData,\n TableFeatures,\n TableOptions,\n TableState,\n} from '@tanstack/table-core'\n\nexport type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<\n TableOptions<TFeatures, any>,\n 'columns' | 'data' | 'state'\n>\n\nexport type AppAlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = AlpineTable<TFeatures, TData>\n\nexport type AppColumnHelper<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = ReturnType<typeof coreCreateColumnHelper<TFeatures, TData>>\n\nexport function createTableHook<TFeatures extends TableFeatures>({\n ...defaultTableOptions\n}: CreateTableHookOptions<TFeatures>) {\n function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<\n TFeatures,\n TData\n > {\n return coreCreateColumnHelper<TFeatures, TData>()\n }\n\n function createAppTable<TData extends RowData>(\n tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>,\n selector?: (state: TableState<TFeatures>) => unknown,\n ): AppAlpineTable<TFeatures, TData> {\n // Merge default options with provided options (provided takes precedence)\n const mergedOptions = {\n ...defaultTableOptions,\n ...tableOptions,\n } as TableOptions<TFeatures, TData>\n\n return createTable<TFeatures, TData>(mergedOptions, selector)\n }\n\n return {\n appFeatures: defaultTableOptions.features as TFeatures,\n createAppColumnHelper,\n createAppTable,\n }\n}\n"],"mappings":";;;;AAyBA,SAAgB,gBAAiD,EAC/D,GAAG,uBACiC;CACpC,SAAS,wBAGP;EACA,oDAAgD;CAClD;CAEA,SAAS,eACP,cACA,UACkC;EAOlC,OAAOA,gCAA8B;GAJnC,GAAG;GACH,GAAG;EAG4C,GAAG,QAAQ;CAC9D;CAEA,OAAO;EACL,aAAa,oBAAoB;EACjC;EACA;CACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { AlpineTable } from "./createTable.cjs";
2
- import { RowData, TableFeatures, TableOptions, createColumnHelper } from "@tanstack/table-core";
2
+ import { RowData, TableFeatures, TableOptions, TableState, createColumnHelper } from "@tanstack/table-core";
3
3
 
4
4
  //#region src/createTableHook.d.ts
5
5
  type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<TableOptions<TFeatures, any>, 'columns' | 'data' | 'state'>;
@@ -10,7 +10,7 @@ declare function createTableHook<TFeatures extends TableFeatures>({
10
10
  }: CreateTableHookOptions<TFeatures>): {
11
11
  appFeatures: TFeatures;
12
12
  createAppColumnHelper: <TData extends RowData>() => AppColumnHelper<TFeatures, TData>;
13
- createAppTable: <TData extends RowData>(tableOptions: Omit<TableOptions<TFeatures, TData>, "features">) => AppAlpineTable<TFeatures, TData>;
13
+ createAppTable: <TData extends RowData>(tableOptions: Omit<TableOptions<TFeatures, TData>, "features">, selector?: (state: TableState<TFeatures>) => unknown) => AppAlpineTable<TFeatures, TData>;
14
14
  };
15
15
  //#endregion
16
16
  export { AppAlpineTable, AppColumnHelper, CreateTableHookOptions, createTableHook };
@@ -1,5 +1,5 @@
1
1
  import { AlpineTable } from "./createTable.js";
2
- import { RowData, TableFeatures, TableOptions, createColumnHelper } from "@tanstack/table-core";
2
+ import { RowData, TableFeatures, TableOptions, TableState, createColumnHelper } from "@tanstack/table-core";
3
3
 
4
4
  //#region src/createTableHook.d.ts
5
5
  type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<TableOptions<TFeatures, any>, 'columns' | 'data' | 'state'>;
@@ -10,7 +10,7 @@ declare function createTableHook<TFeatures extends TableFeatures>({
10
10
  }: CreateTableHookOptions<TFeatures>): {
11
11
  appFeatures: TFeatures;
12
12
  createAppColumnHelper: <TData extends RowData>() => AppColumnHelper<TFeatures, TData>;
13
- createAppTable: <TData extends RowData>(tableOptions: Omit<TableOptions<TFeatures, TData>, "features">) => AppAlpineTable<TFeatures, TData>;
13
+ createAppTable: <TData extends RowData>(tableOptions: Omit<TableOptions<TFeatures, TData>, "features">, selector?: (state: TableState<TFeatures>) => unknown) => AppAlpineTable<TFeatures, TData>;
14
14
  };
15
15
  //#endregion
16
16
  export { AppAlpineTable, AppColumnHelper, CreateTableHookOptions, createTableHook };
@@ -6,11 +6,11 @@ function createTableHook({ ...defaultTableOptions }) {
6
6
  function createAppColumnHelper() {
7
7
  return createColumnHelper();
8
8
  }
9
- function createAppTable(tableOptions) {
9
+ function createAppTable(tableOptions, selector) {
10
10
  return createTable({
11
11
  ...defaultTableOptions,
12
12
  ...tableOptions
13
- });
13
+ }, selector);
14
14
  }
15
15
  return {
16
16
  appFeatures: defaultTableOptions.features,
@@ -1 +1 @@
1
- {"version":3,"file":"createTableHook.js","names":["coreCreateColumnHelper"],"sources":["../src/createTableHook.ts"],"sourcesContent":["import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'\nimport { createTable } from './createTable'\nimport type { AlpineTable } from './createTable'\nimport type { RowData, TableFeatures, TableOptions } from '@tanstack/table-core'\n\nexport type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<\n TableOptions<TFeatures, any>,\n 'columns' | 'data' | 'state'\n>\n\nexport type AppAlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = AlpineTable<TFeatures, TData>\n\nexport type AppColumnHelper<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = ReturnType<typeof coreCreateColumnHelper<TFeatures, TData>>\n\nexport function createTableHook<TFeatures extends TableFeatures>({\n ...defaultTableOptions\n}: CreateTableHookOptions<TFeatures>) {\n function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<\n TFeatures,\n TData\n > {\n return coreCreateColumnHelper<TFeatures, TData>()\n }\n\n function createAppTable<TData extends RowData>(\n tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>,\n ): AppAlpineTable<TFeatures, TData> {\n // Merge default options with provided options (provided takes precedence)\n const mergedOptions = {\n ...defaultTableOptions,\n ...tableOptions,\n } as TableOptions<TFeatures, TData>\n\n return createTable<TFeatures, TData>(mergedOptions)\n }\n\n return {\n appFeatures: defaultTableOptions.features as TFeatures,\n createAppColumnHelper,\n createAppTable,\n }\n}\n"],"mappings":";;;;AAoBA,SAAgB,gBAAiD,EAC/D,GAAG,uBACiC;CACpC,SAAS,wBAGP;EACA,OAAOA,mBAAyC;CAClD;CAEA,SAAS,eACP,cACkC;EAOlC,OAAO,YAA8B;GAJnC,GAAG;GACH,GAAG;EAG4C,CAAC;CACpD;CAEA,OAAO;EACL,aAAa,oBAAoB;EACjC;EACA;CACF;AACF"}
1
+ {"version":3,"file":"createTableHook.js","names":["coreCreateColumnHelper"],"sources":["../src/createTableHook.ts"],"sourcesContent":["import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'\nimport { createTable } from './createTable'\nimport type { AlpineTable } from './createTable'\nimport type {\n RowData,\n TableFeatures,\n TableOptions,\n TableState,\n} from '@tanstack/table-core'\n\nexport type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<\n TableOptions<TFeatures, any>,\n 'columns' | 'data' | 'state'\n>\n\nexport type AppAlpineTable<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = AlpineTable<TFeatures, TData>\n\nexport type AppColumnHelper<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> = ReturnType<typeof coreCreateColumnHelper<TFeatures, TData>>\n\nexport function createTableHook<TFeatures extends TableFeatures>({\n ...defaultTableOptions\n}: CreateTableHookOptions<TFeatures>) {\n function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<\n TFeatures,\n TData\n > {\n return coreCreateColumnHelper<TFeatures, TData>()\n }\n\n function createAppTable<TData extends RowData>(\n tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>,\n selector?: (state: TableState<TFeatures>) => unknown,\n ): AppAlpineTable<TFeatures, TData> {\n // Merge default options with provided options (provided takes precedence)\n const mergedOptions = {\n ...defaultTableOptions,\n ...tableOptions,\n } as TableOptions<TFeatures, TData>\n\n return createTable<TFeatures, TData>(mergedOptions, selector)\n }\n\n return {\n appFeatures: defaultTableOptions.features as TFeatures,\n createAppColumnHelper,\n createAppTable,\n }\n}\n"],"mappings":";;;;AAyBA,SAAgB,gBAAiD,EAC/D,GAAG,uBACiC;CACpC,SAAS,wBAGP;EACA,OAAOA,mBAAyC;CAClD;CAEA,SAAS,eACP,cACA,UACkC;EAOlC,OAAO,YAA8B;GAJnC,GAAG;GACH,GAAG;EAG4C,GAAG,QAAQ;CAC9D;CAEA,OAAO;EACL,aAAa,oBAAoB;EACjC;EACA;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/alpine-table",
3
- "version": "9.0.0-beta.25",
3
+ "version": "9.0.0-beta.27",
4
4
  "description": "Headless UI for building powerful tables & datagrids for Alpine.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@tanstack/store": "^0.11.0",
52
- "@tanstack/table-core": "9.0.0-beta.23"
52
+ "@tanstack/table-core": "9.0.0-beta.27"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/alpinejs": "^3.13.11",
@@ -1,4 +1,5 @@
1
1
  import Alpine from 'alpinejs'
2
+ import { shallow } from '@tanstack/store'
2
3
  import { constructTable } from '@tanstack/table-core'
3
4
  import { FlexRender, flexRender } from './flexRender'
4
5
  import { alpineReactivity } from './reactivity'
@@ -7,6 +8,7 @@ import type {
7
8
  Table,
8
9
  TableFeatures,
9
10
  TableOptions,
11
+ TableState,
10
12
  } from '@tanstack/table-core'
11
13
 
12
14
  export type AlpineTable<
@@ -24,10 +26,30 @@ export type AlpineTable<
24
26
  FlexRender: typeof FlexRender
25
27
  }
26
28
 
29
+ /**
30
+ * Creates an Alpine-reactive table instance.
31
+ *
32
+ * Reactivity is bridged through a single version counter that every proxied
33
+ * table read registers as a dependency, so by default ANY state change
34
+ * re-evaluates every Alpine binding that touches the table. Pass a `selector`
35
+ * to gate that: the counter then only bumps when the selected slice of state
36
+ * changes (shallow compare). Use `() => ({})` to opt out of state-driven
37
+ * re-evaluation entirely and handle high-frequency state (e.g. column
38
+ * resizing) with explicit `table.atoms.<slice>.subscribe()` side effects.
39
+ * Options changes (e.g. new `data`) always re-evaluate.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const table = createTable(options, (state) => ({ sorting: state.sorting }))
44
+ * ```
45
+ */
27
46
  export function createTable<
28
47
  TFeatures extends TableFeatures,
29
48
  TData extends RowData,
30
- >(tableOptions: TableOptions<TFeatures, TData>): AlpineTable<TFeatures, TData> {
49
+ >(
50
+ tableOptions: TableOptions<TFeatures, TData>,
51
+ selector?: (state: TableState<TFeatures>) => unknown,
52
+ ): AlpineTable<TFeatures, TData> {
31
53
  const mergedOptions: TableOptions<TFeatures, TData> = {
32
54
  ...tableOptions,
33
55
  features: {
@@ -55,7 +77,20 @@ export function createTable<
55
77
 
56
78
  const reactivity = Alpine.reactive({ _ver: 0 })
57
79
 
80
+ // With a selector, only bump the version counter (and thereby re-evaluate
81
+ // table-reading Alpine bindings) when the selected state actually changes.
82
+ // No selector keeps the previous behavior of re-evaluating on every state
83
+ // change.
84
+ let lastSelected = selector ? selector(table.store.state) : undefined
85
+
58
86
  table.store.subscribe(() => {
87
+ if (selector) {
88
+ const nextSelected = selector(table.store.state)
89
+ if (shallow(lastSelected as any, nextSelected as any)) {
90
+ return
91
+ }
92
+ lastSelected = nextSelected
93
+ }
59
94
  reactivity._ver++
60
95
  })
61
96
 
@@ -92,6 +127,14 @@ export function createTable<
92
127
 
93
128
  const proxyCache = new WeakMap<object, object>()
94
129
 
130
+ // Cache method wrappers per (target, prop) so repeated reads of the same
131
+ // function property (e.g. `cell.renderValue` across thousands of cells)
132
+ // reuse one closure instead of allocating a new one per access.
133
+ const wrapperCache = new WeakMap<
134
+ object,
135
+ Map<PropertyKey, { original: Function; wrapper: Function }>
136
+ >()
137
+
95
138
  const toReactiveProxy = <TValue>(value: TValue): TValue => {
96
139
  if (typeof value !== 'object' || value === null) {
97
140
  return value
@@ -127,12 +170,23 @@ export function createTable<
127
170
  const resolvedValue = Reflect.get(target, prop, receiver)
128
171
 
129
172
  if (typeof resolvedValue === 'function') {
130
- return (...args: Array<unknown>) => {
173
+ let targetWrappers = wrapperCache.get(target)
174
+ if (!targetWrappers) {
175
+ targetWrappers = new Map()
176
+ wrapperCache.set(target, targetWrappers)
177
+ }
178
+ const cached = targetWrappers.get(prop)
179
+ if (cached && cached.original === resolvedValue) {
180
+ return cached.wrapper
181
+ }
182
+ const wrapper = (...args: Array<unknown>) => {
131
183
  void reactivity._ver
132
184
  return toReactiveProxy(
133
185
  (resolvedValue as Function).apply(target, args),
134
186
  )
135
187
  }
188
+ targetWrappers.set(prop, { original: resolvedValue, wrapper })
189
+ return wrapper
136
190
  }
137
191
 
138
192
  void reactivity._ver
@@ -1,7 +1,12 @@
1
1
  import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'
2
2
  import { createTable } from './createTable'
3
3
  import type { AlpineTable } from './createTable'
4
- import type { RowData, TableFeatures, TableOptions } from '@tanstack/table-core'
4
+ import type {
5
+ RowData,
6
+ TableFeatures,
7
+ TableOptions,
8
+ TableState,
9
+ } from '@tanstack/table-core'
5
10
 
6
11
  export type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<
7
12
  TableOptions<TFeatures, any>,
@@ -30,6 +35,7 @@ export function createTableHook<TFeatures extends TableFeatures>({
30
35
 
31
36
  function createAppTable<TData extends RowData>(
32
37
  tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>,
38
+ selector?: (state: TableState<TFeatures>) => unknown,
33
39
  ): AppAlpineTable<TFeatures, TData> {
34
40
  // Merge default options with provided options (provided takes precedence)
35
41
  const mergedOptions = {
@@ -37,7 +43,7 @@ export function createTableHook<TFeatures extends TableFeatures>({
37
43
  ...tableOptions,
38
44
  } as TableOptions<TFeatures, TData>
39
45
 
40
- return createTable<TFeatures, TData>(mergedOptions)
46
+ return createTable<TFeatures, TData>(mergedOptions, selector)
41
47
  }
42
48
 
43
49
  return {