@saulpaulus17/vue-datatables-flex 1.0.4
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 +21 -0
- package/README.md +209 -0
- package/dist/favicon.ico +0 -0
- package/dist/nuxt.cjs.js +30 -0
- package/dist/nuxt.cjs.js.map +1 -0
- package/dist/nuxt.es.js +30 -0
- package/dist/nuxt.es.js.map +1 -0
- package/dist/runtime/components/MainDataTable.vue +341 -0
- package/dist/runtime/types/index.ts +153 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/nuxt.d.ts +8 -0
- package/dist/types/plugin.d.ts +7 -0
- package/dist/types/runtime/components/MainDataTable.d.ts +86 -0
- package/dist/types/runtime/types/index.d.ts +91 -0
- package/dist/vue-datatables-flex.cjs.js +6168 -0
- package/dist/vue-datatables-flex.cjs.js.map +1 -0
- package/dist/vue-datatables-flex.css +50 -0
- package/dist/vue-datatables-flex.es.js +6168 -0
- package/dist/vue-datatables-flex.es.js.map +1 -0
- package/package.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 saul paulus (Ixspx)
|
|
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,209 @@
|
|
|
1
|
+
# @saulpaulus17/vue-datatables-flex
|
|
2
|
+
|
|
3
|
+
A high-performance, responsive DataTable component for Vue 3 and Nuxt 3/4.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@saulpaulus17/vue-datatables-flex)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://vuejs.org/)
|
|
8
|
+
[](https://nuxt.com/)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Optimized performance using DataTables
|
|
15
|
+
- Clean UI with Bootstrap 5 styling
|
|
16
|
+
- Fully responsive layout
|
|
17
|
+
- Nuxt 3 & 4 module support
|
|
18
|
+
- TypeScript support
|
|
19
|
+
- SSR-friendly rendering
|
|
20
|
+
- Indonesian locale ready
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🚀 Installation
|
|
25
|
+
|
|
26
|
+
Install the package:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @saulpaulus17/vue-datatables-flex
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Required Peer Dependencies
|
|
33
|
+
|
|
34
|
+
Ensure your project has the following essentials installed:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install vue bootstrap datatables.net-bs5
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 📦 Usage in Vue 3
|
|
43
|
+
|
|
44
|
+
### Global Registration
|
|
45
|
+
|
|
46
|
+
Recommended for large applications using the component across multiple pages.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// main.ts
|
|
50
|
+
import { createApp } from "vue";
|
|
51
|
+
import { VueDatatablesFlex } from "@saulpaulus17/vue-datatables-flex";
|
|
52
|
+
import App from "./App.vue";
|
|
53
|
+
|
|
54
|
+
// Import Required CSS
|
|
55
|
+
import "bootstrap/dist/css/bootstrap.min.css";
|
|
56
|
+
import "datatables.net-bs5/css/dataTables.bootstrap5.min.css";
|
|
57
|
+
|
|
58
|
+
const app = createApp(App);
|
|
59
|
+
app.use(VueDatatablesFlex);
|
|
60
|
+
app.mount("#app");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Basic Usage
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<template>
|
|
67
|
+
<MainDataTable
|
|
68
|
+
:data="userRows"
|
|
69
|
+
:columns="userColumns"
|
|
70
|
+
scroll-y="500px"
|
|
71
|
+
@ready="onReady"
|
|
72
|
+
/>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { ref } from "vue";
|
|
77
|
+
import type { Column } from "@saulpaulus17/vue-datatables-flex";
|
|
78
|
+
|
|
79
|
+
const userColumns: Column[] = [
|
|
80
|
+
{ data: "id", title: "ID" },
|
|
81
|
+
{ data: "name", title: "Full Name" },
|
|
82
|
+
{ data: "email", title: "Email Address" },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const userRows = ref([
|
|
86
|
+
{ id: 1, name: "Ahmad", email: "ahmad@example.com" },
|
|
87
|
+
{ id: 2, name: "Budi", email: "budi@example.com" },
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const onReady = (dt: any) => console.log("Table instance:", dt);
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🟢 Usage in Nuxt 3 & 4
|
|
97
|
+
|
|
98
|
+
The simplest way is to use the provided Nuxt module. It handles everything including **Universal Rendering (SSR)**.
|
|
99
|
+
|
|
100
|
+
### 1. Add to Nuxt Config
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// nuxt.config.ts
|
|
104
|
+
export default defineNuxtConfig({
|
|
105
|
+
modules: ["@saulpaulus17/vue-datatables-flex/nuxt"],
|
|
106
|
+
|
|
107
|
+
// Optional: Module Configuration
|
|
108
|
+
vueDatatablesFlex: {
|
|
109
|
+
componentName: "MainDataTable", // Use a custom component name
|
|
110
|
+
addCss: true, // Automatically includes DataTables Bootstrap 5 CSS (default: true)
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// You still need to include Bootstrap 5 CSS
|
|
114
|
+
css: ["bootstrap/dist/css/bootstrap.min.css"],
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Implementation
|
|
119
|
+
|
|
120
|
+
```vue
|
|
121
|
+
<!-- pages/users.vue -->
|
|
122
|
+
<script setup lang="ts">
|
|
123
|
+
const columns = [{ data: "name", title: "Name" }];
|
|
124
|
+
const { data: users } = await useFetch("/api/users");
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<template>
|
|
128
|
+
<!-- No import needed! Component is auto-registered -->
|
|
129
|
+
<MainDataTable :data="users" :columns="columns" />
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> [!TIP]
|
|
134
|
+
> **Universal Rendering Support:**
|
|
135
|
+
> Version 0.1.4+ is fully SSR-aware. While the interactive DataTable initializes on the client, the component renders a semantic HTML table on the server. This ensures your data is **visible to search engines** and avoids Cumulative Layout Shift (CLS).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 🛠 API Reference
|
|
140
|
+
|
|
141
|
+
### Props
|
|
142
|
+
|
|
143
|
+
| Property | Type | Default | Description |
|
|
144
|
+
| :----------- | :----------------- | :------- | :--------------------------------------------------- |
|
|
145
|
+
| `data` | `unknown[]` | `[]` | The data array to display (Alias: `dataTableProps`). |
|
|
146
|
+
| `columns` | `Column[]` | `[]` | Column definitions (Alias: `columnsProps`). |
|
|
147
|
+
| `options` | `DataTableOptions` | `{}` | Advanced DataTables configuration overrides. |
|
|
148
|
+
| `loading` | `boolean` | `false` | Shows a beautiful animated loading overlay. |
|
|
149
|
+
| `responsive` | `boolean` | `false` | Enables DataTables Responsive extension. |
|
|
150
|
+
| `scrollY` | `string \| number` | `'65vh'` | Sets the vertical scroll height. |
|
|
151
|
+
| `tableClass` | `string` | `''` | Custom CSS class for the `<table>` element. |
|
|
152
|
+
|
|
153
|
+
### Events
|
|
154
|
+
|
|
155
|
+
| Event | Payload | Description |
|
|
156
|
+
| :-------- | :---------------- | :-------------------------------------------------------- |
|
|
157
|
+
| `@ready` | `(instance: Api)` | Fired when initialization is complete. |
|
|
158
|
+
| `@draw` | `(settings: any)` | Fired whenever the table is redrawn (sort, filter, page). |
|
|
159
|
+
| `@select` | `(items, type)` | Fired when rows are selected (requires `select: true`). |
|
|
160
|
+
| `@error` | `(err: Error)` | Fired if initialization fails. |
|
|
161
|
+
|
|
162
|
+
### Exposed Methods (Template Ref)
|
|
163
|
+
|
|
164
|
+
Access these by adding a `ref` to your component:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const table = ref<InstanceType<typeof MainDataTable>>();
|
|
168
|
+
|
|
169
|
+
// Methods
|
|
170
|
+
table.value?.getInstance(); // Get raw DataTables API instance
|
|
171
|
+
table.value?.reload(); // Reload AJAX data (if using server-side)
|
|
172
|
+
table.value?.adjustColumns(); // recalculate column widths
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🎨 Customizing Columns
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const columns: Column[] = [
|
|
181
|
+
{
|
|
182
|
+
data: "status",
|
|
183
|
+
title: "Status",
|
|
184
|
+
className: "text-center",
|
|
185
|
+
render: (data) => {
|
|
186
|
+
const color = data === "Active" ? "success" : "secondary";
|
|
187
|
+
return `<span class="badge bg-${color}">${data}</span>`;
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 🙋 Troubleshooting
|
|
196
|
+
|
|
197
|
+
**"JQuery is not defined"**
|
|
198
|
+
Ensure you are importing the Bootstrap integration correctly in your entry point: `import 'datatables.net-bs5';`.
|
|
199
|
+
|
|
200
|
+
**CSS Alignment Issues**
|
|
201
|
+
Ensure you've imported BOTH the Bootstrap 5 core CSS and the DataTables Bootstrap 5 styling in the correct order.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 📄 License
|
|
206
|
+
|
|
207
|
+
This package is open-sourced software licensed under the [MIT License](LICENSE).
|
|
208
|
+
|
|
209
|
+
Built with ❤️ for the Vue ecosystem.
|
package/dist/favicon.ico
ADDED
|
Binary file
|
package/dist/nuxt.cjs.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const kit = require("@nuxt/kit");
|
|
3
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
4
|
+
const nuxt = kit.defineNuxtModule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: "@saulpaulus17/vue-datatables-flex",
|
|
7
|
+
configKey: "vueDatatablesFlex",
|
|
8
|
+
compatibility: {
|
|
9
|
+
nuxt: ">=3.0.0"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
defaults: {
|
|
13
|
+
componentName: "MainDataTable",
|
|
14
|
+
addCss: true
|
|
15
|
+
},
|
|
16
|
+
setup(options, nuxt2) {
|
|
17
|
+
const resolver = kit.createResolver(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("nuxt.cjs.js", document.baseURI).href);
|
|
18
|
+
kit.addComponent({
|
|
19
|
+
name: options.componentName || "MainDataTable",
|
|
20
|
+
// Point langsung ke file .vue di folder runtime
|
|
21
|
+
filePath: resolver.resolve("./runtime/components/MainDataTable.vue")
|
|
22
|
+
});
|
|
23
|
+
if (options.addCss) {
|
|
24
|
+
nuxt2.options.css.push("datatables.net-bs5/css/dataTables.bootstrap5.min.css");
|
|
25
|
+
}
|
|
26
|
+
nuxt2.options.build.transpile.push("@saulpaulus17/vue-datatables-flex");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
module.exports = nuxt;
|
|
30
|
+
//# sourceMappingURL=nuxt.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nuxt.cjs.js","sources":["../src/nuxt.ts"],"sourcesContent":["import { defineNuxtModule, addComponent, createResolver } from '@nuxt/kit'\n\nexport interface ModuleOptions {\n /** Nama komponen global yang didaftarkan (default: \"MainDataTable\") */\n componentName?: string;\n /** Auto-import CSS Bootstrap DataTables */\n addCss?: boolean;\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: '@saulpaulus17/vue-datatables-flex',\n configKey: 'vueDatatablesFlex',\n compatibility: {\n nuxt: '>=3.0.0'\n }\n },\n defaults: {\n componentName: 'MainDataTable',\n addCss: true\n },\n setup(options: ModuleOptions, nuxt: any) {\n const resolver = createResolver(import.meta.url)\n\n // Daftarkan komponen secara global (Default: MainDataTable)\n addComponent({\n name: options.componentName || 'MainDataTable',\n // Point langsung ke file .vue di folder runtime\n filePath: resolver.resolve('./runtime/components/MainDataTable.vue'),\n })\n\n // Tambah CSS jika diminta\n if (options.addCss) {\n nuxt.options.css.push('datatables.net-bs5/css/dataTables.bootstrap5.min.css')\n }\n\n // Pastikan library di-transpile oleh Nuxt\n nuxt.options.build.transpile.push('@saulpaulus17/vue-datatables-flex')\n }\n})\n"],"names":["defineNuxtModule","nuxt","createResolver","addComponent"],"mappings":";;;AASA,MAAA,OAAeA,qBAAgC;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe;AAAA,MACb,MAAM;AAAA,IAAA;AAAA,EACR;AAAA,EAEF,UAAU;AAAA,IACR,eAAe;AAAA,IACf,QAAQ;AAAA,EAAA;AAAA,EAEV,MAAM,SAAwBC,OAAW;AACvC,UAAM,WAAWC,IAAAA,mQAA8B;AAG/CC,qBAAa;AAAA,MACX,MAAM,QAAQ,iBAAiB;AAAA;AAAA,MAE/B,UAAU,SAAS,QAAQ,wCAAwC;AAAA,IAAA,CACpE;AAGD,QAAI,QAAQ,QAAQ;AAClB,MAAAF,MAAK,QAAQ,IAAI,KAAK,sDAAsD;AAAA,IAC9E;AAGA,IAAAA,MAAK,QAAQ,MAAM,UAAU,KAAK,mCAAmC;AAAA,EACvE;AACF,CAAC;;"}
|
package/dist/nuxt.es.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addComponent } from "@nuxt/kit";
|
|
2
|
+
const nuxt = defineNuxtModule({
|
|
3
|
+
meta: {
|
|
4
|
+
name: "@saulpaulus17/vue-datatables-flex",
|
|
5
|
+
configKey: "vueDatatablesFlex",
|
|
6
|
+
compatibility: {
|
|
7
|
+
nuxt: ">=3.0.0"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
defaults: {
|
|
11
|
+
componentName: "MainDataTable",
|
|
12
|
+
addCss: true
|
|
13
|
+
},
|
|
14
|
+
setup(options, nuxt2) {
|
|
15
|
+
const resolver = createResolver(import.meta.url);
|
|
16
|
+
addComponent({
|
|
17
|
+
name: options.componentName || "MainDataTable",
|
|
18
|
+
// Point langsung ke file .vue di folder runtime
|
|
19
|
+
filePath: resolver.resolve("./runtime/components/MainDataTable.vue")
|
|
20
|
+
});
|
|
21
|
+
if (options.addCss) {
|
|
22
|
+
nuxt2.options.css.push("datatables.net-bs5/css/dataTables.bootstrap5.min.css");
|
|
23
|
+
}
|
|
24
|
+
nuxt2.options.build.transpile.push("@saulpaulus17/vue-datatables-flex");
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
export {
|
|
28
|
+
nuxt as default
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=nuxt.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nuxt.es.js","sources":["../src/nuxt.ts"],"sourcesContent":["import { defineNuxtModule, addComponent, createResolver } from '@nuxt/kit'\n\nexport interface ModuleOptions {\n /** Nama komponen global yang didaftarkan (default: \"MainDataTable\") */\n componentName?: string;\n /** Auto-import CSS Bootstrap DataTables */\n addCss?: boolean;\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: '@saulpaulus17/vue-datatables-flex',\n configKey: 'vueDatatablesFlex',\n compatibility: {\n nuxt: '>=3.0.0'\n }\n },\n defaults: {\n componentName: 'MainDataTable',\n addCss: true\n },\n setup(options: ModuleOptions, nuxt: any) {\n const resolver = createResolver(import.meta.url)\n\n // Daftarkan komponen secara global (Default: MainDataTable)\n addComponent({\n name: options.componentName || 'MainDataTable',\n // Point langsung ke file .vue di folder runtime\n filePath: resolver.resolve('./runtime/components/MainDataTable.vue'),\n })\n\n // Tambah CSS jika diminta\n if (options.addCss) {\n nuxt.options.css.push('datatables.net-bs5/css/dataTables.bootstrap5.min.css')\n }\n\n // Pastikan library di-transpile oleh Nuxt\n nuxt.options.build.transpile.push('@saulpaulus17/vue-datatables-flex')\n }\n})\n"],"names":["nuxt"],"mappings":";AASA,MAAA,OAAe,iBAAgC;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe;AAAA,MACb,MAAM;AAAA,IAAA;AAAA,EACR;AAAA,EAEF,UAAU;AAAA,IACR,eAAe;AAAA,IACf,QAAQ;AAAA,EAAA;AAAA,EAEV,MAAM,SAAwBA,OAAW;AACvC,UAAM,WAAW,eAAe,YAAY,GAAG;AAG/C,iBAAa;AAAA,MACX,MAAM,QAAQ,iBAAiB;AAAA;AAAA,MAE/B,UAAU,SAAS,QAAQ,wCAAwC;AAAA,IAAA,CACpE;AAGD,QAAI,QAAQ,QAAQ;AAClB,MAAAA,MAAK,QAAQ,IAAI,KAAK,sDAAsD;AAAA,IAC9E;AAGA,IAAAA,MAAK,QAAQ,MAAM,UAAU,KAAK,mCAAmC;AAAA,EACvE;AACF,CAAC;"}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
4
|
+
import DataTableSource from "datatables.net-vue3";
|
|
5
|
+
import DataTablesLib from "datatables.net-bs5";
|
|
6
|
+
import "datatables.net-responsive-bs5";
|
|
7
|
+
import "datatables.net-select-bs5";
|
|
8
|
+
|
|
9
|
+
import type { Api } from "datatables.net";
|
|
10
|
+
import type { Column, DataTableOptions } from "../types/index";
|
|
11
|
+
import { defaultOptions } from "../types/index";
|
|
12
|
+
|
|
13
|
+
// Datatables.net component: casting to any avoids leaky types (TS4058 / JQueryDataTables)
|
|
14
|
+
const DataTableComponent = DataTableSource as any;
|
|
15
|
+
|
|
16
|
+
const isMounted = ref(false);
|
|
17
|
+
|
|
18
|
+
// Konfigurasi DataTables: Register core dan extension
|
|
19
|
+
if (typeof window !== "undefined") {
|
|
20
|
+
DataTableSource.use(DataTablesLib);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Datatables.net menggunakan API yang sangat dinamis (any is unavoidable here)
|
|
24
|
+
// Semua interaksi publik (props/emits) sudah ditype dengan benar di DataTableMain.vue.d.ts
|
|
25
|
+
|
|
26
|
+
// ===================================================
|
|
27
|
+
// Props
|
|
28
|
+
// ===================================================
|
|
29
|
+
interface Props {
|
|
30
|
+
/** Data array untuk tabel (Alias: dataTableProps) */
|
|
31
|
+
data?: unknown[];
|
|
32
|
+
/** Data array untuk tabel (Prop name dari user snippet) */
|
|
33
|
+
dataTableProps?: unknown[];
|
|
34
|
+
/** Definisi kolom (Alias: columnsProps) */
|
|
35
|
+
columns?: Column[];
|
|
36
|
+
/** Definisi kolom (Prop name dari user snippet) */
|
|
37
|
+
columnsProps?: Column[];
|
|
38
|
+
/** Override DataTable options */
|
|
39
|
+
options?: DataTableOptions;
|
|
40
|
+
/** Tinggi scroll vertikal (mis: "65vh", "400px") */
|
|
41
|
+
scrollY?: string | number | false;
|
|
42
|
+
/** Aktifkan horizontal scroll */
|
|
43
|
+
scrollX?: boolean;
|
|
44
|
+
/** Mode responsive (disable scrollX jika aktif) */
|
|
45
|
+
responsive?: boolean;
|
|
46
|
+
/** Class CSS tambahan untuk wrapper div */
|
|
47
|
+
wrapperClass?: string;
|
|
48
|
+
/** Class CSS tambahan untuk elemen table */
|
|
49
|
+
tableClass?: string;
|
|
50
|
+
/** Loading state — tampilkan overlay loading */
|
|
51
|
+
loading?: boolean;
|
|
52
|
+
/** Teks loading */
|
|
53
|
+
loadingText?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
57
|
+
data: () => [],
|
|
58
|
+
dataTableProps: () => [],
|
|
59
|
+
columns: () => [],
|
|
60
|
+
columnsProps: () => [],
|
|
61
|
+
options: () => ({}),
|
|
62
|
+
scrollY: "65vh",
|
|
63
|
+
scrollX: true,
|
|
64
|
+
responsive: false,
|
|
65
|
+
wrapperClass: "",
|
|
66
|
+
tableClass: "",
|
|
67
|
+
loading: false,
|
|
68
|
+
loadingText: "Memuat data...",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Compute internal data and columns from aliases
|
|
72
|
+
const internalData = computed(() =>
|
|
73
|
+
props.data && props.data.length > 0 ? props.data : props.dataTableProps,
|
|
74
|
+
);
|
|
75
|
+
const internalColumns = computed(() =>
|
|
76
|
+
props.columns && props.columns.length > 0 ? props.columns : props.columnsProps,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// ===================================================
|
|
80
|
+
// Emits
|
|
81
|
+
// ===================================================
|
|
82
|
+
const emit = defineEmits<{
|
|
83
|
+
/** DataTable selesai diinisialisasi */
|
|
84
|
+
ready: [instance: unknown];
|
|
85
|
+
/** Tabel selesai di-draw */
|
|
86
|
+
draw: [settings: unknown];
|
|
87
|
+
/** Baris di-select */
|
|
88
|
+
select: [items: unknown[], type: string];
|
|
89
|
+
/** Baris di-deselect */
|
|
90
|
+
deselect: [items: unknown[], type: string];
|
|
91
|
+
/** Error saat inisialisasi */
|
|
92
|
+
error: [err: Error];
|
|
93
|
+
}>();
|
|
94
|
+
|
|
95
|
+
// ===================================================
|
|
96
|
+
// Refs (datatables API requires any)
|
|
97
|
+
// ===================================================
|
|
98
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
const datatableRef = ref<{ dt: any } | null>(null);
|
|
101
|
+
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
let dtInstance: Api<any> | null = null;
|
|
104
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
105
|
+
|
|
106
|
+
// ===================================================
|
|
107
|
+
// Computed Options — merge defaultOptions + props
|
|
108
|
+
// ===================================================
|
|
109
|
+
const tableOptions = computed<DataTableOptions>(() => ({
|
|
110
|
+
...defaultOptions,
|
|
111
|
+
scrollY: props.scrollY,
|
|
112
|
+
scrollX: props.responsive ? false : props.scrollX,
|
|
113
|
+
responsive: props.responsive,
|
|
114
|
+
autoWidth: false,
|
|
115
|
+
deferRender: true,
|
|
116
|
+
...props.options,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// ===================================================
|
|
120
|
+
// Init
|
|
121
|
+
// ===================================================
|
|
122
|
+
onMounted(async () => {
|
|
123
|
+
isMounted.value = true;
|
|
124
|
+
await nextTick();
|
|
125
|
+
|
|
126
|
+
if (!datatableRef.value) return;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
dtInstance = datatableRef.value.dt;
|
|
130
|
+
if (!dtInstance) return;
|
|
131
|
+
|
|
132
|
+
// Adjust columns setelah mount
|
|
133
|
+
dtInstance.columns.adjust();
|
|
134
|
+
|
|
135
|
+
// Event: ready
|
|
136
|
+
emit("ready", dtInstance);
|
|
137
|
+
|
|
138
|
+
// Event: draw
|
|
139
|
+
dtInstance.on("draw", (e: Event, settings: unknown) => {
|
|
140
|
+
emit("draw", settings);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Event: select / deselect (jika pakai select extension)
|
|
144
|
+
dtInstance.on("select", (_e: Event, _dt: unknown, type: string, indexes: number[]) => {
|
|
145
|
+
if (!dtInstance) return;
|
|
146
|
+
const rows = dtInstance.rows(indexes).data().toArray();
|
|
147
|
+
emit("select", rows, type);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
dtInstance.on("deselect", (_e: Event, _dt: unknown, type: string, indexes: number[]) => {
|
|
151
|
+
if (!dtInstance) return;
|
|
152
|
+
const rows = dtInstance.rows(indexes).data().toArray();
|
|
153
|
+
emit("deselect", rows, type);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ResizeObserver untuk handle container resize
|
|
157
|
+
if (containerRef.value) {
|
|
158
|
+
resizeObserver = new ResizeObserver(() => {
|
|
159
|
+
if (!dtInstance) return;
|
|
160
|
+
dtInstance.columns.adjust();
|
|
161
|
+
setTimeout(() => dtInstance?.columns.adjust(), 150);
|
|
162
|
+
setTimeout(() => dtInstance?.columns.adjust(), 400);
|
|
163
|
+
});
|
|
164
|
+
resizeObserver.observe(containerRef.value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
window.addEventListener("resize", handleResize);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ===================================================
|
|
174
|
+
// Resize Handler
|
|
175
|
+
// ===================================================
|
|
176
|
+
const handleResize = () => {
|
|
177
|
+
dtInstance?.columns.adjust();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// ===================================================
|
|
181
|
+
// Watch: Data changes
|
|
182
|
+
// ===================================================
|
|
183
|
+
watch(
|
|
184
|
+
() => internalData.value,
|
|
185
|
+
async (newData) => {
|
|
186
|
+
if (!dtInstance) return;
|
|
187
|
+
await nextTick();
|
|
188
|
+
|
|
189
|
+
dtInstance.clear();
|
|
190
|
+
|
|
191
|
+
if (Array.isArray(newData) && newData.length > 0) {
|
|
192
|
+
dtInstance.rows.add(newData);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
dtInstance.draw(false);
|
|
196
|
+
dtInstance.columns.adjust();
|
|
197
|
+
|
|
198
|
+
if (props.responsive) {
|
|
199
|
+
dtInstance.responsive?.recalc();
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{ deep: false },
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// ===================================================
|
|
206
|
+
// Expose public API
|
|
207
|
+
// ===================================================
|
|
208
|
+
defineExpose({
|
|
209
|
+
/** Dapatkan DataTables instance untuk akses API langsung */
|
|
210
|
+
getInstance: (): Api<any> | null => dtInstance,
|
|
211
|
+
/** Reload data secara manual */
|
|
212
|
+
reload: () => {
|
|
213
|
+
if (!dtInstance) return;
|
|
214
|
+
dtInstance.ajax?.reload(undefined, false);
|
|
215
|
+
},
|
|
216
|
+
/** Clear & redraw tabel */
|
|
217
|
+
redraw: () => {
|
|
218
|
+
if (!dtInstance) return;
|
|
219
|
+
dtInstance.draw(false);
|
|
220
|
+
},
|
|
221
|
+
/** Adjust column widths */
|
|
222
|
+
adjustColumns: () => {
|
|
223
|
+
dtInstance?.columns.adjust();
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ===================================================
|
|
228
|
+
// Cleanup
|
|
229
|
+
// ===================================================
|
|
230
|
+
onBeforeUnmount(() => {
|
|
231
|
+
window.removeEventListener("resize", handleResize);
|
|
232
|
+
|
|
233
|
+
if (dtInstance) {
|
|
234
|
+
dtInstance.off("draw");
|
|
235
|
+
dtInstance.off("select");
|
|
236
|
+
dtInstance.off("deselect");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
resizeObserver?.disconnect();
|
|
240
|
+
resizeObserver = null;
|
|
241
|
+
|
|
242
|
+
dtInstance?.destroy(true);
|
|
243
|
+
dtInstance = null;
|
|
244
|
+
});
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<template>
|
|
248
|
+
<div ref="containerRef" :class="['dt-wrapper', wrapperClass]" style="position: relative">
|
|
249
|
+
<!-- Loading Overlay -->
|
|
250
|
+
<Transition name="dt-fade">
|
|
251
|
+
<div v-if="loading" class="dt-loading-overlay">
|
|
252
|
+
<div class="dt-loading-spinner" />
|
|
253
|
+
<span class="dt-loading-text">{{ loadingText }}</span>
|
|
254
|
+
</div>
|
|
255
|
+
</Transition>
|
|
256
|
+
|
|
257
|
+
<DataTableComponent
|
|
258
|
+
v-if="isMounted"
|
|
259
|
+
ref="datatableRef"
|
|
260
|
+
:class="['table', 'table-hover', 'table-bordered', 'table-sm', 'w-100', tableClass]"
|
|
261
|
+
:columns="internalColumns as any"
|
|
262
|
+
:data="internalData"
|
|
263
|
+
:options="tableOptions as unknown as any"
|
|
264
|
+
>
|
|
265
|
+
<slot />
|
|
266
|
+
</DataTableComponent>
|
|
267
|
+
|
|
268
|
+
<!-- SSR Fallback: Render a simple table to avoid layout shift -->
|
|
269
|
+
<table
|
|
270
|
+
v-else
|
|
271
|
+
:class="['table', 'table-hover', 'table-bordered', 'table-sm', 'w-100', 'dataTable', tableClass]"
|
|
272
|
+
>
|
|
273
|
+
<thead>
|
|
274
|
+
<tr>
|
|
275
|
+
<th v-for="col in internalColumns" :key="col.data || col.title">
|
|
276
|
+
{{ col.title }}
|
|
277
|
+
</th>
|
|
278
|
+
</tr>
|
|
279
|
+
</thead>
|
|
280
|
+
<tbody>
|
|
281
|
+
<slot />
|
|
282
|
+
</tbody>
|
|
283
|
+
</table>
|
|
284
|
+
</div>
|
|
285
|
+
</template>
|
|
286
|
+
|
|
287
|
+
<style scoped>
|
|
288
|
+
.dt-wrapper {
|
|
289
|
+
width: 100%;
|
|
290
|
+
overflow: hidden;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ---- Loading Overlay ---- */
|
|
294
|
+
.dt-loading-overlay {
|
|
295
|
+
position: absolute;
|
|
296
|
+
inset: 0;
|
|
297
|
+
z-index: 10;
|
|
298
|
+
display: flex;
|
|
299
|
+
flex-direction: column;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
gap: var(--space-3);
|
|
303
|
+
background: rgba(255, 255, 255, 0.7);
|
|
304
|
+
backdrop-filter: blur(4px);
|
|
305
|
+
border-radius: var(--radius-lg);
|
|
306
|
+
transition: var(--transition);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.dt-loading-spinner {
|
|
310
|
+
width: 40px;
|
|
311
|
+
height: 40px;
|
|
312
|
+
border: 3px solid var(--bg-mute);
|
|
313
|
+
border-top-color: var(--primary);
|
|
314
|
+
border-radius: 50%;
|
|
315
|
+
animation: dt-spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.dt-loading-text {
|
|
319
|
+
font-size: var(--fs-sm);
|
|
320
|
+
color: var(--text-soft);
|
|
321
|
+
font-weight: var(--fw-medium);
|
|
322
|
+
letter-spacing: 0.025em;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@keyframes dt-spin {
|
|
326
|
+
to {
|
|
327
|
+
transform: rotate(360deg);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* ---- Fade Transition ---- */
|
|
332
|
+
.dt-fade-enter-active,
|
|
333
|
+
.dt-fade-leave-active {
|
|
334
|
+
transition: opacity 0.3s ease;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.dt-fade-enter-from,
|
|
338
|
+
.dt-fade-leave-to {
|
|
339
|
+
opacity: 0;
|
|
340
|
+
}
|
|
341
|
+
</style>
|