@gridstorm/vue 0.1.2

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/dist/index.js ADDED
@@ -0,0 +1,458 @@
1
+ import { defineComponent, ref, shallowRef, provide, onMounted, onBeforeUnmount, watch, h, computed, onUnmounted, inject } from 'vue';
2
+ import { createGrid } from '@gridstorm/core';
3
+ import { DomRenderer } from '@gridstorm/dom-renderer';
4
+
5
+ // src/GridStorm.ts
6
+
7
+ // src/types.ts
8
+ var gridStormPropDefs = {
9
+ columns: {
10
+ type: Array,
11
+ required: true
12
+ },
13
+ rowData: {
14
+ type: Array,
15
+ required: true
16
+ },
17
+ plugins: {
18
+ type: Array,
19
+ default: () => []
20
+ },
21
+ getRowId: {
22
+ type: Function,
23
+ default: void 0
24
+ },
25
+ rowHeight: {
26
+ type: Number,
27
+ default: 40
28
+ },
29
+ headerHeight: {
30
+ type: Number,
31
+ default: void 0
32
+ },
33
+ theme: {
34
+ type: String,
35
+ default: "light"
36
+ },
37
+ density: {
38
+ type: String,
39
+ default: "normal"
40
+ },
41
+ defaultColDef: {
42
+ type: Object,
43
+ default: void 0
44
+ },
45
+ paginationPageSize: {
46
+ type: Number,
47
+ default: void 0
48
+ },
49
+ pagination: {
50
+ type: Boolean,
51
+ default: void 0
52
+ },
53
+ rowSelection: {
54
+ type: [String, Boolean],
55
+ default: void 0
56
+ },
57
+ editType: {
58
+ type: String,
59
+ default: void 0
60
+ },
61
+ ariaLabel: {
62
+ type: String,
63
+ default: void 0
64
+ },
65
+ height: {
66
+ type: [Number, String],
67
+ default: "100%"
68
+ },
69
+ width: {
70
+ type: [Number, String],
71
+ default: "100%"
72
+ },
73
+ containerClass: {
74
+ type: String,
75
+ default: void 0
76
+ }
77
+ };
78
+ var GRID_CONTEXT_KEY = /* @__PURE__ */ Symbol("gridstorm-context");
79
+ function useGridContext() {
80
+ const context = inject(GRID_CONTEXT_KEY, null);
81
+ if (!context) {
82
+ throw new Error(
83
+ "[GridStorm] Composable must be used within a <GridStorm> component."
84
+ );
85
+ }
86
+ return context;
87
+ }
88
+ function useStoreRef(context, selector) {
89
+ const value = ref();
90
+ let unsubscribe = null;
91
+ function subscribe(ctx) {
92
+ if (unsubscribe) {
93
+ unsubscribe();
94
+ unsubscribe = null;
95
+ }
96
+ if (!ctx) {
97
+ value.value = void 0;
98
+ return;
99
+ }
100
+ value.value = selector(ctx.engine);
101
+ unsubscribe = ctx.engine.store.subscribe(() => {
102
+ value.value = selector(ctx.engine);
103
+ });
104
+ }
105
+ watch(context, (newCtx) => subscribe(newCtx), { immediate: true });
106
+ onUnmounted(() => {
107
+ if (unsubscribe) {
108
+ unsubscribe();
109
+ unsubscribe = null;
110
+ }
111
+ });
112
+ return value;
113
+ }
114
+ function useGridApi() {
115
+ const context = useGridContext();
116
+ return computed(() => context.value?.api);
117
+ }
118
+ function useGridEngine() {
119
+ const context = useGridContext();
120
+ return computed(() => context.value?.engine);
121
+ }
122
+ function useGridSort() {
123
+ const context = useGridContext();
124
+ const sortModel = useStoreRef(
125
+ context,
126
+ (engine) => engine.store.getState().sortModel
127
+ );
128
+ const isSorted = computed(() => (sortModel.value?.length ?? 0) > 0);
129
+ function setSortModel(model) {
130
+ context.value?.api.setSortModel(model);
131
+ }
132
+ function toggleSort(colId, multiSort = false) {
133
+ context.value?.engine.commandBus.dispatch("sort:toggle", {
134
+ colId,
135
+ multiSort
136
+ });
137
+ }
138
+ function clearSort() {
139
+ context.value?.api.setSortModel([]);
140
+ }
141
+ return {
142
+ sortModel,
143
+ isSorted,
144
+ setSortModel,
145
+ toggleSort,
146
+ clearSort
147
+ };
148
+ }
149
+ function useGridFilter() {
150
+ const context = useGridContext();
151
+ const filterModel = useStoreRef(
152
+ context,
153
+ (engine) => engine.store.getState().filterModel
154
+ );
155
+ const quickFilterText = useStoreRef(
156
+ context,
157
+ (engine) => engine.store.getState().quickFilterText
158
+ );
159
+ const isFiltered = computed(() => {
160
+ const hasColumnFilters = Object.keys(filterModel.value ?? {}).length > 0;
161
+ const hasQuickFilter = (quickFilterText.value ?? "").length > 0;
162
+ return hasColumnFilters || hasQuickFilter;
163
+ });
164
+ function setFilterModel(model) {
165
+ context.value?.api.setFilterModel(model);
166
+ }
167
+ function setQuickFilter(text) {
168
+ context.value?.api.setQuickFilter(text);
169
+ }
170
+ function clearFilters() {
171
+ context.value?.api.setFilterModel({});
172
+ context.value?.api.setQuickFilter("");
173
+ }
174
+ return {
175
+ filterModel,
176
+ quickFilterText,
177
+ isFiltered,
178
+ setFilterModel,
179
+ setQuickFilter,
180
+ clearFilters
181
+ };
182
+ }
183
+ function useGridSelection() {
184
+ const context = useGridContext();
185
+ const selectedRowIds = useStoreRef(
186
+ context,
187
+ (engine) => engine.store.getState().selection.selectedRowIds
188
+ );
189
+ const selectedCount = computed(() => selectedRowIds.value?.size ?? 0);
190
+ function getSelectedRows() {
191
+ return context.value?.api.getSelectedRows() ?? [];
192
+ }
193
+ function getSelectedNodes() {
194
+ return context.value?.api.getSelectedNodes() ?? [];
195
+ }
196
+ function isRowSelected(rowId) {
197
+ return selectedRowIds.value?.has(rowId) ?? false;
198
+ }
199
+ function selectAll() {
200
+ context.value?.api.selectAll();
201
+ }
202
+ function deselectAll() {
203
+ context.value?.api.deselectAll();
204
+ }
205
+ return {
206
+ selectedRowIds,
207
+ selectedCount,
208
+ getSelectedRows,
209
+ getSelectedNodes,
210
+ isRowSelected,
211
+ selectAll,
212
+ deselectAll
213
+ };
214
+ }
215
+ function useGridPagination() {
216
+ const context = useGridContext();
217
+ const paginationState = useStoreRef(
218
+ context,
219
+ (engine) => engine.store.getState().pagination
220
+ );
221
+ const currentPage = computed(() => paginationState.value?.currentPage ?? 0);
222
+ const pageSize = computed(() => paginationState.value?.pageSize ?? 100);
223
+ const totalRows = computed(() => paginationState.value?.totalRows ?? 0);
224
+ const totalPages = computed(
225
+ () => Math.max(1, Math.ceil(totalRows.value / pageSize.value))
226
+ );
227
+ const hasNextPage = computed(() => currentPage.value < totalPages.value - 1);
228
+ const hasPreviousPage = computed(() => currentPage.value > 0);
229
+ function goToPage(page) {
230
+ context.value?.api.paginationGoToPage(page);
231
+ }
232
+ function nextPage() {
233
+ if (hasNextPage.value) {
234
+ context.value?.api.paginationGoToPage(currentPage.value + 1);
235
+ }
236
+ }
237
+ function previousPage() {
238
+ if (hasPreviousPage.value) {
239
+ context.value?.api.paginationGoToPage(currentPage.value - 1);
240
+ }
241
+ }
242
+ function firstPage() {
243
+ context.value?.api.paginationGoToPage(0);
244
+ }
245
+ function lastPage() {
246
+ context.value?.api.paginationGoToPage(totalPages.value - 1);
247
+ }
248
+ return {
249
+ currentPage,
250
+ totalPages,
251
+ pageSize,
252
+ totalRows,
253
+ hasNextPage,
254
+ hasPreviousPage,
255
+ goToPage,
256
+ nextPage,
257
+ previousPage,
258
+ firstPage,
259
+ lastPage
260
+ };
261
+ }
262
+ function useGridEvent(event, handler) {
263
+ const context = useGridContext();
264
+ let unsubscribe = null;
265
+ watch(
266
+ context,
267
+ (ctx) => {
268
+ if (unsubscribe) {
269
+ unsubscribe();
270
+ unsubscribe = null;
271
+ }
272
+ if (ctx) {
273
+ unsubscribe = ctx.engine.eventBus.on(event, handler);
274
+ }
275
+ },
276
+ { immediate: true }
277
+ );
278
+ onUnmounted(() => {
279
+ if (unsubscribe) {
280
+ unsubscribe();
281
+ unsubscribe = null;
282
+ }
283
+ });
284
+ }
285
+
286
+ // src/GridStorm.ts
287
+ var GridStorm = defineComponent({
288
+ name: "GridStorm",
289
+ props: gridStormPropDefs,
290
+ emits: [
291
+ "gridReady",
292
+ "rowDataChanged",
293
+ "selectionChanged",
294
+ "sortChanged",
295
+ "filterChanged",
296
+ "cellValueChanged",
297
+ "cellClicked",
298
+ "cellDoubleClicked",
299
+ "rowClicked",
300
+ "paginationChanged",
301
+ "columnResized"
302
+ ],
303
+ setup(props, { emit, expose }) {
304
+ const containerRef = ref(null);
305
+ let engine = null;
306
+ let renderer = null;
307
+ const eventUnsubscribers = [];
308
+ function buildConfig() {
309
+ return {
310
+ columns: props.columns,
311
+ rowData: props.rowData,
312
+ plugins: props.plugins,
313
+ getRowId: props.getRowId,
314
+ rowHeight: props.rowHeight,
315
+ headerHeight: props.headerHeight,
316
+ defaultColDef: props.defaultColDef,
317
+ paginationPageSize: props.paginationPageSize,
318
+ pagination: props.pagination,
319
+ rowSelection: props.rowSelection,
320
+ editType: props.editType,
321
+ ariaLabel: props.ariaLabel
322
+ };
323
+ }
324
+ function subscribeToEvents() {
325
+ if (!engine) return;
326
+ const eb = engine.eventBus;
327
+ eventUnsubscribers.push(
328
+ eb.on("rowData:changed", (e) => emit("rowDataChanged", e))
329
+ );
330
+ eventUnsubscribers.push(
331
+ eb.on("selection:changed", (e) => emit("selectionChanged", e))
332
+ );
333
+ eventUnsubscribers.push(
334
+ eb.on("column:sort:changed", (e) => emit("sortChanged", e))
335
+ );
336
+ eventUnsubscribers.push(
337
+ eb.on("filter:changed", (e) => emit("filterChanged", e))
338
+ );
339
+ eventUnsubscribers.push(
340
+ eb.on("cell:valueChanged", (e) => emit("cellValueChanged", e))
341
+ );
342
+ eventUnsubscribers.push(
343
+ eb.on("cell:clicked", (e) => emit("cellClicked", e))
344
+ );
345
+ eventUnsubscribers.push(
346
+ eb.on("cell:doubleClicked", (e) => emit("cellDoubleClicked", e))
347
+ );
348
+ eventUnsubscribers.push(
349
+ eb.on("row:clicked", (e) => emit("rowClicked", e))
350
+ );
351
+ eventUnsubscribers.push(
352
+ eb.on("pagination:changed", (e) => emit("paginationChanged", e))
353
+ );
354
+ eventUnsubscribers.push(
355
+ eb.on("column:resized", (e) => emit("columnResized", e))
356
+ );
357
+ }
358
+ const gridContext = shallowRef(null);
359
+ provide(GRID_CONTEXT_KEY, gridContext);
360
+ function initGrid() {
361
+ if (!containerRef.value) return;
362
+ const config = buildConfig();
363
+ engine = createGrid(config);
364
+ gridContext.value = {
365
+ engine,
366
+ api: engine.api
367
+ };
368
+ renderer = new DomRenderer({
369
+ container: containerRef.value,
370
+ engine
371
+ });
372
+ renderer.mount();
373
+ subscribeToEvents();
374
+ emit("gridReady", engine.api);
375
+ }
376
+ function destroyGrid() {
377
+ for (const unsub of eventUnsubscribers) {
378
+ unsub();
379
+ }
380
+ eventUnsubscribers.length = 0;
381
+ renderer?.destroy();
382
+ renderer = null;
383
+ engine?.destroy();
384
+ engine = null;
385
+ gridContext.value = null;
386
+ }
387
+ onMounted(() => {
388
+ initGrid();
389
+ });
390
+ onBeforeUnmount(() => {
391
+ destroyGrid();
392
+ });
393
+ watch(
394
+ () => props.rowData,
395
+ (newData) => {
396
+ if (engine && newData) {
397
+ engine.api.setRowData(newData);
398
+ }
399
+ },
400
+ { deep: false }
401
+ );
402
+ watch(
403
+ () => props.columns,
404
+ (newCols) => {
405
+ if (engine && newCols) {
406
+ engine.api.setColumnDefs(newCols);
407
+ }
408
+ },
409
+ { deep: false }
410
+ );
411
+ watch(
412
+ () => props.theme,
413
+ (newTheme) => {
414
+ if (containerRef.value && newTheme) {
415
+ containerRef.value.setAttribute("data-theme", newTheme);
416
+ }
417
+ }
418
+ );
419
+ watch(
420
+ () => props.density,
421
+ (newDensity) => {
422
+ if (containerRef.value && newDensity) {
423
+ containerRef.value.setAttribute("data-density", newDensity);
424
+ }
425
+ }
426
+ );
427
+ expose({
428
+ /**
429
+ * Get the GridApi instance.
430
+ * Returns undefined if the grid has not been initialized yet.
431
+ */
432
+ getApi: () => engine?.api,
433
+ /**
434
+ * Get the GridEngine instance.
435
+ * Returns undefined if the grid has not been initialized yet.
436
+ */
437
+ getEngine: () => engine
438
+ });
439
+ return () => {
440
+ const heightStyle = typeof props.height === "number" ? `${props.height}px` : props.height;
441
+ const widthStyle = typeof props.width === "number" ? `${props.width}px` : props.width;
442
+ return h("div", {
443
+ ref: containerRef,
444
+ class: ["gridstorm-wrapper", props.containerClass].filter(Boolean).join(" "),
445
+ "data-theme": props.theme,
446
+ "data-density": props.density,
447
+ style: {
448
+ width: widthStyle,
449
+ height: heightStyle
450
+ }
451
+ });
452
+ };
453
+ }
454
+ });
455
+
456
+ export { GRID_CONTEXT_KEY, GridStorm, useGridApi, useGridEngine, useGridEvent, useGridFilter, useGridPagination, useGridSelection, useGridSort };
457
+ //# sourceMappingURL=index.js.map
458
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/composables.ts","../src/GridStorm.ts"],"names":["ref","watch"],"mappings":"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","file":"index.js","sourcesContent":["// ─── Vue Adapter Types ───\n// Central type definitions for all Vue-specific interfaces.\n\nimport type {\n ColumnDef,\n GridApi,\n GridConfig,\n GridEngine,\n GridEventMap,\n GridPlugin,\n SortModelItem,\n FilterModel,\n RowNode,\n} from '@gridstorm/core';\nimport type { PropType } from 'vue';\n\n// ── Grid Context ──\n\n/**\n * Context value provided to child composables via Vue provide/inject.\n */\nexport interface GridContextValue<TData = any> {\n /** The grid engine instance. */\n engine: GridEngine<TData>;\n /** The public grid API. */\n api: GridApi<TData>;\n}\n\n// ── Component Props ──\n\n/**\n * Props interface for the GridStorm Vue component.\n *\n * @typeParam TData - The type of each row data object.\n */\nexport interface GridStormProps<TData = any> {\n /** Column definitions. */\n columns: ColumnDef<TData>[];\n /** Row data array. */\n rowData: TData[];\n /** Plugins to install. */\n plugins?: GridPlugin<TData>[];\n /** Row ID getter function. */\n getRowId?: GridConfig<TData>['getRowId'];\n /** Row height in pixels. */\n rowHeight?: number;\n /** Header height in pixels. */\n headerHeight?: number;\n /** Theme identifier (e.g., 'light', 'dark'). */\n theme?: string;\n /** Density mode (e.g., 'compact', 'normal', 'comfortable'). */\n density?: string;\n /** Default column definition applied to all columns. */\n defaultColDef?: Partial<ColumnDef<TData>>;\n /** Number of rows per page when pagination is enabled. */\n paginationPageSize?: number;\n /** Enable client-side pagination. */\n pagination?: boolean;\n /** Row selection mode. */\n rowSelection?: 'single' | 'multiple' | false;\n /** Edit type mode. */\n editType?: 'cell' | 'fullRow';\n /** ARIA label for the grid root element. */\n ariaLabel?: string;\n /** Container height. Default: '100%'. */\n height?: number | string;\n /** Container width. Default: '100%'. */\n width?: number | string;\n /** Additional CSS class for the container. */\n containerClass?: string;\n}\n\n/**\n * Vue prop definitions for the GridStorm component.\n * Used with defineComponent's props option.\n */\nexport const gridStormPropDefs = {\n columns: {\n type: Array as PropType<ColumnDef[]>,\n required: true as const,\n },\n rowData: {\n type: Array as PropType<any[]>,\n required: true as const,\n },\n plugins: {\n type: Array as PropType<GridPlugin[]>,\n default: () => [],\n },\n getRowId: {\n type: Function as PropType<GridConfig['getRowId']>,\n default: undefined,\n },\n rowHeight: {\n type: Number,\n default: 40,\n },\n headerHeight: {\n type: Number,\n default: undefined,\n },\n theme: {\n type: String,\n default: 'light',\n },\n density: {\n type: String,\n default: 'normal',\n },\n defaultColDef: {\n type: Object as PropType<Partial<ColumnDef>>,\n default: undefined,\n },\n paginationPageSize: {\n type: Number,\n default: undefined,\n },\n pagination: {\n type: Boolean,\n default: undefined,\n },\n rowSelection: {\n type: [String, Boolean] as PropType<'single' | 'multiple' | false>,\n default: undefined,\n },\n editType: {\n type: String as PropType<'cell' | 'fullRow'>,\n default: undefined,\n },\n ariaLabel: {\n type: String,\n default: undefined,\n },\n height: {\n type: [Number, String] as PropType<number | string>,\n default: '100%',\n },\n width: {\n type: [Number, String] as PropType<number | string>,\n default: '100%',\n },\n containerClass: {\n type: String,\n default: undefined,\n },\n} as const;\n\n// ── Event Types ──\n\n/**\n * All events emitted by the GridStorm Vue component.\n */\nexport interface GridStormEmits<TData = any> {\n /** Fired when the grid engine is ready and the API is available. */\n gridReady: [api: GridApi<TData>];\n /** Fired when row data changes. */\n rowDataChanged: [event: GridEventMap<TData>['rowData:changed']];\n /** Fired when the selection changes. */\n selectionChanged: [event: GridEventMap<TData>['selection:changed']];\n /** Fired when the sort model changes. */\n sortChanged: [event: GridEventMap<TData>['column:sort:changed']];\n /** Fired when the filter model changes. */\n filterChanged: [event: GridEventMap<TData>['filter:changed']];\n /** Fired when a cell value is changed via editing. */\n cellValueChanged: [event: GridEventMap<TData>['cell:valueChanged']];\n /** Fired when a cell is clicked. */\n cellClicked: [event: GridEventMap<TData>['cell:clicked']];\n /** Fired when a cell is double-clicked. */\n cellDoubleClicked: [event: GridEventMap<TData>['cell:doubleClicked']];\n /** Fired when a row is clicked. */\n rowClicked: [event: GridEventMap<TData>['row:clicked']];\n /** Fired when pagination state changes. */\n paginationChanged: [event: GridEventMap<TData>['pagination:changed']];\n /** Fired when a column is resized. */\n columnResized: [event: GridEventMap<TData>['column:resized']];\n}\n\n// ── Exposed API ──\n\n/**\n * Public methods exposed by the GridStorm component via template refs.\n *\n * @typeParam TData - The type of each row data object.\n */\nexport interface GridStormExposed<TData = any> {\n /** Get the GridApi instance. */\n getApi(): GridApi<TData> | undefined;\n /** Get the GridEngine instance. */\n getEngine(): GridEngine<TData> | undefined;\n}\n\n// ── Composable Return Types ──\n\n/**\n * Return type for the useGridSort composable.\n */\nexport interface GridSortResult {\n /** Current sort model (reactive). */\n sortModel: SortModelItem[];\n /** Whether any sort is active (reactive). */\n isSorted: boolean;\n /** Set the sort model directly. */\n setSortModel: (model: SortModelItem[]) => void;\n /** Toggle sort on a column. */\n toggleSort: (colId: string, multiSort?: boolean) => void;\n /** Clear all sorting. */\n clearSort: () => void;\n}\n\n/**\n * Return type for the useGridFilter composable.\n */\nexport interface GridFilterResult {\n /** Current filter model keyed by column ID (reactive). */\n filterModel: Record<string, FilterModel>;\n /** Current quick filter text (reactive). */\n quickFilterText: string;\n /** Whether any filter is active (reactive). */\n isFiltered: boolean;\n /** Set the filter model. */\n setFilterModel: (model: Record<string, FilterModel>) => void;\n /** Set quick filter text. */\n setQuickFilter: (text: string) => void;\n /** Clear all filters. */\n clearFilters: () => void;\n}\n\n/**\n * Return type for the useGridSelection composable.\n */\nexport interface GridSelectionResult<TData = any> {\n /** Set of selected row IDs (reactive). */\n selectedRowIds: Set<string>;\n /** Number of selected rows (reactive). */\n selectedCount: number;\n /** Get selected row data objects. */\n getSelectedRows: () => TData[];\n /** Get selected RowNode objects. */\n getSelectedNodes: () => RowNode<TData>[];\n /** Check if a specific row is selected. */\n isRowSelected: (rowId: string) => boolean;\n /** Select all visible rows. */\n selectAll: () => void;\n /** Deselect all rows. */\n deselectAll: () => void;\n}\n\n/**\n * Return type for the useGridPagination composable.\n */\nexport interface GridPaginationResult {\n /** Current page (0-indexed, reactive). */\n currentPage: number;\n /** Total number of pages (reactive). */\n totalPages: number;\n /** Rows per page (reactive). */\n pageSize: number;\n /** Total row count after filtering (reactive). */\n totalRows: number;\n /** Whether there is a next page (reactive). */\n hasNextPage: boolean;\n /** Whether there is a previous page (reactive). */\n hasPreviousPage: boolean;\n /** Go to a specific page. */\n goToPage: (page: number) => void;\n /** Go to next page. */\n nextPage: () => void;\n /** Go to previous page. */\n previousPage: () => void;\n /** Go to first page. */\n firstPage: () => void;\n /** Go to last page. */\n lastPage: () => void;\n}\n","// ─── Vue 3 Composables for GridStorm ───\n// Provide/inject-based composables that mirror the React hooks API.\n// All composables must be called inside a component that is a child of <GridStorm>.\n\nimport {\n inject,\n ref,\n onUnmounted,\n computed,\n watch,\n type InjectionKey,\n type Ref,\n type ShallowRef,\n} from 'vue';\nimport type { GridApi, GridEngine, SortModelItem, FilterModel, RowNode } from '@gridstorm/core';\nimport type { GridContextValue } from './types';\n\n// ── Context Key ──\n\n/**\n * Injection key used internally to provide/inject the grid context.\n * Uses ShallowRef to avoid deep reactive unwrapping of GridEngine internals\n * (the Store class has private fields incompatible with Vue's deep proxy typing).\n */\nexport const GRID_CONTEXT_KEY: InjectionKey<ShallowRef<GridContextValue | null>> =\n Symbol('gridstorm-context');\n\n// ── Internal helper ──\n\n/**\n * Internal helper to get the grid context with validation.\n * Throws if called outside a <GridStorm> component hierarchy.\n */\nfunction useGridContext<TData = any>(): ShallowRef<GridContextValue<TData> | null> {\n const context = inject(GRID_CONTEXT_KEY, null);\n if (!context) {\n throw new Error(\n '[GridStorm] Composable must be used within a <GridStorm> component.',\n );\n }\n return context as ShallowRef<GridContextValue<TData> | null>;\n}\n\n/**\n * Subscribe to the grid store and keep a reactive ref in sync.\n * Returns the reactive ref and automatically unsubscribes on unmount.\n */\nfunction useStoreRef<T>(\n context: ShallowRef<GridContextValue | null>,\n selector: (engine: GridEngine) => T,\n): Ref<T | undefined> {\n const value = ref<T | undefined>() as Ref<T | undefined>;\n let unsubscribe: (() => void) | null = null;\n\n function subscribe(ctx: GridContextValue | null) {\n // Clean up previous subscription\n if (unsubscribe) {\n unsubscribe();\n unsubscribe = null;\n }\n\n if (!ctx) {\n value.value = undefined;\n return;\n }\n\n // Set initial value\n value.value = selector(ctx.engine);\n\n // Subscribe to store changes\n unsubscribe = ctx.engine.store.subscribe(() => {\n value.value = selector(ctx.engine);\n });\n }\n\n // Watch for context changes (grid initialization/destruction)\n watch(context, (newCtx) => subscribe(newCtx), { immediate: true });\n\n onUnmounted(() => {\n if (unsubscribe) {\n unsubscribe();\n unsubscribe = null;\n }\n });\n\n return value;\n}\n\n// ── Public Composables ──\n\n/**\n * Access the GridApi instance from a parent GridStorm component.\n *\n * Returns a computed ref that resolves to the GridApi once the grid is initialized.\n * The ref will be undefined until the grid has mounted.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridApi } from '@gridstorm/vue';\n *\n * const api = useGridApi();\n *\n * function exportData() {\n * const rows = api.value?.getSelectedRows() ?? [];\n * console.log('Selected:', rows);\n * }\n * </script>\n * ```\n */\nexport function useGridApi<TData = any>(): Ref<GridApi<TData> | undefined> {\n const context = useGridContext<TData>();\n return computed(() => context.value?.api);\n}\n\n/**\n * Access the GridEngine instance from a parent GridStorm component.\n *\n * Primarily useful for advanced use cases that need direct access to\n * the store, event bus, or command bus.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridEngine } from '@gridstorm/vue';\n *\n * const engine = useGridEngine();\n * </script>\n * ```\n */\nexport function useGridEngine<TData = any>(): Ref<GridEngine<TData> | undefined> {\n const context = useGridContext<TData>();\n return computed(() => context.value?.engine);\n}\n\n/**\n * Reactive sort model state and sort actions.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridSort } from '@gridstorm/vue';\n *\n * const { sortModel, isSorted, toggleSort, clearSort } = useGridSort();\n * </script>\n *\n * <template>\n * <button @click=\"toggleSort('name')\">Sort by Name</button>\n * <button v-if=\"isSorted\" @click=\"clearSort()\">Clear Sort</button>\n * <pre>{{ sortModel }}</pre>\n * </template>\n * ```\n */\nexport function useGridSort(): {\n sortModel: Ref<SortModelItem[]>;\n isSorted: Ref<boolean>;\n setSortModel: (model: SortModelItem[]) => void;\n toggleSort: (colId: string, multiSort?: boolean) => void;\n clearSort: () => void;\n} {\n const context = useGridContext();\n\n const sortModel = useStoreRef(context, (engine) =>\n engine.store.getState().sortModel,\n );\n\n const isSorted = computed(() => (sortModel.value?.length ?? 0) > 0);\n\n function setSortModel(model: SortModelItem[]): void {\n context.value?.api.setSortModel(model);\n }\n\n function toggleSort(colId: string, multiSort = false): void {\n context.value?.engine.commandBus.dispatch('sort:toggle', {\n colId,\n multiSort,\n });\n }\n\n function clearSort(): void {\n context.value?.api.setSortModel([]);\n }\n\n return {\n sortModel: sortModel as Ref<SortModelItem[]>,\n isSorted,\n setSortModel,\n toggleSort,\n clearSort,\n };\n}\n\n/**\n * Reactive filter model state and filter actions.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridFilter } from '@gridstorm/vue';\n *\n * const { isFiltered, setQuickFilter, clearFilters } = useGridFilter();\n * </script>\n *\n * <template>\n * <input @input=\"(e) => setQuickFilter((e.target as HTMLInputElement).value)\" />\n * <button v-if=\"isFiltered\" @click=\"clearFilters()\">Clear Filters</button>\n * </template>\n * ```\n */\nexport function useGridFilter(): {\n filterModel: Ref<Record<string, FilterModel>>;\n quickFilterText: Ref<string>;\n isFiltered: Ref<boolean>;\n setFilterModel: (model: Record<string, FilterModel>) => void;\n setQuickFilter: (text: string) => void;\n clearFilters: () => void;\n} {\n const context = useGridContext();\n\n const filterModel = useStoreRef(context, (engine) =>\n engine.store.getState().filterModel,\n );\n\n const quickFilterText = useStoreRef(context, (engine) =>\n engine.store.getState().quickFilterText,\n );\n\n const isFiltered = computed(() => {\n const hasColumnFilters = Object.keys(filterModel.value ?? {}).length > 0;\n const hasQuickFilter = (quickFilterText.value ?? '').length > 0;\n return hasColumnFilters || hasQuickFilter;\n });\n\n function setFilterModel(model: Record<string, FilterModel>): void {\n context.value?.api.setFilterModel(model);\n }\n\n function setQuickFilter(text: string): void {\n context.value?.api.setQuickFilter(text);\n }\n\n function clearFilters(): void {\n context.value?.api.setFilterModel({});\n context.value?.api.setQuickFilter('');\n }\n\n return {\n filterModel: filterModel as Ref<Record<string, FilterModel>>,\n quickFilterText: quickFilterText as Ref<string>,\n isFiltered,\n setFilterModel,\n setQuickFilter,\n clearFilters,\n };\n}\n\n/**\n * Reactive selection state and selection actions.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridSelection } from '@gridstorm/vue';\n *\n * const { selectedCount, selectAll, deselectAll, isRowSelected } = useGridSelection();\n * </script>\n *\n * <template>\n * <p>{{ selectedCount }} rows selected</p>\n * <button @click=\"selectAll()\">Select All</button>\n * <button @click=\"deselectAll()\">Deselect All</button>\n * </template>\n * ```\n */\nexport function useGridSelection<TData = any>(): {\n selectedRowIds: Ref<Set<string>>;\n selectedCount: Ref<number>;\n getSelectedRows: () => TData[];\n getSelectedNodes: () => RowNode<TData>[];\n isRowSelected: (rowId: string) => boolean;\n selectAll: () => void;\n deselectAll: () => void;\n} {\n const context = useGridContext<TData>();\n\n const selectedRowIds = useStoreRef(context, (engine) =>\n engine.store.getState().selection.selectedRowIds,\n );\n\n const selectedCount = computed(() => selectedRowIds.value?.size ?? 0);\n\n function getSelectedRows(): TData[] {\n return context.value?.api.getSelectedRows() ?? [];\n }\n\n function getSelectedNodes(): RowNode<TData>[] {\n return context.value?.api.getSelectedNodes() ?? [];\n }\n\n function isRowSelected(rowId: string): boolean {\n return selectedRowIds.value?.has(rowId) ?? false;\n }\n\n function selectAll(): void {\n context.value?.api.selectAll();\n }\n\n function deselectAll(): void {\n context.value?.api.deselectAll();\n }\n\n return {\n selectedRowIds: selectedRowIds as Ref<Set<string>>,\n selectedCount,\n getSelectedRows,\n getSelectedNodes,\n isRowSelected,\n selectAll,\n deselectAll,\n };\n}\n\n/**\n * Reactive pagination state and navigation actions.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridPagination } from '@gridstorm/vue';\n *\n * const {\n * currentPage,\n * totalPages,\n * hasNextPage,\n * hasPreviousPage,\n * nextPage,\n * previousPage,\n * } = useGridPagination();\n * </script>\n *\n * <template>\n * <div class=\"pagination\">\n * <button :disabled=\"!hasPreviousPage\" @click=\"previousPage()\">Prev</button>\n * <span>Page {{ currentPage + 1 }} of {{ totalPages }}</span>\n * <button :disabled=\"!hasNextPage\" @click=\"nextPage()\">Next</button>\n * </div>\n * </template>\n * ```\n */\nexport function useGridPagination(): {\n currentPage: Ref<number>;\n totalPages: Ref<number>;\n pageSize: Ref<number>;\n totalRows: Ref<number>;\n hasNextPage: Ref<boolean>;\n hasPreviousPage: Ref<boolean>;\n goToPage: (page: number) => void;\n nextPage: () => void;\n previousPage: () => void;\n firstPage: () => void;\n lastPage: () => void;\n} {\n const context = useGridContext();\n\n const paginationState = useStoreRef(context, (engine) =>\n engine.store.getState().pagination,\n );\n\n const currentPage = computed(() => paginationState.value?.currentPage ?? 0);\n const pageSize = computed(() => paginationState.value?.pageSize ?? 100);\n const totalRows = computed(() => paginationState.value?.totalRows ?? 0);\n const totalPages = computed(() =>\n Math.max(1, Math.ceil(totalRows.value / pageSize.value)),\n );\n const hasNextPage = computed(() => currentPage.value < totalPages.value - 1);\n const hasPreviousPage = computed(() => currentPage.value > 0);\n\n function goToPage(page: number): void {\n context.value?.api.paginationGoToPage(page);\n }\n\n function nextPage(): void {\n if (hasNextPage.value) {\n context.value?.api.paginationGoToPage(currentPage.value + 1);\n }\n }\n\n function previousPage(): void {\n if (hasPreviousPage.value) {\n context.value?.api.paginationGoToPage(currentPage.value - 1);\n }\n }\n\n function firstPage(): void {\n context.value?.api.paginationGoToPage(0);\n }\n\n function lastPage(): void {\n context.value?.api.paginationGoToPage(totalPages.value - 1);\n }\n\n return {\n currentPage,\n totalPages,\n pageSize,\n totalRows,\n hasNextPage,\n hasPreviousPage,\n goToPage,\n nextPage,\n previousPage,\n firstPage,\n lastPage,\n };\n}\n\n/**\n * Listen to a specific grid event with automatic cleanup on unmount.\n *\n * @param event - The event name from GridEventMap.\n * @param handler - Callback function invoked when the event fires.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useGridEvent } from '@gridstorm/vue';\n *\n * useGridEvent('selection:changed', (e) => {\n * console.log('Selection changed:', e.selectedNodes);\n * });\n * </script>\n * ```\n */\nexport function useGridEvent<TData = any>(\n event: string,\n handler: (payload: any) => void,\n): void {\n const context = useGridContext<TData>();\n let unsubscribe: (() => void) | null = null;\n\n watch(\n context,\n (ctx) => {\n // Clean up previous listener\n if (unsubscribe) {\n unsubscribe();\n unsubscribe = null;\n }\n\n if (ctx) {\n unsubscribe = ctx.engine.eventBus.on(event as any, handler);\n }\n },\n { immediate: true },\n );\n\n onUnmounted(() => {\n if (unsubscribe) {\n unsubscribe();\n unsubscribe = null;\n }\n });\n}\n","// ─── GridStorm Vue 3 Component ───\n// Production-grade Vue wrapper around the headless core engine.\n// Uses defineComponent with a render function (no .vue SFC) for tsup compatibility.\n// Supports event emission, reactive prop watching, and composable injection.\n\nimport {\n defineComponent,\n ref,\n shallowRef,\n onMounted,\n onBeforeUnmount,\n watch,\n provide,\n h,\n} from 'vue';\nimport type { GridEngine } from '@gridstorm/core';\nimport { createGrid } from '@gridstorm/core';\nimport { DomRenderer } from '@gridstorm/dom-renderer';\nimport { gridStormPropDefs } from './types';\nimport type { GridContextValue } from './types';\nimport { GRID_CONTEXT_KEY } from './composables';\n\n/**\n * GridStorm Vue 3 component.\n *\n * Wraps the headless GridStorm core engine and DOM renderer into a Vue component\n * with reactive prop watching, event emission, and provide/inject context for\n * child composables.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { GridStorm } from '@gridstorm/vue';\n * import { sortingPlugin } from '@gridstorm/plugin-sorting';\n *\n * const columns = [\n * { field: 'name', headerName: 'Name', sortable: true },\n * { field: 'age', headerName: 'Age', width: 100 },\n * ];\n * const rowData = [\n * { name: 'Alice', age: 30 },\n * { name: 'Bob', age: 25 },\n * ];\n * </script>\n *\n * <template>\n * <GridStorm\n * :columns=\"columns\"\n * :row-data=\"rowData\"\n * :plugins=\"[sortingPlugin()]\"\n * @grid-ready=\"(api) => console.log('Grid ready!', api)\"\n * />\n * </template>\n * ```\n */\nexport const GridStorm = defineComponent({\n name: 'GridStorm',\n\n props: gridStormPropDefs,\n\n emits: [\n 'gridReady',\n 'rowDataChanged',\n 'selectionChanged',\n 'sortChanged',\n 'filterChanged',\n 'cellValueChanged',\n 'cellClicked',\n 'cellDoubleClicked',\n 'rowClicked',\n 'paginationChanged',\n 'columnResized',\n ],\n\n setup(props, { emit, expose }) {\n const containerRef = ref<HTMLElement | null>(null);\n let engine: GridEngine | null = null;\n let renderer: DomRenderer | null = null;\n const eventUnsubscribers: Array<() => void> = [];\n\n // ── Build GridConfig from props ──\n function buildConfig() {\n return {\n columns: props.columns as any,\n rowData: props.rowData as any,\n plugins: props.plugins as any,\n getRowId: props.getRowId as any,\n rowHeight: props.rowHeight,\n headerHeight: props.headerHeight,\n defaultColDef: props.defaultColDef as any,\n paginationPageSize: props.paginationPageSize,\n pagination: props.pagination,\n rowSelection: props.rowSelection as any,\n editType: props.editType as any,\n ariaLabel: props.ariaLabel,\n };\n }\n\n // ── Subscribe to engine events and bridge to Vue emits ──\n function subscribeToEvents() {\n if (!engine) return;\n\n const eb = engine.eventBus;\n\n eventUnsubscribers.push(\n eb.on('rowData:changed', (e) => emit('rowDataChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('selection:changed', (e) => emit('selectionChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('column:sort:changed', (e) => emit('sortChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('filter:changed', (e) => emit('filterChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('cell:valueChanged', (e) => emit('cellValueChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('cell:clicked', (e) => emit('cellClicked', e)),\n );\n eventUnsubscribers.push(\n eb.on('cell:doubleClicked', (e) => emit('cellDoubleClicked', e)),\n );\n eventUnsubscribers.push(\n eb.on('row:clicked', (e) => emit('rowClicked', e)),\n );\n eventUnsubscribers.push(\n eb.on('pagination:changed', (e) => emit('paginationChanged', e)),\n );\n eventUnsubscribers.push(\n eb.on('column:resized', (e) => emit('columnResized', e)),\n );\n }\n\n // ── Provide context for composables ──\n // Use shallowRef to avoid deep unwrapping of GridEngine internals\n // (Store class has private fields that break Vue's deep reactive proxy typing)\n const gridContext = shallowRef<GridContextValue | null>(null);\n\n provide(GRID_CONTEXT_KEY, gridContext);\n\n // ── Initialize grid engine and renderer ──\n function initGrid() {\n if (!containerRef.value) return;\n\n const config = buildConfig();\n engine = createGrid(config);\n\n // Update context for composables\n gridContext.value = {\n engine,\n api: engine.api,\n };\n\n // Mount DOM renderer\n renderer = new DomRenderer({\n container: containerRef.value,\n engine,\n });\n renderer.mount();\n\n // Subscribe to events\n subscribeToEvents();\n\n // Emit gridReady\n emit('gridReady', engine.api);\n }\n\n // ── Teardown ──\n function destroyGrid() {\n // Unsubscribe from all events\n for (const unsub of eventUnsubscribers) {\n unsub();\n }\n eventUnsubscribers.length = 0;\n\n renderer?.destroy();\n renderer = null;\n\n engine?.destroy();\n engine = null;\n\n gridContext.value = null;\n }\n\n // ── Lifecycle ──\n onMounted(() => {\n initGrid();\n });\n\n onBeforeUnmount(() => {\n destroyGrid();\n });\n\n // ── Watch for rowData changes ──\n watch(\n () => props.rowData,\n (newData) => {\n if (engine && newData) {\n engine.api.setRowData(newData as any);\n }\n },\n { deep: false },\n );\n\n // ── Watch for column changes ──\n watch(\n () => props.columns,\n (newCols) => {\n if (engine && newCols) {\n engine.api.setColumnDefs(newCols as any);\n }\n },\n { deep: false },\n );\n\n // ── Watch for theme changes ──\n watch(\n () => props.theme,\n (newTheme) => {\n if (containerRef.value && newTheme) {\n containerRef.value.setAttribute('data-theme', newTheme);\n }\n },\n );\n\n // ── Watch for density changes ──\n watch(\n () => props.density,\n (newDensity) => {\n if (containerRef.value && newDensity) {\n containerRef.value.setAttribute('data-density', newDensity);\n }\n },\n );\n\n // ── Expose public API via template refs ──\n expose({\n /**\n * Get the GridApi instance.\n * Returns undefined if the grid has not been initialized yet.\n */\n getApi: () => engine?.api,\n\n /**\n * Get the GridEngine instance.\n * Returns undefined if the grid has not been initialized yet.\n */\n getEngine: () => engine,\n });\n\n // ── Render function ──\n return () => {\n const heightStyle =\n typeof props.height === 'number' ? `${props.height}px` : props.height;\n const widthStyle =\n typeof props.width === 'number' ? `${props.width}px` : props.width;\n\n return h('div', {\n ref: containerRef,\n class: ['gridstorm-wrapper', props.containerClass].filter(Boolean).join(' '),\n 'data-theme': props.theme,\n 'data-density': props.density,\n style: {\n width: widthStyle,\n height: heightStyle,\n },\n });\n };\n },\n});\n"]}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@gridstorm/vue",
3
+ "version": "0.1.2",
4
+ "description": "GridStorm Vue 3 adapter",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "source": "./src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "LICENSE.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "typecheck": "tsc --noEmit",
30
+ "clean": "rm -rf dist"
31
+ },
32
+ "dependencies": {
33
+ "@gridstorm/core": "workspace:*",
34
+ "@gridstorm/dom-renderer": "workspace:*"
35
+ },
36
+ "peerDependencies": {
37
+ "vue": "^3.3.0"
38
+ },
39
+ "devDependencies": {
40
+ "vue": "^3.5.0",
41
+ "tsup": "^8.2.0",
42
+ "typescript": "^5.5.0"
43
+ },
44
+ "sideEffects": false,
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "license": "MIT",
49
+ "keywords": [
50
+ "datagrid",
51
+ "vue",
52
+ "vue3",
53
+ "vue-grid",
54
+ "vue-table",
55
+ "gridstorm",
56
+ "composables"
57
+ ],
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/007krcs/grid-data.git",
61
+ "directory": "packages/vue-adapter"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/007krcs/grid-data/issues"
65
+ },
66
+ "homepage": "https://grid-data-analytics-explorer.vercel.app/",
67
+ "author": {
68
+ "name": "GridStorm",
69
+ "url": "https://grid-data-analytics-explorer.vercel.app/"
70
+ },
71
+ "funding": {
72
+ "type": "github",
73
+ "url": "https://github.com/sponsors/gridstorm"
74
+ },
75
+ "engines": {
76
+ "node": ">=18.0.0"
77
+ }
78
+ }