@alaarab/ogrid-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @alaarab/ogrid-core
2
+
3
+ Framework-agnostic types, hooks, and utilities for [OGrid](https://github.com/alaarab/ogrid) data tables.
4
+
5
+ This package is the shared foundation used by `@alaarab/ogrid-fluent` and `@alaarab/ogrid-material`. You typically don't need to install it directly -- both framework packages re-export everything from core.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @alaarab/ogrid-core
11
+ ```
12
+
13
+ ## What's Included
14
+
15
+ ### Types
16
+
17
+ - `IColumnDef<T>` -- Column definition with sorting, filtering, and rendering
18
+ - `IDataSource<T>` -- Server-side data source interface
19
+ - `IFetchParams` -- Parameters for `fetchPage()`
20
+ - `IFilters` -- Unified filter values (text, multi-select, people)
21
+ - `UserLike` -- Minimal user shape for people picker
22
+ - `IColumnFilterDef`, `IColumnMeta`, `IPageResult`, `ColumnFilterType`
23
+
24
+ ### Hooks
25
+
26
+ - `useFilterOptions(dataSource, fields)` -- Loads filter options for multi-select columns
27
+
28
+ ### Utilities
29
+
30
+ - `toDataGridFilterProps(filters)` -- Splits `IFilters` into `multiSelectFilters`, `textFilters`, `peopleFilters`
31
+ - `toUserLike(user)` -- Converts a user-like object to `UserLike`
32
+ - `exportToCsv(items, columns, getValue, filename)` -- Full CSV export
33
+ - `buildCsvHeader`, `buildCsvRows`, `triggerCsvDownload`, `escapeCsvValue` -- Low-level CSV helpers
34
+
35
+ ## License
36
+
37
+ MIT
@@ -0,0 +1 @@
1
+ export { useFilterOptions } from './useFilterOptions';
@@ -0,0 +1,40 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ /**
3
+ * Load filter options for the given fields from a data source.
4
+ *
5
+ * Accepts `IDataSource<T>` or a plain `{ fetchFilterOptions }` object.
6
+ */
7
+ export function useFilterOptions(dataSource, fields) {
8
+ const [filterOptions, setFilterOptions] = useState({});
9
+ const [loadingOptions, setLoadingOptions] = useState({});
10
+ const load = useCallback(async () => {
11
+ const fetcher = 'fetchFilterOptions' in dataSource && typeof dataSource.fetchFilterOptions === 'function'
12
+ ? dataSource.fetchFilterOptions.bind(dataSource)
13
+ : undefined;
14
+ if (!fetcher) {
15
+ setFilterOptions({});
16
+ setLoadingOptions({});
17
+ return;
18
+ }
19
+ const loading = {};
20
+ fields.forEach((f) => { loading[f] = true; });
21
+ setLoadingOptions(loading);
22
+ const results = {};
23
+ await Promise.all(fields.map(async (field) => {
24
+ try {
25
+ results[field] = await fetcher(field);
26
+ }
27
+ catch (e) {
28
+ console.error(`Error loading filter options for ${field}:`, e);
29
+ results[field] = [];
30
+ }
31
+ }));
32
+ setFilterOptions(results);
33
+ setLoadingOptions({});
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [dataSource, fields.slice().sort().join(',')]);
36
+ useEffect(() => {
37
+ load().catch(console.error);
38
+ }, [load]);
39
+ return { filterOptions, loadingOptions };
40
+ }
@@ -0,0 +1,5 @@
1
+ export { toUserLike, toDataGridFilterProps } from './types';
2
+ // Hooks
3
+ export { useFilterOptions } from './hooks';
4
+ // Utilities
5
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './utils';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ export function toUserLike(u) {
2
+ if (!u)
3
+ return undefined;
4
+ return {
5
+ id: u.id,
6
+ displayName: u.displayName,
7
+ email: 'email' in u && u.email ? u.email : (u.mail || u.userPrincipalName || ''),
8
+ photo: u.photo
9
+ };
10
+ }
11
+ /** Split IFilters into DataGridTable's multiSelect, text, and people props. */
12
+ export function toDataGridFilterProps(filters) {
13
+ const multiSelectFilters = {};
14
+ const textFilters = {};
15
+ const peopleFilters = {};
16
+ for (const [key, value] of Object.entries(filters)) {
17
+ if (value === undefined)
18
+ continue;
19
+ if (Array.isArray(value))
20
+ multiSelectFilters[key] = value;
21
+ else if (typeof value === 'string')
22
+ textFilters[key] = value;
23
+ else if (typeof value === 'object' && value !== null && 'email' in value)
24
+ peopleFilters[key] = value;
25
+ }
26
+ return { multiSelectFilters, textFilters, peopleFilters };
27
+ }
@@ -0,0 +1 @@
1
+ export { toUserLike, toDataGridFilterProps } from './dataGridTypes';
@@ -0,0 +1,34 @@
1
+ export function escapeCsvValue(value) {
2
+ if (value === null || value === undefined) {
3
+ return '';
4
+ }
5
+ const s = String(value);
6
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) {
7
+ return `"${s.replace(/"/g, '""')}"`;
8
+ }
9
+ return s;
10
+ }
11
+ export function buildCsvHeader(columns) {
12
+ return columns.map((c) => escapeCsvValue(c.name)).join(',');
13
+ }
14
+ export function buildCsvRows(items, columns, getValue) {
15
+ return items.map((item) => columns.map((c) => escapeCsvValue(getValue(item, c.columnId))).join(','));
16
+ }
17
+ export function exportToCsv(items, columns, getValue, filename) {
18
+ const header = buildCsvHeader(columns);
19
+ const rows = buildCsvRows(items, columns, getValue);
20
+ const csv = [header, ...rows].join('\n');
21
+ triggerCsvDownload(csv, filename ?? `export_${new Date().toISOString().slice(0, 10)}.csv`);
22
+ }
23
+ export function triggerCsvDownload(csvContent, filename) {
24
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
25
+ const link = document.createElement('a');
26
+ const url = URL.createObjectURL(blob);
27
+ link.setAttribute('href', url);
28
+ link.setAttribute('download', filename);
29
+ link.style.visibility = 'hidden';
30
+ document.body.appendChild(link);
31
+ link.click();
32
+ document.body.removeChild(link);
33
+ URL.revokeObjectURL(url);
34
+ }
@@ -0,0 +1 @@
1
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './exportToCsv';
@@ -0,0 +1,2 @@
1
+ export { useFilterOptions } from './useFilterOptions';
2
+ export type { UseFilterOptionsResult } from './useFilterOptions';
@@ -0,0 +1,16 @@
1
+ import type { IDataSource } from '../types/dataGridTypes';
2
+ export interface UseFilterOptionsResult {
3
+ filterOptions: Record<string, string[]>;
4
+ loadingOptions: Record<string, boolean>;
5
+ }
6
+ /** Accepted data source shapes for useFilterOptions. */
7
+ type FilterOptionsSource = IDataSource<unknown> | {
8
+ fetchFilterOptions?: (field: string) => Promise<string[]>;
9
+ };
10
+ /**
11
+ * Load filter options for the given fields from a data source.
12
+ *
13
+ * Accepts `IDataSource<T>` or a plain `{ fetchFilterOptions }` object.
14
+ */
15
+ export declare function useFilterOptions(dataSource: FilterOptionsSource, fields: string[]): UseFilterOptionsResult;
16
+ export {};
@@ -0,0 +1,6 @@
1
+ export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnDefinition, UserLike, IFilters, IFetchParams, IPageResult, IDataSource, } from './types';
2
+ export { toUserLike, toDataGridFilterProps } from './types';
3
+ export { useFilterOptions } from './hooks';
4
+ export type { UseFilterOptionsResult } from './hooks';
5
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './utils';
6
+ export type { CsvColumn } from './utils';
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ export type ColumnFilterType = 'none' | 'text' | 'multiSelect' | 'people';
3
+ export interface IColumnFilterDef {
4
+ type: Exclude<ColumnFilterType, 'none'>;
5
+ filterField?: string;
6
+ optionsSource?: 'api' | 'static' | 'years';
7
+ options?: string[];
8
+ yearsCount?: number;
9
+ }
10
+ export interface IColumnMeta {
11
+ columnId: string;
12
+ name: string;
13
+ sortable?: boolean;
14
+ filterable?: false | IColumnFilterDef;
15
+ defaultVisible?: boolean;
16
+ required?: boolean;
17
+ minWidth?: number;
18
+ defaultWidth?: number;
19
+ idealWidth?: number;
20
+ }
21
+ export interface IColumnDef<T = unknown> extends IColumnMeta {
22
+ renderCell?: (item: T) => React.ReactNode;
23
+ compare?: (a: T, b: T) => number;
24
+ }
25
+ /** Minimal column info for the ColumnChooser (framework-agnostic). */
26
+ export interface IColumnDefinition {
27
+ columnId: string;
28
+ name: string;
29
+ required?: boolean;
30
+ }
@@ -0,0 +1,44 @@
1
+ export interface UserLike {
2
+ id?: string;
3
+ displayName: string;
4
+ email: string;
5
+ photo?: string;
6
+ }
7
+ export declare function toUserLike(u: {
8
+ displayName: string;
9
+ mail?: string;
10
+ userPrincipalName?: string;
11
+ email?: string;
12
+ id?: string;
13
+ photo?: string;
14
+ } | undefined): UserLike | undefined;
15
+ /** Unified filter values: text (string), multi-select (string[]), or people (UserLike). */
16
+ export interface IFilters {
17
+ [field: string]: string | string[] | UserLike | undefined;
18
+ }
19
+ /** Split IFilters into DataGridTable's multiSelect, text, and people props. */
20
+ export declare function toDataGridFilterProps(filters: IFilters): {
21
+ multiSelectFilters: Record<string, string[]>;
22
+ textFilters: Record<string, string>;
23
+ peopleFilters: Record<string, UserLike | undefined>;
24
+ };
25
+ export interface IFetchParams {
26
+ page: number;
27
+ pageSize: number;
28
+ sort?: {
29
+ field: string;
30
+ direction: 'asc' | 'desc';
31
+ };
32
+ filters: IFilters;
33
+ }
34
+ export interface IPageResult<T> {
35
+ items: T[];
36
+ totalCount: number;
37
+ }
38
+ /** Data source API: fetch a page and optionally filter options / people. */
39
+ export interface IDataSource<T> {
40
+ fetchPage(params: IFetchParams): Promise<IPageResult<T>>;
41
+ fetchFilterOptions?(field: string): Promise<string[]>;
42
+ searchPeople?(query: string): Promise<UserLike[]>;
43
+ getUserByEmail?(email: string): Promise<UserLike | undefined>;
44
+ }
@@ -0,0 +1,3 @@
1
+ export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnDefinition, } from './columnTypes';
2
+ export type { UserLike, IFilters, IFetchParams, IPageResult, IDataSource, } from './dataGridTypes';
3
+ export { toUserLike, toDataGridFilterProps } from './dataGridTypes';
@@ -0,0 +1,9 @@
1
+ export interface CsvColumn {
2
+ columnId: string;
3
+ name: string;
4
+ }
5
+ export declare function escapeCsvValue(value: unknown): string;
6
+ export declare function buildCsvHeader(columns: CsvColumn[]): string;
7
+ export declare function buildCsvRows<T>(items: T[], columns: CsvColumn[], getValue: (item: T, columnId: string) => string): string[];
8
+ export declare function exportToCsv<T>(items: T[], columns: CsvColumn[], getValue: (item: T, columnId: string) => string, filename?: string): void;
9
+ export declare function triggerCsvDownload(csvContent: string, filename: string): void;
@@ -0,0 +1,2 @@
1
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './exportToCsv';
2
+ export type { CsvColumn } from './exportToCsv';
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@alaarab/ogrid-core",
3
+ "version": "1.0.0",
4
+ "description": "OGrid core – framework-agnostic types, hooks, and utilities for OGrid data tables.",
5
+ "main": "dist/esm/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types/index.d.ts",
11
+ "import": "./dist/esm/index.js",
12
+ "require": "./dist/esm/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "rimraf dist && tsc -p tsconfig.build.json",
17
+ "test": "jest"
18
+ },
19
+ "keywords": ["ogrid", "datatable", "react", "typescript", "grid", "core"],
20
+ "author": "Ala Arab",
21
+ "license": "MIT",
22
+ "files": ["dist", "README.md", "LICENSE"],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "peerDependencies": {
27
+ "react": "^17.0.0 || ^18.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }