@cocoar/vue-ui 3.1.0 → 3.2.0-beta.11
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/bin/cli.mjs +53 -0
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
- package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
- package/dist/components/data-list/data-list-builder.d.ts +288 -0
- package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
- package/dist/components/data-list/index.d.ts +12 -0
- package/dist/components/data-list/index.d.ts.map +1 -0
- package/dist/components/data-list/internal/compare.d.ts +14 -0
- package/dist/components/data-list/internal/compare.d.ts.map +1 -0
- package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
- package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
- package/dist/components/data-list/internal/search.d.ts +16 -0
- package/dist/components/data-list/internal/search.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
- package/dist/components/data-list/types.d.ts +185 -0
- package/dist/components/data-list/types.d.ts.map +1 -0
- package/dist/components/data-list/useDataListModel.d.ts +79 -0
- package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
- package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
- package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
- package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
- package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
- package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
- package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
- package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
- package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
- package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
- package/dist/composables/dragRegistry.d.ts +12 -0
- package/dist/composables/dragRegistry.d.ts.map +1 -1
- package/dist/composables/index.d.ts +2 -2
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDragDrop.d.ts +62 -6
- package/dist/composables/useDragDrop.d.ts.map +1 -1
- package/dist/composables/useVirtualList.d.ts +21 -1
- package/dist/composables/useVirtualList.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4508 -2039
- package/package.json +9 -3
- package/skills/cocoar-vue-ui/SKILL.md +244 -0
- package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
- package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
- package/skills/cocoar-vue-ui/references/components/button.md +252 -0
- package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +372 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1647 -0
- package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
- package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
- package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +486 -0
- package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
- package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
- package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
- package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
- package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
- package/skills/cocoar-vue-ui/references/components/card.md +151 -0
- package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
- package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
- package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
- package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
- package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
- package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
- package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
- package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
- package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
- package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
- package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
- package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
- package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
- package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
- package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
- package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
- package/skills/cocoar-vue-ui/references/components/link.md +235 -0
- package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
- package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
- package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
- package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
- package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
- package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
- package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
- package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
- package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
- package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
- package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
- package/skills/cocoar-vue-ui/references/components/note.md +96 -0
- package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
- package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
- package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
- package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
- package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
- package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
- package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
- package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
- package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
- package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
- package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
- package/skills/cocoar-vue-ui/references/components/select.md +463 -0
- package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
- package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
- package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
- package/skills/cocoar-vue-ui/references/components/table.md +170 -0
- package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
- package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
- package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
- package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
- package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
- package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
- package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
- package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
- package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
- package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
- package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
- package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
- package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
- package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
- package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
- package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
- package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
- package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
- package/skills/cocoar-vue-ui/references/guide/getting-started.md +116 -0
- package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
- package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
- package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/data-grid.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# Data Grid
|
|
4
|
+
|
|
5
|
+
A powerful data grid built on AG Grid with Cocoar theming. Configure columns, sorting, selection, and cell renderers through a fluent builder API — no raw AG Grid config needed.
|
|
6
|
+
|
|
7
|
+
> **Info: Separate Package**
|
|
8
|
+
>
|
|
9
|
+
> The Data Grid depends on AG Grid. Install it separately:
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @cocoar/vue-data-grid ag-grid-community ag-grid-vue3
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
Define columns with `.field()`, `.header()`, and `.flex()` / `.width()`. Pass row data with `.rowData()`.
|
|
21
|
+
|
|
22
|
+
**Demo — `data-grid/demos/GridBasic.vue`**
|
|
23
|
+
|
|
24
|
+
```vue
|
|
25
|
+
<template>
|
|
26
|
+
<div style="height: 350px;">
|
|
27
|
+
<CoarDataGrid :builder="builder" />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
33
|
+
|
|
34
|
+
interface User {
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
role: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const data: User[] = [
|
|
41
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer' },
|
|
42
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Designer' },
|
|
43
|
+
{ name: 'Carol Williams', email: 'carol@example.com', role: 'Manager' },
|
|
44
|
+
{ name: 'David Brown', email: 'david@example.com', role: 'Engineer' },
|
|
45
|
+
{ name: 'Eve Davis', email: 'eve@example.com', role: 'Designer' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const builder = CoarGridBuilder.create<User>()
|
|
49
|
+
.columns([
|
|
50
|
+
(col) => col.field('name').header('Name').flex(1),
|
|
51
|
+
(col) => col.field('email').header('Email').flex(1),
|
|
52
|
+
(col) => col.field('role').header('Role').width(120),
|
|
53
|
+
])
|
|
54
|
+
.rowData(data);
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Appearance
|
|
59
|
+
|
|
60
|
+
Add a border or elevation shadow to the grid. Toggle the checkboxes to see the effect.
|
|
61
|
+
|
|
62
|
+
**Demo — `data-grid/demos/GridAppearance.vue`**
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<template>
|
|
66
|
+
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
67
|
+
<div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
|
|
68
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
69
|
+
<input type="checkbox" v-model="showTitle" /> Title (left)
|
|
70
|
+
</label>
|
|
71
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
72
|
+
<input type="checkbox" v-model="showSearch" /> Search
|
|
73
|
+
</label>
|
|
74
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
75
|
+
<input type="checkbox" v-model="showActions" /> Actions (right)
|
|
76
|
+
</label>
|
|
77
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
78
|
+
<input type="checkbox" v-model="bordered" /> Bordered
|
|
79
|
+
</label>
|
|
80
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
81
|
+
<input type="checkbox" v-model="elevated" /> Elevated
|
|
82
|
+
</label>
|
|
83
|
+
</div>
|
|
84
|
+
<div style="height: 300px;">
|
|
85
|
+
<CoarDataGrid
|
|
86
|
+
:builder="builder"
|
|
87
|
+
:show-search="showSearch"
|
|
88
|
+
:bordered="bordered"
|
|
89
|
+
:elevated="elevated"
|
|
90
|
+
search-placeholder="Search users..."
|
|
91
|
+
>
|
|
92
|
+
<template v-if="showTitle" #toolbar-left>
|
|
93
|
+
<span style="font-weight: 600; white-space: nowrap;">User List</span>
|
|
94
|
+
</template>
|
|
95
|
+
<template v-if="showActions" #toolbar-right>
|
|
96
|
+
<CoarButton size="s" variant="secondary">Export</CoarButton>
|
|
97
|
+
<CoarButton size="s">Add User</CoarButton>
|
|
98
|
+
</template>
|
|
99
|
+
</CoarDataGrid>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<script setup lang="ts">
|
|
105
|
+
import { ref } from 'vue';
|
|
106
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
107
|
+
import { CoarButton } from '@cocoar/vue-ui';
|
|
108
|
+
|
|
109
|
+
const showTitle = ref(false);
|
|
110
|
+
const showSearch = ref(true);
|
|
111
|
+
const showActions = ref(true);
|
|
112
|
+
const bordered = ref(true);
|
|
113
|
+
const elevated = ref(false);
|
|
114
|
+
|
|
115
|
+
interface User {
|
|
116
|
+
name: string;
|
|
117
|
+
email: string;
|
|
118
|
+
role: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const data: User[] = [
|
|
122
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer' },
|
|
123
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Designer' },
|
|
124
|
+
{ name: 'Carol Williams', email: 'carol@example.com', role: 'Manager' },
|
|
125
|
+
{ name: 'David Brown', email: 'david@example.com', role: 'Engineer' },
|
|
126
|
+
{ name: 'Eve Davis', email: 'eve@example.com', role: 'Designer' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const builder = CoarGridBuilder.create<User>()
|
|
130
|
+
.columns([
|
|
131
|
+
(col) => col.field('name').header('Name').flex(1),
|
|
132
|
+
(col) => col.field('email').header('Email').flex(1),
|
|
133
|
+
(col) => col.field('role').header('Role').width(120),
|
|
134
|
+
])
|
|
135
|
+
.rowData(data);
|
|
136
|
+
</script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Dark mode
|
|
140
|
+
|
|
141
|
+
Dark styles ship with the package. The theme maps AG Grid's variables onto the semantic `--coar-*` tokens (with hardcoded fallbacks), so the grid follows your design-system theme in both modes. Dark values activate via the `.dark-mode` class — on `<html>` or any ancestor (the Cocoar convention, same as `@cocoar/vue-ui`), or directly on the grid element. `[data-theme="dark"]` is **not** a trigger here.
|
|
142
|
+
|
|
143
|
+
## Column Types
|
|
144
|
+
|
|
145
|
+
Built-in renderers for dates, numbers, currency, tags, and icons — no custom cell components needed. Date, number, and currency columns are locale-aware and update reactively when the locale changes. Try the locale switcher in the nav bar.
|
|
146
|
+
|
|
147
|
+
**Demo — `data-grid/demos/GridColumnTypes.vue`**
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<template>
|
|
151
|
+
<div style="height: 350px;">
|
|
152
|
+
<CoarDataGrid :builder="builder" />
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<script setup lang="ts">
|
|
157
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
158
|
+
|
|
159
|
+
interface Employee {
|
|
160
|
+
name: string;
|
|
161
|
+
joinDate: string;
|
|
162
|
+
salary: number;
|
|
163
|
+
hours: number;
|
|
164
|
+
status: 'active' | 'inactive' | 'pending';
|
|
165
|
+
icon: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const data: Employee[] = [
|
|
169
|
+
{ name: 'Alice Johnson', joinDate: '2022-03-15', salary: 95000, hours: 1420.5, status: 'active', icon: 'user' },
|
|
170
|
+
{ name: 'Bob Smith', joinDate: '2021-07-01', salary: 85000, hours: 1105, status: 'active', icon: 'palette' },
|
|
171
|
+
{ name: 'Carol Williams', joinDate: '2020-01-20', salary: 110000, hours: 2250.75, status: 'inactive', icon: 'users' },
|
|
172
|
+
{ name: 'David Brown', joinDate: '2023-06-10', salary: 90000, hours: 870.25, status: 'pending', icon: 'user' },
|
|
173
|
+
{ name: 'Eve Davis', joinDate: '2022-11-05', salary: 88000, hours: 1680, status: 'active', icon: 'palette' },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const builder = CoarGridBuilder.create<Employee>()
|
|
177
|
+
.columns([
|
|
178
|
+
(col) => col.field('name').header('Name').flex(1).sortable(),
|
|
179
|
+
(col) => col.date('joinDate').header('Joined').width(150),
|
|
180
|
+
(col) => col.currency('salary', { currencyCode: 'EUR' }).header('Salary').width(140),
|
|
181
|
+
(col) => col.number('hours', { decimals: 1 }).header('Hours').width(110),
|
|
182
|
+
(col) => col.tag('status', {
|
|
183
|
+
variantMap: { active: 'success', inactive: 'error', pending: 'warning' },
|
|
184
|
+
}).header('Status').width(130),
|
|
185
|
+
(col) => col.icon('icon', { size: 's' }).header('Type').fixedWidth(60),
|
|
186
|
+
])
|
|
187
|
+
.rowData(data);
|
|
188
|
+
</script>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| Method | Description |
|
|
192
|
+
|--------|-------------|
|
|
193
|
+
| `.field(name)` | Plain text column |
|
|
194
|
+
| `.date(field, config?)` | Locale-aware date display |
|
|
195
|
+
| `.number(field, config?)` | Locale-aware number display |
|
|
196
|
+
| `.currency(field, config?)` | Locale-aware currency display |
|
|
197
|
+
| `.tag(field, config)` | Renders a `CoarTag` with variant mapping or custom colors |
|
|
198
|
+
| `.icon(field, config?)` | Renders a `CoarIcon` |
|
|
199
|
+
| `.wrap(inner)` | Wraps any column builder with left/right decoration slots |
|
|
200
|
+
|
|
201
|
+
## Wrapper Column
|
|
202
|
+
|
|
203
|
+
Decorate any column with left and/or right slots — perfect for status indicators, action icons, or inline badges. The inner column keeps all its behavior (sort, filter, edit, `valueFormatter`, custom `cellRenderer`, …); only rendering gets an extra frame around it.
|
|
204
|
+
|
|
205
|
+
**Demo — `data-grid/demos/GridWrapperColumn.vue`**
|
|
206
|
+
|
|
207
|
+
```vue
|
|
208
|
+
<template>
|
|
209
|
+
<div style="height: 420px;">
|
|
210
|
+
<CoarDataGrid :builder="builder" />
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
213
|
+
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref, defineComponent, h } from 'vue';
|
|
216
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
217
|
+
import { CoarBadge, CoarTag, CoarIcon } from '@cocoar/vue-ui';
|
|
218
|
+
|
|
219
|
+
interface Message {
|
|
220
|
+
id: number;
|
|
221
|
+
subject: string;
|
|
222
|
+
sender: string;
|
|
223
|
+
starred: boolean;
|
|
224
|
+
unread: number;
|
|
225
|
+
isCritical: boolean;
|
|
226
|
+
awaitingFeedback: boolean;
|
|
227
|
+
priority: 'low' | 'normal' | 'high';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const rows = ref<Message[]>([
|
|
231
|
+
{ id: 1, subject: 'Q4 budget review', sender: 'Alice', starred: true, unread: 3, isCritical: true, awaitingFeedback: false, priority: 'high' },
|
|
232
|
+
{ id: 2, subject: 'Design handoff — checkout', sender: 'Bob', starred: false, unread: 0, isCritical: false, awaitingFeedback: true, priority: 'normal' },
|
|
233
|
+
{ id: 3, subject: 'Release notes draft', sender: 'Carol', starred: false, unread: 12, isCritical: false, awaitingFeedback: false, priority: 'low' },
|
|
234
|
+
{ id: 4, subject: 'Offsite logistics', sender: 'David', starred: true, unread: 1, isCritical: true, awaitingFeedback: true, priority: 'high' },
|
|
235
|
+
{ id: 5, subject: 'Customer escalation #42', sender: 'Eve', starred: false, unread: 0, isCritical: false, awaitingFeedback: false, priority: 'normal' },
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
function toggleStar(row: Message): void {
|
|
239
|
+
const target = rows.value.find((r) => r.id === row.id);
|
|
240
|
+
if (target) target.starred = !target.starred;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// A single Vue component that receives the whole row and decides internally
|
|
244
|
+
// whether to render an icon, a tag, or nothing — demonstrates the implicit `row` prop.
|
|
245
|
+
const PriorityIndicator = defineComponent({
|
|
246
|
+
props: { row: { type: Object as () => Message, required: true } },
|
|
247
|
+
setup(props) {
|
|
248
|
+
return () => {
|
|
249
|
+
if (props.row.priority === 'high') {
|
|
250
|
+
return h(CoarTag, { variant: 'error', size: 's' }, () => 'HIGH');
|
|
251
|
+
}
|
|
252
|
+
if (props.row.priority === 'low') {
|
|
253
|
+
return h(CoarIcon, { name: 'arrow-down', source: 'coar-builtin', size: 's', color: '#9ca3af' });
|
|
254
|
+
}
|
|
255
|
+
return null; // 'normal' — render nothing
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const builder = CoarGridBuilder.create<Message>()
|
|
261
|
+
.columns([
|
|
262
|
+
// Wrapper with a single left item (star toggle) and a single right item (badge).
|
|
263
|
+
// Subject is editable — double-click opens the editor. Slots disappear automatically
|
|
264
|
+
// in edit mode because AG Grid swaps the whole cellRenderer for the cellEditor.
|
|
265
|
+
(col) => col
|
|
266
|
+
.wrap(col.field('subject').header('Subject').flex(1).sortable().option('editable', true))
|
|
267
|
+
.left({
|
|
268
|
+
icon: 'star',
|
|
269
|
+
source: 'coar-builtin',
|
|
270
|
+
color: (r) => (r.starred ? '#f5a623' : '#cbd5e1'),
|
|
271
|
+
tooltip: (r) => (r.starred ? 'Unstar' : 'Star'),
|
|
272
|
+
onClick: (r) => toggleStar(r),
|
|
273
|
+
})
|
|
274
|
+
.right({
|
|
275
|
+
component: CoarBadge,
|
|
276
|
+
params: (r) => ({ content: String(r.unread) }),
|
|
277
|
+
show: (r) => r.unread > 0,
|
|
278
|
+
}),
|
|
279
|
+
|
|
280
|
+
(col) => col.field('sender').header('From').width(130).sortable(),
|
|
281
|
+
|
|
282
|
+
// Wrapper with TWO right-side icons + a component that decides what to render
|
|
283
|
+
// based on the full row. Each item has its own show() gate.
|
|
284
|
+
(col) => col
|
|
285
|
+
.wrap(col.field('priority').header('Priority').width(200).sortable())
|
|
286
|
+
.right([
|
|
287
|
+
// 1) Critical-flag icon — only visible when isCritical = true
|
|
288
|
+
{
|
|
289
|
+
icon: 'circle-alert',
|
|
290
|
+
source: 'coar-builtin',
|
|
291
|
+
color: '#dc2626',
|
|
292
|
+
tooltip: 'Critical',
|
|
293
|
+
show: (r) => r.isCritical,
|
|
294
|
+
},
|
|
295
|
+
// 2) Awaiting-feedback icon — only visible when awaitingFeedback = true
|
|
296
|
+
{
|
|
297
|
+
icon: 'message-circle',
|
|
298
|
+
source: 'coar-builtin',
|
|
299
|
+
color: '#3b82f6',
|
|
300
|
+
tooltip: 'Awaiting feedback',
|
|
301
|
+
show: (r) => r.awaitingFeedback,
|
|
302
|
+
},
|
|
303
|
+
// 3) A component that receives the whole row and renders tag OR icon OR nothing
|
|
304
|
+
{ component: PriorityIndicator },
|
|
305
|
+
]),
|
|
306
|
+
])
|
|
307
|
+
.rowDataRef(rows)
|
|
308
|
+
.rowId((params) => String(params.data.id));
|
|
309
|
+
</script>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Each slot accepts one of three shapes:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
// 1) Icon shorthand
|
|
316
|
+
.left({
|
|
317
|
+
icon: (row) => row.starred ? 'star' : 'star-outline',
|
|
318
|
+
color: (row) => row.starred ? '#f5a623' : '#ccc',
|
|
319
|
+
tooltip: (row) => row.starred ? 'Unstar' : 'Star',
|
|
320
|
+
onClick: (row, event) => toggleStar(row),
|
|
321
|
+
show: (row) => row.visible, // optional v-if gate
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// 2) Any Vue component
|
|
325
|
+
// The component automatically receives `row: TData` as a prop —
|
|
326
|
+
// use `params(row)` to add or override props.
|
|
327
|
+
.right({
|
|
328
|
+
component: CoarBadge,
|
|
329
|
+
params: (row) => ({ content: String(row.unread) }),
|
|
330
|
+
show: (row) => row.unread > 0,
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// 3) Plain text
|
|
334
|
+
.right({ text: (row) => row.suffix })
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Multiple items per slot
|
|
338
|
+
|
|
339
|
+
Pass an array to stack several items in the same slot — each with its own `show()` gate, `onClick`, and tooltip. Items are rendered in order with a small gap.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
.right([
|
|
343
|
+
{ icon: 'circle-alert', color: '#dc2626', show: (r) => r.isCritical },
|
|
344
|
+
{ icon: 'message-circle', color: '#3b82f6', show: (r) => r.awaitingFeedback },
|
|
345
|
+
{ component: PriorityIndicator }, // receives `row` automatically
|
|
346
|
+
])
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Row-aware components
|
|
350
|
+
|
|
351
|
+
Every component slot automatically receives `row: TData` as a prop. This lets a single component decide what to render — icon, tag, or nothing — based on the full row:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const PriorityIndicator = defineComponent({
|
|
355
|
+
props: { row: { type: Object as () => Message, required: true } },
|
|
356
|
+
setup(props) {
|
|
357
|
+
return () => {
|
|
358
|
+
if (props.row.priority === 'high') return h(CoarTag, { variant: 'error' }, () => 'HIGH');
|
|
359
|
+
if (props.row.priority === 'low') return h(CoarIcon, { name: 'arrow-down' });
|
|
360
|
+
return null;
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Slot `onClick` handlers automatically call `event.stopPropagation()` so they don't trigger row-click or cell-click events on the grid.
|
|
367
|
+
|
|
368
|
+
## Row Selection
|
|
369
|
+
|
|
370
|
+
Toggle between single-click and multi-select with checkboxes.
|
|
371
|
+
|
|
372
|
+
**Demo — `data-grid/demos/GridSelection.vue`**
|
|
373
|
+
|
|
374
|
+
```vue
|
|
375
|
+
<template>
|
|
376
|
+
<div>
|
|
377
|
+
<div style="display: flex; gap: 12px; margin-bottom: 12px;">
|
|
378
|
+
<label style="display: flex; align-items: center; gap: 4px; font-size: 13px; cursor: pointer;">
|
|
379
|
+
<input type="radio" value="single" v-model="mode" /> Single
|
|
380
|
+
</label>
|
|
381
|
+
<label style="display: flex; align-items: center; gap: 4px; font-size: 13px; cursor: pointer;">
|
|
382
|
+
<input type="radio" value="multiple" v-model="mode" /> Multi (Checkboxes)
|
|
383
|
+
</label>
|
|
384
|
+
</div>
|
|
385
|
+
<div style="height: 300px;">
|
|
386
|
+
<CoarDataGrid :key="mode" :builder="builders[mode]" />
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</template>
|
|
390
|
+
|
|
391
|
+
<script setup lang="ts">
|
|
392
|
+
import { ref } from 'vue';
|
|
393
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
394
|
+
|
|
395
|
+
interface User {
|
|
396
|
+
name: string;
|
|
397
|
+
email: string;
|
|
398
|
+
role: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const data: User[] = [
|
|
402
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer' },
|
|
403
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Designer' },
|
|
404
|
+
{ name: 'Carol Williams', email: 'carol@example.com', role: 'Manager' },
|
|
405
|
+
{ name: 'David Brown', email: 'david@example.com', role: 'Engineer' },
|
|
406
|
+
{ name: 'Eve Davis', email: 'eve@example.com', role: 'Designer' },
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
const cols = [
|
|
410
|
+
(col: any) => col.field('name').header('Name').flex(1),
|
|
411
|
+
(col: any) => col.field('email').header('Email').flex(1),
|
|
412
|
+
(col: any) => col.field('role').header('Role').width(120),
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
const mode = ref<'single' | 'multiple'>('single');
|
|
416
|
+
|
|
417
|
+
const builders = {
|
|
418
|
+
single: CoarGridBuilder.create<User>().columns(cols).rowData(data).rowSelection('single'),
|
|
419
|
+
multiple: CoarGridBuilder.create<User>().columns(cols).rowData(data).rowSelection('multiple', { checkboxes: true }),
|
|
420
|
+
};
|
|
421
|
+
</script>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Reactive Data
|
|
425
|
+
|
|
426
|
+
Bind a `ref` with `.rowDataRef()` and the grid updates automatically when your data changes.
|
|
427
|
+
|
|
428
|
+
**Demo — `data-grid/demos/GridReactive.vue`**
|
|
429
|
+
|
|
430
|
+
```vue
|
|
431
|
+
<template>
|
|
432
|
+
<div>
|
|
433
|
+
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
|
434
|
+
<CoarButton size="s" @click="addRow">Add Row</CoarButton>
|
|
435
|
+
<CoarButton size="s" variant="secondary" @click="reset">Reset</CoarButton>
|
|
436
|
+
<span style="font-size: 13px; color: var(--coar-text-neutral-secondary); align-self: center;">
|
|
437
|
+
{{ data.length }} rows
|
|
438
|
+
</span>
|
|
439
|
+
</div>
|
|
440
|
+
<div style="height: 300px;">
|
|
441
|
+
<CoarDataGrid :builder="builder" />
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
</template>
|
|
445
|
+
|
|
446
|
+
<script setup lang="ts">
|
|
447
|
+
import { ref } from 'vue';
|
|
448
|
+
import { CoarButton } from '@cocoar/vue-ui';
|
|
449
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
450
|
+
|
|
451
|
+
interface User {
|
|
452
|
+
id: number;
|
|
453
|
+
name: string;
|
|
454
|
+
email: string;
|
|
455
|
+
role: string;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const initialData: User[] = [
|
|
459
|
+
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer' },
|
|
460
|
+
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Designer' },
|
|
461
|
+
{ id: 3, name: 'Carol Williams', email: 'carol@example.com', role: 'Manager' },
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
const allRows: User[] = [
|
|
465
|
+
...initialData,
|
|
466
|
+
{ id: 4, name: 'David Brown', email: 'david@example.com', role: 'Engineer' },
|
|
467
|
+
{ id: 5, name: 'Eve Davis', email: 'eve@example.com', role: 'Designer' },
|
|
468
|
+
{ id: 6, name: 'Frank Miller', email: 'frank@example.com', role: 'Engineer' },
|
|
469
|
+
{ id: 7, name: 'Grace Wilson', email: 'grace@example.com', role: 'Manager' },
|
|
470
|
+
{ id: 8, name: 'Henry Taylor', email: 'henry@example.com', role: 'Designer' },
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
const data = ref<User[]>([...initialData]);
|
|
474
|
+
|
|
475
|
+
const builder = CoarGridBuilder.create<User>()
|
|
476
|
+
.columns([
|
|
477
|
+
(col) => col.field('name').header('Name').flex(1),
|
|
478
|
+
(col) => col.field('email').header('Email').flex(1),
|
|
479
|
+
(col) => col.field('role').header('Role').width(120),
|
|
480
|
+
])
|
|
481
|
+
.rowDataRef(data);
|
|
482
|
+
|
|
483
|
+
function addRow() {
|
|
484
|
+
const next = allRows[data.value.length % allRows.length];
|
|
485
|
+
if (next) {
|
|
486
|
+
data.value = [...data.value, { ...next, id: Date.now() }];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function reset() {
|
|
491
|
+
data.value = [...initialData];
|
|
492
|
+
}
|
|
493
|
+
</script>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
## Search (Quick Filter)
|
|
497
|
+
|
|
498
|
+
Enable the built-in search bar with `show-search`. It wires the search input to the builder's quick filter automatically.
|
|
499
|
+
|
|
500
|
+
**Demo — `data-grid/demos/GridSearchPanel.vue`**
|
|
501
|
+
|
|
502
|
+
```vue
|
|
503
|
+
<template>
|
|
504
|
+
<div style="height: 400px;">
|
|
505
|
+
<CoarDataGrid
|
|
506
|
+
:builder="builder"
|
|
507
|
+
show-search
|
|
508
|
+
search-placeholder="Search users..."
|
|
509
|
+
>
|
|
510
|
+
<template #toolbar-right>
|
|
511
|
+
<CoarButton size="s" variant="secondary" @click="addUser">Add User</CoarButton>
|
|
512
|
+
</template>
|
|
513
|
+
</CoarDataGrid>
|
|
514
|
+
</div>
|
|
515
|
+
</template>
|
|
516
|
+
|
|
517
|
+
<script setup lang="ts">
|
|
518
|
+
import { ref } from 'vue';
|
|
519
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
520
|
+
import { CoarButton } from '@cocoar/vue-ui';
|
|
521
|
+
|
|
522
|
+
interface User {
|
|
523
|
+
name: string;
|
|
524
|
+
email: string;
|
|
525
|
+
role: string;
|
|
526
|
+
department: string;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const users = ref<User[]>([
|
|
530
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer', department: 'Platform' },
|
|
531
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Designer', department: 'Product' },
|
|
532
|
+
{ name: 'Carol Williams', email: 'carol@example.com', role: 'Manager', department: 'Platform' },
|
|
533
|
+
{ name: 'David Brown', email: 'david@example.com', role: 'Engineer', department: 'Mobile' },
|
|
534
|
+
{ name: 'Eve Davis', email: 'eve@example.com', role: 'Designer', department: 'Product' },
|
|
535
|
+
{ name: 'Frank Miller', email: 'frank@example.com', role: 'Engineer', department: 'Mobile' },
|
|
536
|
+
{ name: 'Grace Wilson', email: 'grace@example.com', role: 'Manager', department: 'Product' },
|
|
537
|
+
{ name: 'Henry Taylor', email: 'henry@example.com', role: 'Designer', department: 'Platform' },
|
|
538
|
+
]);
|
|
539
|
+
|
|
540
|
+
const builder = CoarGridBuilder.create<User>()
|
|
541
|
+
.columns([
|
|
542
|
+
(col) => col.field('name').header('Name').flex(1).sortable(),
|
|
543
|
+
(col) => col.field('email').header('Email').flex(1),
|
|
544
|
+
(col) => col.field('role').header('Role').width(120),
|
|
545
|
+
(col) => col.field('department').header('Department').width(130),
|
|
546
|
+
])
|
|
547
|
+
.rowDataRef(users)
|
|
548
|
+
.searchHighlight();
|
|
549
|
+
|
|
550
|
+
let counter = 0;
|
|
551
|
+
function addUser() {
|
|
552
|
+
counter++;
|
|
553
|
+
users.value = [
|
|
554
|
+
...users.value,
|
|
555
|
+
{ name: `New User ${counter}`, email: `new${counter}@example.com`, role: 'Engineer', department: 'Platform' },
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
</script>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Custom Layout
|
|
562
|
+
|
|
563
|
+
Use `CoarDataGridSearch` and `CoarDataGrid` separately for full layout control. Connect them via `builder.quickFilterText(ref)`.
|
|
564
|
+
|
|
565
|
+
**Demo — `data-grid/demos/GridSearchCustom.vue`**
|
|
566
|
+
|
|
567
|
+
```vue
|
|
568
|
+
<template>
|
|
569
|
+
<div style="height: 400px; display: flex; flex-direction: column; gap: 8px;">
|
|
570
|
+
<h4 style="margin: 0;">Custom Layout</h4>
|
|
571
|
+
<CoarDataGridSearch v-model="search" placeholder="Filter by name or tags...">
|
|
572
|
+
<CoarButton size="s" variant="secondary" @click="showAll = !showAll">
|
|
573
|
+
{{ showAll ? 'Active Only' : 'Show All' }}
|
|
574
|
+
</CoarButton>
|
|
575
|
+
</CoarDataGridSearch>
|
|
576
|
+
<CoarDataGrid :builder="builder" />
|
|
577
|
+
</div>
|
|
578
|
+
</template>
|
|
579
|
+
|
|
580
|
+
<script setup lang="ts">
|
|
581
|
+
import { ref } from 'vue';
|
|
582
|
+
import { CoarDataGrid, CoarDataGridSearch, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
583
|
+
import { CoarButton } from '@cocoar/vue-ui';
|
|
584
|
+
|
|
585
|
+
interface Task {
|
|
586
|
+
title: string;
|
|
587
|
+
status: string;
|
|
588
|
+
tags: string[];
|
|
589
|
+
assignee: string;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const tasks: Task[] = [
|
|
593
|
+
{ title: 'Fix login bug', status: 'active', tags: ['bug', 'urgent'], assignee: 'Alice' },
|
|
594
|
+
{ title: 'Add dark mode', status: 'active', tags: ['feature', 'ui'], assignee: 'Bob' },
|
|
595
|
+
{ title: 'Update docs', status: 'done', tags: ['docs'], assignee: 'Carol' },
|
|
596
|
+
{ title: 'Refactor API', status: 'active', tags: ['refactor', 'backend'], assignee: 'David' },
|
|
597
|
+
{ title: 'Fix typo in header', status: 'done', tags: ['bug'], assignee: 'Eve' },
|
|
598
|
+
{ title: 'Add search to grid', status: 'active', tags: ['feature', 'ui'], assignee: 'Frank' },
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
const search = ref('');
|
|
602
|
+
const showAll = ref(true);
|
|
603
|
+
|
|
604
|
+
const builder = CoarGridBuilder.create<Task>()
|
|
605
|
+
.columns([
|
|
606
|
+
(col) => col.field('title').header('Title').flex(1),
|
|
607
|
+
(col) => col.field('status').header('Status').width(100),
|
|
608
|
+
(col) => col.field('tags').header('Tags').flex(1)
|
|
609
|
+
.valueFormatter((p) => p.value?.join(', ') ?? '')
|
|
610
|
+
.quickFilter((tags) => tags.join(' ')),
|
|
611
|
+
(col) => col.field('assignee').header('Assignee').width(120),
|
|
612
|
+
])
|
|
613
|
+
.rowData(tasks)
|
|
614
|
+
.quickFilterText(search)
|
|
615
|
+
.externalFilter((node) => showAll.value || node.data?.status === 'active')
|
|
616
|
+
.updateExternalFilterWhen(showAll);
|
|
617
|
+
</script>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Per-Column Configuration
|
|
621
|
+
|
|
622
|
+
Control how each column participates in quick filtering:
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
builder.columns([
|
|
626
|
+
// Default: searches by String(value)
|
|
627
|
+
(col) => col.field('name').header('Name'),
|
|
628
|
+
|
|
629
|
+
// Custom text extraction (e.g., for arrays or objects)
|
|
630
|
+
(col) => col.field('tags').quickFilter((tags) => tags.map(t => t.label).join(' ')),
|
|
631
|
+
|
|
632
|
+
// Exclude from search
|
|
633
|
+
(col) => col.field('id').quickFilter(false),
|
|
634
|
+
]);
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Custom Filter Function
|
|
638
|
+
|
|
639
|
+
Override the default per-column matching with a fully custom filter:
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
builder.quickFilterFn((searchValue, data) => {
|
|
643
|
+
// searchValue is already lowercased and trimmed
|
|
644
|
+
return data.name.toLowerCase().includes(searchValue)
|
|
645
|
+
|| data.email.toLowerCase().includes(searchValue);
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Search Highlighting
|
|
650
|
+
|
|
651
|
+
Enable text highlighting in grid cells using the CSS Custom Highlight API. Matching text is underlined without modifying the DOM.
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
builder
|
|
655
|
+
.quickFilterText(searchRef)
|
|
656
|
+
.searchHighlight()
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
The highlight style can be customized via CSS:
|
|
660
|
+
|
|
661
|
+
```css
|
|
662
|
+
::highlight(coar-search) {
|
|
663
|
+
text-decoration: underline;
|
|
664
|
+
text-decoration-color: #0066cc;
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
## I18n Headers
|
|
669
|
+
|
|
670
|
+
Column headers support runtime language switching via `@cocoar/vue-localization`. Pass a fallback text and an optional translation key:
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
builder.columns([
|
|
674
|
+
// Static header
|
|
675
|
+
(col) => col.field('name').header('Name'),
|
|
676
|
+
|
|
677
|
+
// With i18n — falls back to 'Name' if no translation found
|
|
678
|
+
(col) => col.field('name').header('Name', 'todo.grid.header.title'),
|
|
679
|
+
])
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
If `@cocoar/vue-localization` is not installed, the fallback text is always shown. Headers update automatically when the language changes at runtime.
|
|
683
|
+
|
|
684
|
+
## Auto Size
|
|
685
|
+
|
|
686
|
+
Control how columns are sized initially:
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
// Columns fill the grid width (most common)
|
|
690
|
+
builder.autoSize('fitGridWidth')
|
|
691
|
+
|
|
692
|
+
// Columns fit their content
|
|
693
|
+
builder.autoSize('fitCellContents')
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
## Tree Drag & Drop
|
|
697
|
+
|
|
698
|
+
Move rows between parents via drag & drop. Use `.rowDrag()` on the tree column, `.rowDragHighlight()` for visual feedback, and `.onRowDragEnd()` to handle the reparenting.
|
|
699
|
+
|
|
700
|
+
**Demo — `data-grid/demos/GridTreeDrag.vue`**
|
|
701
|
+
|
|
702
|
+
```vue
|
|
703
|
+
<template>
|
|
704
|
+
<div style="height: 450px; display: flex; flex-direction: column; gap: 8px;">
|
|
705
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
|
|
706
|
+
<input type="checkbox" v-model="maxTwoLevels" />
|
|
707
|
+
Limit to 2 levels (no nesting into children)
|
|
708
|
+
</label>
|
|
709
|
+
<CoarDataGrid :builder="builder" />
|
|
710
|
+
<div v-if="lastAction" style="font-size: 0.85em; color: #666;">
|
|
711
|
+
{{ lastAction }}
|
|
712
|
+
</div>
|
|
713
|
+
</div>
|
|
714
|
+
</template>
|
|
715
|
+
|
|
716
|
+
<script setup lang="ts">
|
|
717
|
+
import { ref } from 'vue';
|
|
718
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
719
|
+
|
|
720
|
+
const maxTwoLevels = ref(false);
|
|
721
|
+
|
|
722
|
+
interface Task {
|
|
723
|
+
id: string;
|
|
724
|
+
title: string;
|
|
725
|
+
status: string;
|
|
726
|
+
children?: Task[];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const tasks = ref<Task[]>([
|
|
730
|
+
{
|
|
731
|
+
id: '1', title: 'Frontend', status: 'active', children: [
|
|
732
|
+
{ id: '1-1', title: 'Login page', status: 'done' },
|
|
733
|
+
{ id: '1-2', title: 'Dashboard', status: 'active' },
|
|
734
|
+
],
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
id: '2', title: 'Backend', status: 'active', children: [
|
|
738
|
+
{ id: '2-1', title: 'Auth API', status: 'done' },
|
|
739
|
+
{ id: '2-2', title: 'User API', status: 'active' },
|
|
740
|
+
{ id: '2-3', title: 'Settings API', status: 'active' },
|
|
741
|
+
],
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
id: '3', title: 'Testing', status: 'active', children: [
|
|
745
|
+
{ id: '3-1', title: 'Unit tests', status: 'active' },
|
|
746
|
+
],
|
|
747
|
+
},
|
|
748
|
+
]);
|
|
749
|
+
|
|
750
|
+
const openRows = ref(['1', '2', '3']);
|
|
751
|
+
const lastAction = ref('');
|
|
752
|
+
|
|
753
|
+
function findAndRemove(items: Task[], id: string): Task | undefined {
|
|
754
|
+
for (let i = 0; i < items.length; i++) {
|
|
755
|
+
if (items[i].id === id) return items.splice(i, 1)[0];
|
|
756
|
+
if (items[i].children) {
|
|
757
|
+
const found = findAndRemove(items[i].children!, id);
|
|
758
|
+
if (found) return found;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function findById(items: Task[], id: string): Task | undefined {
|
|
765
|
+
for (const item of items) {
|
|
766
|
+
if (item.id === id) return item;
|
|
767
|
+
if (item.children) {
|
|
768
|
+
const found = findById(item.children, id);
|
|
769
|
+
if (found) return found;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function isDescendantOf(items: Task[], ancestorId: string, targetId: string): boolean {
|
|
776
|
+
const ancestor = findById(items, ancestorId);
|
|
777
|
+
if (!ancestor?.children) return false;
|
|
778
|
+
return !!findById(ancestor.children, targetId);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const builder = CoarGridBuilder.create<Task>()
|
|
782
|
+
.columns([
|
|
783
|
+
(col) => col.tree('title').header('Task').flex(1).rowDrag(),
|
|
784
|
+
(col) => col.field('status').header('Status').width(100),
|
|
785
|
+
])
|
|
786
|
+
.treeData({
|
|
787
|
+
children: (row) => row.children ?? [],
|
|
788
|
+
rowId: (row) => row.id,
|
|
789
|
+
})
|
|
790
|
+
.openRows(openRows)
|
|
791
|
+
.rowDataRef(tasks)
|
|
792
|
+
.rowDragHighlight({
|
|
793
|
+
canDrop: (dragged, target) => {
|
|
794
|
+
if (dragged.id === target.id) return false;
|
|
795
|
+
if (isDescendantOf(tasks.value, dragged.id, target.id)) return false;
|
|
796
|
+
// When "max 2 levels" is on, only allow drop on root items
|
|
797
|
+
if (maxTwoLevels.value) {
|
|
798
|
+
const targetMeta = builder.getTreeMeta(target.id);
|
|
799
|
+
if (targetMeta && targetMeta.depth > 0) return false;
|
|
800
|
+
}
|
|
801
|
+
return true;
|
|
802
|
+
},
|
|
803
|
+
})
|
|
804
|
+
.onRowDragEnd((event) => {
|
|
805
|
+
const dragged = event.node.data;
|
|
806
|
+
const target = event.overNode?.data;
|
|
807
|
+
if (!dragged) return;
|
|
808
|
+
|
|
809
|
+
const clone = JSON.parse(JSON.stringify(tasks.value)) as Task[];
|
|
810
|
+
const movedItem = findAndRemove(clone, dragged.id);
|
|
811
|
+
if (!movedItem) return;
|
|
812
|
+
|
|
813
|
+
if (!target) {
|
|
814
|
+
clone.push(movedItem);
|
|
815
|
+
tasks.value = clone;
|
|
816
|
+
lastAction.value = `Moved "${movedItem.title}" to root level`;
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (dragged.id === target.id) return;
|
|
821
|
+
if (isDescendantOf(tasks.value, dragged.id, target.id)) return;
|
|
822
|
+
if (maxTwoLevels.value) {
|
|
823
|
+
const targetMeta = builder.getTreeMeta(target.id);
|
|
824
|
+
if (targetMeta && targetMeta.depth > 0) return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const targetItem = findById(clone, target.id);
|
|
828
|
+
if (!targetItem) return;
|
|
829
|
+
|
|
830
|
+
if (!targetItem.children) targetItem.children = [];
|
|
831
|
+
targetItem.children.push(movedItem);
|
|
832
|
+
|
|
833
|
+
if (!openRows.value.includes(target.id)) {
|
|
834
|
+
openRows.value = [...openRows.value, target.id];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
tasks.value = clone;
|
|
838
|
+
lastAction.value = `Moved "${movedItem.title}" into "${targetItem.title}"`;
|
|
839
|
+
});
|
|
840
|
+
</script>
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
```ts
|
|
844
|
+
builder
|
|
845
|
+
.treeData({ children: (r) => r.children ?? [], rowId: (r) => r.id })
|
|
846
|
+
.openRows(openRows)
|
|
847
|
+
.rowDragHighlight()
|
|
848
|
+
.onRowDragEnd((event) => {
|
|
849
|
+
const dragged = event.node.data;
|
|
850
|
+
const target = event.overNode?.data;
|
|
851
|
+
if (!dragged || !target) return;
|
|
852
|
+
// API call or store mutation to reparent
|
|
853
|
+
api.moveInto(dragged.id, target.id);
|
|
854
|
+
});
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
## Tree Data
|
|
858
|
+
|
|
859
|
+
Display hierarchical data with expand/collapse. Use `treeData()` with nested children arrays and `openRows()` to control expansion. The `tree()` column type renders indentation, chevron toggle, and child count.
|
|
860
|
+
|
|
861
|
+
Search automatically expands matching branches — a parent stays visible when any descendant matches.
|
|
862
|
+
|
|
863
|
+
**Demo — `data-grid/demos/GridTreeData.vue`**
|
|
864
|
+
|
|
865
|
+
```vue
|
|
866
|
+
<template>
|
|
867
|
+
<div style="height: 450px;">
|
|
868
|
+
<CoarDataGrid
|
|
869
|
+
:builder="builder"
|
|
870
|
+
show-search
|
|
871
|
+
search-placeholder="Search files..."
|
|
872
|
+
/>
|
|
873
|
+
</div>
|
|
874
|
+
</template>
|
|
875
|
+
|
|
876
|
+
<script setup lang="ts">
|
|
877
|
+
import { ref } from 'vue';
|
|
878
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
879
|
+
|
|
880
|
+
interface FileNode {
|
|
881
|
+
id: string;
|
|
882
|
+
name: string;
|
|
883
|
+
type: 'folder' | 'file';
|
|
884
|
+
size?: string;
|
|
885
|
+
modified: string;
|
|
886
|
+
children?: FileNode[];
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const files: FileNode[] = [
|
|
890
|
+
{
|
|
891
|
+
id: '1', name: 'src', type: 'folder', modified: '2024-03-15', children: [
|
|
892
|
+
{
|
|
893
|
+
id: '1-1', name: 'components', type: 'folder', modified: '2024-03-14', children: [
|
|
894
|
+
{ id: '1-1-1', name: 'Button.vue', type: 'file', size: '2.4 KB', modified: '2024-03-10' },
|
|
895
|
+
{ id: '1-1-2', name: 'Input.vue', type: 'file', size: '3.1 KB', modified: '2024-03-12' },
|
|
896
|
+
{ id: '1-1-3', name: 'Dialog.vue', type: 'file', size: '5.8 KB', modified: '2024-03-14' },
|
|
897
|
+
],
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
id: '1-2', name: 'utils', type: 'folder', modified: '2024-03-13', children: [
|
|
901
|
+
{ id: '1-2-1', name: 'format.ts', type: 'file', size: '1.2 KB', modified: '2024-03-13' },
|
|
902
|
+
{ id: '1-2-2', name: 'validate.ts', type: 'file', size: '0.8 KB', modified: '2024-03-11' },
|
|
903
|
+
],
|
|
904
|
+
},
|
|
905
|
+
{ id: '1-3', name: 'App.vue', type: 'file', size: '1.5 KB', modified: '2024-03-15' },
|
|
906
|
+
{ id: '1-4', name: 'main.ts', type: 'file', size: '0.3 KB', modified: '2024-03-01' },
|
|
907
|
+
],
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
id: '2', name: 'public', type: 'folder', modified: '2024-02-20', children: [
|
|
911
|
+
{ id: '2-1', name: 'favicon.ico', type: 'file', size: '4.2 KB', modified: '2024-01-15' },
|
|
912
|
+
{ id: '2-2', name: 'index.html', type: 'file', size: '0.5 KB', modified: '2024-02-20' },
|
|
913
|
+
],
|
|
914
|
+
},
|
|
915
|
+
{ id: '3', name: 'package.json', type: 'file', size: '1.1 KB', modified: '2024-03-15' },
|
|
916
|
+
{ id: '4', name: 'tsconfig.json', type: 'file', size: '0.4 KB', modified: '2024-01-10' },
|
|
917
|
+
{ id: '5', name: 'README.md', type: 'file', size: '2.0 KB', modified: '2024-03-05' },
|
|
918
|
+
];
|
|
919
|
+
|
|
920
|
+
const openRows = ref<string[]>(['1']);
|
|
921
|
+
|
|
922
|
+
const builder = CoarGridBuilder.create<FileNode>()
|
|
923
|
+
.columns([
|
|
924
|
+
(col) => col.tree('name').header('Name').flex(2),
|
|
925
|
+
(col) => col.field('type').header('Type').width(90),
|
|
926
|
+
(col) => col.field('size').header('Size').width(100),
|
|
927
|
+
(col) => col.field('modified').header('Modified').width(130),
|
|
928
|
+
])
|
|
929
|
+
.treeData({
|
|
930
|
+
children: (row) => row.children ?? [],
|
|
931
|
+
rowId: (row) => row.id,
|
|
932
|
+
})
|
|
933
|
+
.openRows(openRows)
|
|
934
|
+
.rowData(files)
|
|
935
|
+
.searchHighlight();
|
|
936
|
+
</script>
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
```ts
|
|
940
|
+
builder
|
|
941
|
+
.treeData({
|
|
942
|
+
children: (row) => row.children ?? [],
|
|
943
|
+
rowId: (row) => row.id,
|
|
944
|
+
})
|
|
945
|
+
.openRows(openRowsRef)
|
|
946
|
+
.columns([
|
|
947
|
+
(col) => col.tree('name').header('Name').flex(1), // tree column
|
|
948
|
+
(col) => col.field('size').header('Size').width(100),
|
|
949
|
+
])
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
## Row Drag & Drop
|
|
953
|
+
|
|
954
|
+
Reorder rows via drag & drop. Use `.rowDrag()` on a column to show the drag handle, and `.rowDragManaged()` on the builder. Dragging is automatically disabled when a column sort is active.
|
|
955
|
+
|
|
956
|
+
**Demo — `data-grid/demos/GridRowDrag.vue`**
|
|
957
|
+
|
|
958
|
+
```vue
|
|
959
|
+
<template>
|
|
960
|
+
<div style="height: 350px; display: flex; flex-direction: column; gap: 8px;">
|
|
961
|
+
<CoarDataGrid :builder="builder" />
|
|
962
|
+
<div v-if="lastOrder" style="font-size: 0.85em; color: #666;">
|
|
963
|
+
Order: {{ lastOrder }}
|
|
964
|
+
</div>
|
|
965
|
+
</div>
|
|
966
|
+
</template>
|
|
967
|
+
|
|
968
|
+
<script setup lang="ts">
|
|
969
|
+
import { ref } from 'vue';
|
|
970
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
971
|
+
|
|
972
|
+
interface Step {
|
|
973
|
+
id: number;
|
|
974
|
+
title: string;
|
|
975
|
+
priority: string;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const steps = ref<Step[]>([
|
|
979
|
+
{ id: 1, title: 'Gather requirements', priority: 'High' },
|
|
980
|
+
{ id: 2, title: 'Create wireframes', priority: 'Medium' },
|
|
981
|
+
{ id: 3, title: 'Implement prototype', priority: 'High' },
|
|
982
|
+
{ id: 4, title: 'User testing', priority: 'Medium' },
|
|
983
|
+
{ id: 5, title: 'Final review', priority: 'Low' },
|
|
984
|
+
]);
|
|
985
|
+
|
|
986
|
+
const lastOrder = ref('');
|
|
987
|
+
|
|
988
|
+
const builder = CoarGridBuilder.create<Step>()
|
|
989
|
+
.columns([
|
|
990
|
+
(col) => col.field('title').header('Step').flex(1).rowDrag(),
|
|
991
|
+
(col) => col.field('priority').header('Priority').width(120).sortable(),
|
|
992
|
+
])
|
|
993
|
+
.rowDataRef(steps)
|
|
994
|
+
.rowId((p) => String(p.data.id))
|
|
995
|
+
.rowDragManaged()
|
|
996
|
+
.onRowDragEnd(() => {
|
|
997
|
+
const newOrder = builder.getDisplayedRowData();
|
|
998
|
+
lastOrder.value = newOrder.map((s) => s.title).join(' → ');
|
|
999
|
+
// In a real app: store.updateOrder(newOrder) or api.reorder(newOrder)
|
|
1000
|
+
});
|
|
1001
|
+
</script>
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
builder
|
|
1006
|
+
.columns([
|
|
1007
|
+
(col) => col.field('name').rowDrag().flex(1),
|
|
1008
|
+
])
|
|
1009
|
+
.rowDragManaged()
|
|
1010
|
+
.onRowDragEnd(() => {
|
|
1011
|
+
const newOrder = builder.getDisplayedRowData();
|
|
1012
|
+
store.updateOrder(newOrder); // persist new order
|
|
1013
|
+
});
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
## Sorting
|
|
1017
|
+
|
|
1018
|
+
Make columns sortable and set a default sort order.
|
|
1019
|
+
|
|
1020
|
+
```ts
|
|
1021
|
+
const builder = CoarGridBuilder.create<Row>()
|
|
1022
|
+
.columns([
|
|
1023
|
+
(col) => col.field('name').header('Name').flex(1).sortable(),
|
|
1024
|
+
(col) => col.number('salary').header('Salary').width(120).sortable(),
|
|
1025
|
+
])
|
|
1026
|
+
.rowData(data)
|
|
1027
|
+
.defaultSort('name', 'asc');
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
## Column Persistence
|
|
1031
|
+
|
|
1032
|
+
Persist column widths, order, visibility, and sort in IndexedDB with `.persistColumnState(key)`.
|
|
1033
|
+
|
|
1034
|
+
**Width buckets:** The grid container width is rounded to buckets (default: 100px). Each bucket gets its own saved column layout, so different container sizes — switching monitors, collapsing a sidebar — each keep their own column widths. When no exact bucket exists, the nearest saved state is applied.
|
|
1035
|
+
|
|
1036
|
+
**Live sync:** Multiple grids with the same key synchronize column changes instantly. Resize, reorder, or hide a column in one grid and all others update immediately. Useful for comparison views with different filters on the same data structure.
|
|
1037
|
+
|
|
1038
|
+
Try it below — resize a column in Team A and watch Team B follow.
|
|
1039
|
+
|
|
1040
|
+
**Demo — `data-grid/demos/GridPersistence.vue`**
|
|
1041
|
+
|
|
1042
|
+
```vue
|
|
1043
|
+
<template>
|
|
1044
|
+
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
1045
|
+
<div>
|
|
1046
|
+
<CoarButton size="s" @click="builder.resetPersistedStates()">Reset columns</CoarButton>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
1049
|
+
<div style="height: 250px;">
|
|
1050
|
+
<CoarDataGrid :builder="builder" bordered>
|
|
1051
|
+
<template #toolbar-left>
|
|
1052
|
+
<span style="font-weight: 600;">Team A</span>
|
|
1053
|
+
</template>
|
|
1054
|
+
</CoarDataGrid>
|
|
1055
|
+
</div>
|
|
1056
|
+
|
|
1057
|
+
<div style="height: 250px;">
|
|
1058
|
+
<CoarDataGrid :builder="builder2" bordered>
|
|
1059
|
+
<template #toolbar-left>
|
|
1060
|
+
<span style="font-weight: 600;">Team B</span>
|
|
1061
|
+
</template>
|
|
1062
|
+
</CoarDataGrid>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
</template>
|
|
1066
|
+
|
|
1067
|
+
<script setup lang="ts">
|
|
1068
|
+
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
|
|
1069
|
+
import { CoarButton } from '@cocoar/vue-ui';
|
|
1070
|
+
|
|
1071
|
+
interface User {
|
|
1072
|
+
name: string;
|
|
1073
|
+
email: string;
|
|
1074
|
+
role: string;
|
|
1075
|
+
department: string;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const teamA: User[] = [
|
|
1079
|
+
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer', department: 'Platform' },
|
|
1080
|
+
{ name: 'Bob Smith', email: 'bob@example.com', role: 'Designer', department: 'Platform' },
|
|
1081
|
+
{ name: 'Carol Williams', email: 'carol@example.com', role: 'Manager', department: 'Platform' },
|
|
1082
|
+
];
|
|
1083
|
+
|
|
1084
|
+
const teamB: User[] = [
|
|
1085
|
+
{ name: 'David Brown', email: 'david@example.com', role: 'Engineer', department: 'Product' },
|
|
1086
|
+
{ name: 'Eve Davis', email: 'eve@example.com', role: 'Designer', department: 'Product' },
|
|
1087
|
+
{ name: 'Frank Miller', email: 'frank@example.com', role: 'Manager', department: 'Product' },
|
|
1088
|
+
];
|
|
1089
|
+
|
|
1090
|
+
const columns = [
|
|
1091
|
+
(col: any) => col.field('name').header('Name').flex(1),
|
|
1092
|
+
(col: any) => col.field('email').header('Email').flex(1),
|
|
1093
|
+
(col: any) => col.field('role').header('Role').width(120),
|
|
1094
|
+
(col: any) => col.field('department').header('Dept').width(120),
|
|
1095
|
+
];
|
|
1096
|
+
|
|
1097
|
+
const builder = CoarGridBuilder.create<User>()
|
|
1098
|
+
.persistColumnState('docs-persistence-demo')
|
|
1099
|
+
.columns(columns)
|
|
1100
|
+
.rowData(teamA);
|
|
1101
|
+
|
|
1102
|
+
const builder2 = CoarGridBuilder.create<User>()
|
|
1103
|
+
.persistColumnState('docs-persistence-demo')
|
|
1104
|
+
.columns(columns)
|
|
1105
|
+
.rowData(teamB);
|
|
1106
|
+
</script>
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
```ts
|
|
1110
|
+
const builder = CoarGridBuilder.create<User>()
|
|
1111
|
+
.persistColumnState('my-users-grid')
|
|
1112
|
+
.columns([...])
|
|
1113
|
+
|
|
1114
|
+
// Optional: custom bucket size and debounce
|
|
1115
|
+
.persistColumnState('my-grid', { bucketSize: 200, debounceMs: 1000 })
|
|
1116
|
+
|
|
1117
|
+
// Reset current bucket
|
|
1118
|
+
builder.resetPersistedState()
|
|
1119
|
+
|
|
1120
|
+
// Reset all buckets
|
|
1121
|
+
builder.resetPersistedStates()
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### Cleanup
|
|
1125
|
+
|
|
1126
|
+
Persisted entries are timestamped on every read and write. Call `cleanupColumnStates()` once at application startup to remove stale entries and prevent unbounded growth:
|
|
1127
|
+
|
|
1128
|
+
```ts
|
|
1129
|
+
// main.ts
|
|
1130
|
+
import { cleanupColumnStates } from '@cocoar/vue-data-grid';
|
|
1131
|
+
|
|
1132
|
+
cleanupColumnStates(180); // Remove entries older than 6 months
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
## API
|
|
1136
|
+
|
|
1137
|
+
### CoarDataGrid Props
|
|
1138
|
+
|
|
1139
|
+
| Prop | Type | Default | Description |
|
|
1140
|
+
|------|------|---------|-------------|
|
|
1141
|
+
| `builder` | `CoarGridBuilder<T>` | — | Grid configuration builder (required) |
|
|
1142
|
+
| `theme` | `Theme` | `cocoarTheme` | AG Grid theme override |
|
|
1143
|
+
| `showSearch` | `boolean` | `false` | Show the search bar in the toolbar |
|
|
1144
|
+
| `searchPlaceholder` | `string` | `'Search...'` | Placeholder for the search input |
|
|
1145
|
+
| `searchSize` | `'xs' \| 's' \| 'm' \| 'l'` | `'m'` | Search input size |
|
|
1146
|
+
| `search` | `string` | `''` | Search text (`v-model:search`) |
|
|
1147
|
+
| `bordered` | `boolean` | `false` | Show a border around the grid |
|
|
1148
|
+
| `elevated` | `boolean` | `false` | Add elevation shadow |
|
|
1149
|
+
|
|
1150
|
+
### CoarDataGrid Slots
|
|
1151
|
+
|
|
1152
|
+
| Slot | Description |
|
|
1153
|
+
|------|-------------|
|
|
1154
|
+
| `toolbar-left` | Content on the left side of the toolbar (e.g., title, icon) |
|
|
1155
|
+
| `toolbar-right` | Content on the right side of the toolbar (e.g., buttons, actions) |
|
|
1156
|
+
|
|
1157
|
+
The toolbar appears automatically when `showSearch` is enabled or any `toolbar-*` slot is used. The search input fills available space (`flex: 1`). When search is disabled, a spacer pushes `toolbar-right` to the far right.
|
|
1158
|
+
|
|
1159
|
+
```vue
|
|
1160
|
+
<!-- Search + actions -->
|
|
1161
|
+
<CoarDataGrid :builder="builder" show-search bordered>
|
|
1162
|
+
<template #toolbar-left>
|
|
1163
|
+
<span style="font-weight: 600;">Users</span>
|
|
1164
|
+
</template>
|
|
1165
|
+
<template #toolbar-right>
|
|
1166
|
+
<CoarButton size="s">Add User</CoarButton>
|
|
1167
|
+
</template>
|
|
1168
|
+
</CoarDataGrid>
|
|
1169
|
+
|
|
1170
|
+
<!-- Only toolbar actions, no search -->
|
|
1171
|
+
<CoarDataGrid :builder="builder">
|
|
1172
|
+
<template #toolbar-right>
|
|
1173
|
+
<CoarButton size="s">Export</CoarButton>
|
|
1174
|
+
</template>
|
|
1175
|
+
</CoarDataGrid>
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
### CoarGridBuilder Methods
|
|
1179
|
+
|
|
1180
|
+
| Method | Parameters | Description |
|
|
1181
|
+
|--------|-----------|-------------|
|
|
1182
|
+
| `.columns(defs)` | `ColumnDefFn<T>[]` | Define column configuration |
|
|
1183
|
+
| `.rowData(data)` | `T[]` | Set static row data |
|
|
1184
|
+
| `.rowDataRef(ref)` | `Ref<T[]>` | Bind reactive row data |
|
|
1185
|
+
| `.quickFilterText(ref)` | `Ref<string>` | Bind search text for quick filtering |
|
|
1186
|
+
| `.quickFilterFn(fn)` | `(search, data) => boolean` | Custom filter function override |
|
|
1187
|
+
| `.searchHighlight()` | — | Highlight matching text via CSS Custom Highlight API |
|
|
1188
|
+
| `.rowDragManaged()` | — | Enable managed drag & drop reordering |
|
|
1189
|
+
| `.onRowDragEnd(fn)` | `(event) => void` | Handle drag end, persist new order |
|
|
1190
|
+
| `.rowDragHighlight(opts?)` | `{ canDrop? }` | Visual drop target feedback with validation |
|
|
1191
|
+
| `.getDisplayedRowData()` | — | Get row data in current display order |
|
|
1192
|
+
| `.getTreeMeta(rowId)` | `string` | Get tree node depth, children info |
|
|
1193
|
+
| `.treeData(config)` | `TreeDataConfig<T>` | Enable tree mode with nested children |
|
|
1194
|
+
| `.openRows(ref)` | `Ref<string[]>` | Reactive ref of expanded row IDs |
|
|
1195
|
+
| `.autoSize(strategy)` | `'fitGridWidth' \| 'fitCellContents'` | Column auto-sizing strategy |
|
|
1196
|
+
| `.rowSelection(mode, opts?)` | `'single' \| 'multiple'` | Enable row selection |
|
|
1197
|
+
| `.defaultSort(field, dir)` | `string, 'asc' \| 'desc'` | Set default sort column |
|
|
1198
|
+
| `.persistColumnState(key, opts?)` | `string, ColumnPersistenceOptions?` | Persist column state in IndexedDB with width-based buckets |
|
|
1199
|
+
| `.resetPersistedState(bucket?)` | `number?` | Reset persisted state for a specific bucket (defaults to current) |
|
|
1200
|
+
| `.resetPersistedStates()` | — | Reset all persisted column states (all buckets) |
|
|
1201
|
+
| `.rowClassRules(rules)` | `RowClassRules<T>` | Conditional row CSS classes |
|
|
1202
|
+
|
|
1203
|
+
### Standalone Functions
|
|
1204
|
+
|
|
1205
|
+
| Function | Parameters | Description |
|
|
1206
|
+
|----------|-----------|-------------|
|
|
1207
|
+
| `cleanupColumnStates(maxAgeDays)` | `number` | Remove persisted column states older than `maxAgeDays`. Call at app startup. |
|
|
1208
|
+
|
|
1209
|
+
### CoarGridColumnBuilder Methods
|
|
1210
|
+
|
|
1211
|
+
| Method | Parameters | Description |
|
|
1212
|
+
|--------|-----------|-------------|
|
|
1213
|
+
| `.field(name)` | `keyof T` | Set column data field |
|
|
1214
|
+
| `.header(text, i18nKey?)` | `string, string?` | Set header text with optional i18n key |
|
|
1215
|
+
| `.flex(value)` | `number` | Flexible column width |
|
|
1216
|
+
| `.width(px)` | `number` | Fixed column width |
|
|
1217
|
+
| `.fixedWidth(px)` | `number` | Non-resizable fixed width |
|
|
1218
|
+
| `.sortable()` | — | Enable column sorting |
|
|
1219
|
+
| `.quickFilter(fn)` | `boolean \| (value, data) => string` | Configure quick filter for column |
|
|
1220
|
+
| `.date(field, config?)` | `keyof T, DateCellRendererConfig?` | Locale-aware date cell renderer |
|
|
1221
|
+
| `.number(field, config?)` | `keyof T, NumberCellRendererConfig?` | Locale-aware number cell renderer |
|
|
1222
|
+
| `.currency(field, config?)` | `keyof T, CurrencyCellRendererConfig?` | Locale-aware currency cell renderer |
|
|
1223
|
+
| `.tag(field, config)` | `keyof T, TagConfig` | Tag cell renderer |
|
|
1224
|
+
| `.icon(field, config?)` | `keyof T, IconConfig?` | Icon cell renderer |
|
|
1225
|
+
| `.tree(field, config?)` | `keyof T, TreeCellRendererConfig?` | Tree column with expand/collapse |
|