@trellisjs/plugin-pagination 0.1.0-alpha.1 → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 TrellisJS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @trellisjs/plugin-pagination
2
+
3
+ Page navigation plugin for Trellis data tables with configurable page size.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @trellisjs/plugin-pagination
9
+ ```
10
+
11
+ ## Events
12
+
13
+ ### Listens
14
+
15
+ | Event | Payload | Description |
16
+ |---|---|---|
17
+ | `pagination:goto` | `{ page: number }` | Jump to a specific page. Clamped to the valid range `[1, totalPages]`. |
18
+ | `pagination:prev` | _(none)_ | Go to the previous page. No-op when already on page 1. |
19
+ | `pagination:next` | _(none)_ | Go to the next page. No-op when already on the last page. |
20
+ | `pagination:pageSize` | `{ pageSize: number }` | Change the number of rows per page. Resets to page 1. |
21
+
22
+ ### State Updated
23
+
24
+ The plugin writes the following on every navigation:
25
+
26
+ - `state.pagination` -- `{ page, pageSize, totalItems }`.
27
+ - `state.data` -- the sliced rows for the current page.
28
+
29
+ ## Usage
30
+
31
+ ```ts
32
+ import { createTrellis } from '@trellisjs/core';
33
+ import { createPaginationPlugin } from '@trellisjs/plugin-pagination';
34
+
35
+ const api = createTrellis({
36
+ columns: [{ id: 'name', accessor: 'name' }],
37
+ data: [/* ... 100 rows ... */],
38
+ plugins: [createPaginationPlugin()],
39
+ });
40
+
41
+ // Go to page 3
42
+ api.emit('pagination:goto', { page: 3 });
43
+
44
+ // Next page
45
+ api.emit('pagination:next', {});
46
+
47
+ // Previous page
48
+ api.emit('pagination:prev', {});
49
+
50
+ // Change page size to 25 rows per page (resets to page 1)
51
+ api.emit('pagination:pageSize', { pageSize: 25 });
52
+ ```
53
+
54
+ ### Upstream Reactivity
55
+
56
+ The plugin subscribes to state changes from upstream plugins (e.g. sort, filter). When the data array is replaced by another plugin, pagination automatically resets to page 1 and re-slices the new dataset.
57
+
58
+ ### Plugin Order
59
+
60
+ Install the pagination plugin **after** filter and sort plugins so it paginates the already-transformed data:
61
+
62
+ ```ts
63
+ plugins: [createSortPlugin(), createFilterPlugin(), createPaginationPlugin()]
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT
package/dist/index.d.mts CHANGED
@@ -3,7 +3,7 @@ import { TrellisPlugin } from '@trellisjs/core';
3
3
  /**
4
4
  * 建立分頁插件實例。
5
5
  * 管理資料的頁碼導航。
6
- * 會響應上游插件(排序、篩選)的資料變更並重新分頁。
6
+ * 透過管線轉換函式處理分頁切片。
7
7
  */
8
8
  declare function createPaginationPlugin(): TrellisPlugin;
9
9
 
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { TrellisPlugin } from '@trellisjs/core';
3
3
  /**
4
4
  * 建立分頁插件實例。
5
5
  * 管理資料的頁碼導航。
6
- * 會響應上游插件(排序、篩選)的資料變更並重新分頁。
6
+ * 透過管線轉換函式處理分頁切片。
7
7
  */
8
8
  declare function createPaginationPlugin(): TrellisPlugin;
9
9
 
package/dist/index.js CHANGED
@@ -29,81 +29,50 @@ function createPaginationPlugin() {
29
29
  return {
30
30
  name: "pagination",
31
31
  install(api) {
32
- let sourceData = [...api.getState().data];
33
- let selfUpdate = false;
34
- applyPagination(api, sourceData, true);
35
- api.subscribe((newState) => {
36
- if (selfUpdate) {
37
- selfUpdate = false;
38
- return;
39
- }
40
- sourceData = newState.data;
41
- selfUpdate = true;
42
- applyPagination(api, sourceData, true);
32
+ api.registerTransform("pagination", 30, (data, state) => {
33
+ const { page, pageSize } = state.pagination;
34
+ const start = (page - 1) * pageSize;
35
+ return data.slice(start, start + pageSize);
43
36
  });
44
37
  api.on("pagination:next", () => {
45
38
  const state = api.getState();
46
- const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);
39
+ const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);
47
40
  if (state.pagination.page >= totalPages) return;
48
41
  const newPage = state.pagination.page + 1;
49
- const start = (newPage - 1) * state.pagination.pageSize;
50
- selfUpdate = true;
51
- api.setState(() => ({
52
- pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),
53
- data: sourceData.slice(start, start + state.pagination.pageSize)
54
- }));
42
+ api.recompute({
43
+ pagination: { ...state.pagination, page: newPage }
44
+ });
55
45
  });
56
46
  api.on("pagination:prev", () => {
57
47
  const state = api.getState();
58
48
  if (state.pagination.page <= 1) return;
59
49
  const newPage = state.pagination.page - 1;
60
- const start = (newPage - 1) * state.pagination.pageSize;
61
- selfUpdate = true;
62
- api.setState(() => ({
63
- pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),
64
- data: sourceData.slice(start, start + state.pagination.pageSize)
65
- }));
50
+ api.recompute({
51
+ pagination: { ...state.pagination, page: newPage }
52
+ });
66
53
  });
67
54
  api.on("pagination:goto", (payload) => {
68
55
  const { page } = payload;
69
56
  const state = api.getState();
70
- const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);
57
+ const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);
71
58
  const target = Math.max(1, Math.min(page, totalPages));
72
- const start = (target - 1) * state.pagination.pageSize;
73
- selfUpdate = true;
74
- api.setState(() => ({
75
- pagination: buildPagination(target, state.pagination.pageSize, sourceData.length),
76
- data: sourceData.slice(start, start + state.pagination.pageSize)
77
- }));
59
+ api.recompute({
60
+ pagination: { ...state.pagination, page: target }
61
+ });
78
62
  });
79
63
  api.on("pagination:pageSize", (payload) => {
80
64
  const { pageSize } = payload;
81
- selfUpdate = true;
82
- api.setState(() => ({
83
- pagination: buildPagination(1, pageSize, sourceData.length),
84
- data: sourceData.slice(0, pageSize)
85
- }));
65
+ const state = api.getState();
66
+ api.recompute({
67
+ pagination: { ...state.pagination, page: 1, pageSize }
68
+ });
86
69
  });
87
70
  }
88
71
  };
89
72
  }
90
- function buildPagination(page, pageSize, totalItems) {
91
- return { page, pageSize, totalItems };
92
- }
93
73
  function getTotalPages(totalCount, pageSize) {
94
74
  return Math.max(1, Math.ceil(totalCount / pageSize));
95
75
  }
96
- function applyPagination(api, sourceData, resetToPage1) {
97
- const state = api.getState();
98
- const page = resetToPage1 ? 1 : state.pagination.page;
99
- const { pageSize } = state.pagination;
100
- const start = (page - 1) * pageSize;
101
- const sliced = sourceData.slice(start, start + pageSize);
102
- api.setState(() => ({
103
- pagination: buildPagination(page, pageSize, sourceData.length),
104
- data: sliced
105
- }));
106
- }
107
76
  // Annotate the CommonJS export names for ESM import in node:
108
77
  0 && (module.exports = {
109
78
  createPaginationPlugin
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/pagination-plugin.ts"],"sourcesContent":["export { createPaginationPlugin } from './pagination-plugin';\n","import type { TrellisPlugin, TrellisAPI, DataRow, PaginationState } from '@trellisjs/core';\n\n/**\n * 建立分頁插件實例。\n * 管理資料的頁碼導航。\n * 會響應上游插件(排序、篩選)的資料變更並重新分頁。\n */\nexport function createPaginationPlugin(): TrellisPlugin {\n return {\n name: 'pagination',\n\n install(api: TrellisAPI) {\n // 儲存分頁前的來源資料(可能已排序/篩選)\n let sourceData: DataRow[] = [...api.getState().data];\n // 防止自己的 setState 觸發重複分頁\n let selfUpdate = false;\n\n // 套用初始分頁\n applyPagination(api, sourceData, true);\n\n // 訂閱狀態變更 — 當上游插件(sort/filter)改變 data 時重新分頁\n api.subscribe((newState) => {\n if (selfUpdate) {\n selfUpdate = false;\n return;\n }\n // 上游插件改變了資料 採用為新的來源並重設到第 1 頁\n sourceData = newState.data;\n selfUpdate = true;\n applyPagination(api, sourceData, true);\n });\n\n api.on('pagination:next', () => {\n const state = api.getState();\n const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);\n if (state.pagination.page >= totalPages) return;\n const newPage = state.pagination.page + 1;\n const start = (newPage - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:prev', () => {\n const state = api.getState();\n if (state.pagination.page <= 1) return;\n const newPage = state.pagination.page - 1;\n const start = (newPage - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:goto', (payload) => {\n const { page } = payload as { page: number };\n const state = api.getState();\n const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);\n const target = Math.max(1, Math.min(page, totalPages));\n const start = (target - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(target, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:pageSize', (payload) => {\n const { pageSize } = payload as { pageSize: number };\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(1, pageSize, sourceData.length),\n data: sourceData.slice(0, pageSize),\n }));\n });\n },\n };\n}\n\nfunction buildPagination(page: number, pageSize: number, totalItems: number): PaginationState {\n return { page, pageSize, totalItems };\n}\n\nfunction getTotalPages(totalCount: number, pageSize: number): number {\n return Math.max(1, Math.ceil(totalCount / pageSize));\n}\n\nfunction applyPagination(api: TrellisAPI, sourceData: DataRow[], resetToPage1?: boolean): void {\n const state = api.getState();\n const page = resetToPage1 ? 1 : state.pagination.page;\n const { pageSize } = state.pagination;\n const start = (page - 1) * pageSize;\n const sliced = sourceData.slice(start, start + pageSize);\n\n api.setState(() => ({\n pagination: buildPagination(page, pageSize, sourceData.length),\n data: sliced,\n }));\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/pagination-plugin.ts"],"sourcesContent":["export { createPaginationPlugin } from './pagination-plugin';\n","import type { TrellisPlugin, TrellisAPI } from '@trellisjs/core';\n\n/**\n * 建立分頁插件實例。\n * 管理資料的頁碼導航。\n * 透過管線轉換函式處理分頁切片。\n */\nexport function createPaginationPlugin(): TrellisPlugin {\n return {\n name: 'pagination',\n\n install(api: TrellisAPI) {\n // 註冊管線轉換函式 (priority=30)\n api.registerTransform('pagination', 30, (data, state) => {\n const { page, pageSize } = state.pagination;\n const start = (page - 1) * pageSize;\n return data.slice(start, start + pageSize);\n });\n\n api.on('pagination:next', () => {\n const state = api.getState();\n const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);\n if (state.pagination.page >= totalPages) return;\n const newPage = state.pagination.page + 1;\n api.recompute({\n pagination: { ...state.pagination, page: newPage },\n });\n });\n\n api.on('pagination:prev', () => {\n const state = api.getState();\n if (state.pagination.page <= 1) return;\n const newPage = state.pagination.page - 1;\n api.recompute({\n pagination: { ...state.pagination, page: newPage },\n });\n });\n\n api.on('pagination:goto', (payload) => {\n const { page } = payload as { page: number };\n const state = api.getState();\n const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);\n const target = Math.max(1, Math.min(page, totalPages));\n api.recompute({\n pagination: { ...state.pagination, page: target },\n });\n });\n\n api.on('pagination:pageSize', (payload) => {\n const { pageSize } = payload as { pageSize: number };\n const state = api.getState();\n api.recompute({\n pagination: { ...state.pagination, page: 1, pageSize },\n });\n });\n },\n };\n}\n\nfunction getTotalPages(totalCount: number, pageSize: number): number {\n return Math.max(1, Math.ceil(totalCount / pageSize));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,SAAS,yBAAwC;AACtD,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,QAAQ,KAAiB;AAEvB,UAAI,kBAAkB,cAAc,IAAI,CAAC,MAAM,UAAU;AACvD,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM;AACjC,cAAM,SAAS,OAAO,KAAK;AAC3B,eAAO,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAAA,MAC3C,CAAC;AAED,UAAI,GAAG,mBAAmB,MAAM;AAC9B,cAAM,QAAQ,IAAI,SAAS;AAC3B,cAAM,aAAa,cAAc,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AACvF,YAAI,MAAM,WAAW,QAAQ,WAAY;AACzC,cAAM,UAAU,MAAM,WAAW,OAAO;AACxC,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,QAAQ;AAAA,QACnD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,mBAAmB,MAAM;AAC9B,cAAM,QAAQ,IAAI,SAAS;AAC3B,YAAI,MAAM,WAAW,QAAQ,EAAG;AAChC,cAAM,UAAU,MAAM,WAAW,OAAO;AACxC,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,QAAQ;AAAA,QACnD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,mBAAmB,CAAC,YAAY;AACrC,cAAM,EAAE,KAAK,IAAI;AACjB,cAAM,QAAQ,IAAI,SAAS;AAC3B,cAAM,aAAa,cAAc,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AACvF,cAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC;AACrD,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,OAAO;AAAA,QAClD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,uBAAuB,CAAC,YAAY;AACzC,cAAM,EAAE,SAAS,IAAI;AACrB,cAAM,QAAQ,IAAI,SAAS;AAC3B,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,GAAG,SAAS;AAAA,QACvD,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,cAAc,YAAoB,UAA0B;AACnE,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,aAAa,QAAQ,CAAC;AACrD;","names":[]}
package/dist/index.mjs CHANGED
@@ -3,81 +3,50 @@ function createPaginationPlugin() {
3
3
  return {
4
4
  name: "pagination",
5
5
  install(api) {
6
- let sourceData = [...api.getState().data];
7
- let selfUpdate = false;
8
- applyPagination(api, sourceData, true);
9
- api.subscribe((newState) => {
10
- if (selfUpdate) {
11
- selfUpdate = false;
12
- return;
13
- }
14
- sourceData = newState.data;
15
- selfUpdate = true;
16
- applyPagination(api, sourceData, true);
6
+ api.registerTransform("pagination", 30, (data, state) => {
7
+ const { page, pageSize } = state.pagination;
8
+ const start = (page - 1) * pageSize;
9
+ return data.slice(start, start + pageSize);
17
10
  });
18
11
  api.on("pagination:next", () => {
19
12
  const state = api.getState();
20
- const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);
13
+ const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);
21
14
  if (state.pagination.page >= totalPages) return;
22
15
  const newPage = state.pagination.page + 1;
23
- const start = (newPage - 1) * state.pagination.pageSize;
24
- selfUpdate = true;
25
- api.setState(() => ({
26
- pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),
27
- data: sourceData.slice(start, start + state.pagination.pageSize)
28
- }));
16
+ api.recompute({
17
+ pagination: { ...state.pagination, page: newPage }
18
+ });
29
19
  });
30
20
  api.on("pagination:prev", () => {
31
21
  const state = api.getState();
32
22
  if (state.pagination.page <= 1) return;
33
23
  const newPage = state.pagination.page - 1;
34
- const start = (newPage - 1) * state.pagination.pageSize;
35
- selfUpdate = true;
36
- api.setState(() => ({
37
- pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),
38
- data: sourceData.slice(start, start + state.pagination.pageSize)
39
- }));
24
+ api.recompute({
25
+ pagination: { ...state.pagination, page: newPage }
26
+ });
40
27
  });
41
28
  api.on("pagination:goto", (payload) => {
42
29
  const { page } = payload;
43
30
  const state = api.getState();
44
- const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);
31
+ const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);
45
32
  const target = Math.max(1, Math.min(page, totalPages));
46
- const start = (target - 1) * state.pagination.pageSize;
47
- selfUpdate = true;
48
- api.setState(() => ({
49
- pagination: buildPagination(target, state.pagination.pageSize, sourceData.length),
50
- data: sourceData.slice(start, start + state.pagination.pageSize)
51
- }));
33
+ api.recompute({
34
+ pagination: { ...state.pagination, page: target }
35
+ });
52
36
  });
53
37
  api.on("pagination:pageSize", (payload) => {
54
38
  const { pageSize } = payload;
55
- selfUpdate = true;
56
- api.setState(() => ({
57
- pagination: buildPagination(1, pageSize, sourceData.length),
58
- data: sourceData.slice(0, pageSize)
59
- }));
39
+ const state = api.getState();
40
+ api.recompute({
41
+ pagination: { ...state.pagination, page: 1, pageSize }
42
+ });
60
43
  });
61
44
  }
62
45
  };
63
46
  }
64
- function buildPagination(page, pageSize, totalItems) {
65
- return { page, pageSize, totalItems };
66
- }
67
47
  function getTotalPages(totalCount, pageSize) {
68
48
  return Math.max(1, Math.ceil(totalCount / pageSize));
69
49
  }
70
- function applyPagination(api, sourceData, resetToPage1) {
71
- const state = api.getState();
72
- const page = resetToPage1 ? 1 : state.pagination.page;
73
- const { pageSize } = state.pagination;
74
- const start = (page - 1) * pageSize;
75
- const sliced = sourceData.slice(start, start + pageSize);
76
- api.setState(() => ({
77
- pagination: buildPagination(page, pageSize, sourceData.length),
78
- data: sliced
79
- }));
80
- }
81
50
  export {
82
51
  createPaginationPlugin
83
52
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pagination-plugin.ts"],"sourcesContent":["import type { TrellisPlugin, TrellisAPI, DataRow, PaginationState } from '@trellisjs/core';\n\n/**\n * 建立分頁插件實例。\n * 管理資料的頁碼導航。\n * 會響應上游插件(排序、篩選)的資料變更並重新分頁。\n */\nexport function createPaginationPlugin(): TrellisPlugin {\n return {\n name: 'pagination',\n\n install(api: TrellisAPI) {\n // 儲存分頁前的來源資料(可能已排序/篩選)\n let sourceData: DataRow[] = [...api.getState().data];\n // 防止自己的 setState 觸發重複分頁\n let selfUpdate = false;\n\n // 套用初始分頁\n applyPagination(api, sourceData, true);\n\n // 訂閱狀態變更 — 當上游插件(sort/filter)改變 data 時重新分頁\n api.subscribe((newState) => {\n if (selfUpdate) {\n selfUpdate = false;\n return;\n }\n // 上游插件改變了資料 採用為新的來源並重設到第 1 頁\n sourceData = newState.data;\n selfUpdate = true;\n applyPagination(api, sourceData, true);\n });\n\n api.on('pagination:next', () => {\n const state = api.getState();\n const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);\n if (state.pagination.page >= totalPages) return;\n const newPage = state.pagination.page + 1;\n const start = (newPage - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:prev', () => {\n const state = api.getState();\n if (state.pagination.page <= 1) return;\n const newPage = state.pagination.page - 1;\n const start = (newPage - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(newPage, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:goto', (payload) => {\n const { page } = payload as { page: number };\n const state = api.getState();\n const totalPages = getTotalPages(sourceData.length, state.pagination.pageSize);\n const target = Math.max(1, Math.min(page, totalPages));\n const start = (target - 1) * state.pagination.pageSize;\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(target, state.pagination.pageSize, sourceData.length),\n data: sourceData.slice(start, start + state.pagination.pageSize),\n }));\n });\n\n api.on('pagination:pageSize', (payload) => {\n const { pageSize } = payload as { pageSize: number };\n selfUpdate = true;\n api.setState(() => ({\n pagination: buildPagination(1, pageSize, sourceData.length),\n data: sourceData.slice(0, pageSize),\n }));\n });\n },\n };\n}\n\nfunction buildPagination(page: number, pageSize: number, totalItems: number): PaginationState {\n return { page, pageSize, totalItems };\n}\n\nfunction getTotalPages(totalCount: number, pageSize: number): number {\n return Math.max(1, Math.ceil(totalCount / pageSize));\n}\n\nfunction applyPagination(api: TrellisAPI, sourceData: DataRow[], resetToPage1?: boolean): void {\n const state = api.getState();\n const page = resetToPage1 ? 1 : state.pagination.page;\n const { pageSize } = state.pagination;\n const start = (page - 1) * pageSize;\n const sliced = sourceData.slice(start, start + pageSize);\n\n api.setState(() => ({\n pagination: buildPagination(page, pageSize, sourceData.length),\n data: sliced,\n }));\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/pagination-plugin.ts"],"sourcesContent":["import type { TrellisPlugin, TrellisAPI } from '@trellisjs/core';\n\n/**\n * 建立分頁插件實例。\n * 管理資料的頁碼導航。\n * 透過管線轉換函式處理分頁切片。\n */\nexport function createPaginationPlugin(): TrellisPlugin {\n return {\n name: 'pagination',\n\n install(api: TrellisAPI) {\n // 註冊管線轉換函式 (priority=30)\n api.registerTransform('pagination', 30, (data, state) => {\n const { page, pageSize } = state.pagination;\n const start = (page - 1) * pageSize;\n return data.slice(start, start + pageSize);\n });\n\n api.on('pagination:next', () => {\n const state = api.getState();\n const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);\n if (state.pagination.page >= totalPages) return;\n const newPage = state.pagination.page + 1;\n api.recompute({\n pagination: { ...state.pagination, page: newPage },\n });\n });\n\n api.on('pagination:prev', () => {\n const state = api.getState();\n if (state.pagination.page <= 1) return;\n const newPage = state.pagination.page - 1;\n api.recompute({\n pagination: { ...state.pagination, page: newPage },\n });\n });\n\n api.on('pagination:goto', (payload) => {\n const { page } = payload as { page: number };\n const state = api.getState();\n const totalPages = getTotalPages(state.pagination.totalItems, state.pagination.pageSize);\n const target = Math.max(1, Math.min(page, totalPages));\n api.recompute({\n pagination: { ...state.pagination, page: target },\n });\n });\n\n api.on('pagination:pageSize', (payload) => {\n const { pageSize } = payload as { pageSize: number };\n const state = api.getState();\n api.recompute({\n pagination: { ...state.pagination, page: 1, pageSize },\n });\n });\n },\n };\n}\n\nfunction getTotalPages(totalCount: number, pageSize: number): number {\n return Math.max(1, Math.ceil(totalCount / pageSize));\n}\n"],"mappings":";AAOO,SAAS,yBAAwC;AACtD,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,QAAQ,KAAiB;AAEvB,UAAI,kBAAkB,cAAc,IAAI,CAAC,MAAM,UAAU;AACvD,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM;AACjC,cAAM,SAAS,OAAO,KAAK;AAC3B,eAAO,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAAA,MAC3C,CAAC;AAED,UAAI,GAAG,mBAAmB,MAAM;AAC9B,cAAM,QAAQ,IAAI,SAAS;AAC3B,cAAM,aAAa,cAAc,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AACvF,YAAI,MAAM,WAAW,QAAQ,WAAY;AACzC,cAAM,UAAU,MAAM,WAAW,OAAO;AACxC,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,QAAQ;AAAA,QACnD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,mBAAmB,MAAM;AAC9B,cAAM,QAAQ,IAAI,SAAS;AAC3B,YAAI,MAAM,WAAW,QAAQ,EAAG;AAChC,cAAM,UAAU,MAAM,WAAW,OAAO;AACxC,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,QAAQ;AAAA,QACnD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,mBAAmB,CAAC,YAAY;AACrC,cAAM,EAAE,KAAK,IAAI;AACjB,cAAM,QAAQ,IAAI,SAAS;AAC3B,cAAM,aAAa,cAAc,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AACvF,cAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,UAAU,CAAC;AACrD,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,OAAO;AAAA,QAClD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,GAAG,uBAAuB,CAAC,YAAY;AACzC,cAAM,EAAE,SAAS,IAAI;AACrB,cAAM,QAAQ,IAAI,SAAS;AAC3B,YAAI,UAAU;AAAA,UACZ,YAAY,EAAE,GAAG,MAAM,YAAY,MAAM,GAAG,SAAS;AAAA,QACvD,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,cAAc,YAAoB,UAA0B;AACnE,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,aAAa,QAAQ,CAAC;AACrD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trellisjs/plugin-pagination",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "1.0.0",
4
4
  "description": "Trellis 分頁插件 — 基本頁碼導航",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,22 +20,27 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
- "scripts": {
24
- "build": "tsup",
25
- "dev": "tsup --watch",
26
- "test": "vitest run",
27
- "test:watch": "vitest",
28
- "clean": "rm -rf dist"
29
- },
30
23
  "peerDependencies": {
31
- "@trellisjs/core": "workspace:*"
24
+ "@trellisjs/core": "0.1.0"
32
25
  },
33
26
  "devDependencies": {
34
- "@trellisjs/core": "workspace:*",
35
27
  "tsup": "^8.4.0",
36
28
  "typescript": "^5.7.0",
37
- "vitest": "^3.1.0"
29
+ "vitest": "^3.1.0",
30
+ "@trellisjs/core": "0.1.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/cluion/trellis.git",
35
+ "directory": "packages/plugin-pagination"
38
36
  },
39
37
  "license": "MIT",
40
- "sideEffects": false
41
- }
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "clean": "rm -rf dist"
45
+ }
46
+ }