runeforge 0.0.17 → 0.0.19

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.
@@ -30,6 +30,8 @@
30
30
  initialSort = undefined as { column: string; direction: SortDirection } | undefined,
31
31
  initialFilters = undefined as Partial<FilterSnapshot> | undefined,
32
32
  onPaginationChange = undefined as ((query: TableQuery) => void) | undefined,
33
+ visibleRows = $bindable<T[]>([]),
34
+ query = $bindable<TableQuery | undefined>(undefined),
33
35
  }: {
34
36
  data?: T[];
35
37
  columns?: ColumnDefinition<T>[];
@@ -45,6 +47,12 @@
45
47
  initialSort?: { column: string; direction: SortDirection };
46
48
  initialFilters?: Partial<FilterSnapshot>;
47
49
  onPaginationChange?: (query: TableQuery) => void;
50
+ /** Filtered + sorted rows before page slicing (client mode), or the
51
+ * current page's rows as-is (server mode). Read-only for callers. */
52
+ visibleRows?: T[];
53
+ /** Current ordering + filters snapshot, kept in sync for callers that
54
+ * need to replicate the active query (e.g. exporting server-side). */
55
+ query?: TableQuery;
48
56
  } = $props();
49
57
 
50
58
  // Intentional one-time hydration of local state from the initial prop
@@ -107,6 +115,14 @@
107
115
  }
108
116
  });
109
117
 
118
+ // Surface the filtered+sorted rows and current query for callers (e.g. export).
119
+ $effect(() => {
120
+ visibleRows = sorted.map((e) => e.row);
121
+ });
122
+ $effect(() => {
123
+ query = currentQuery(displayPage);
124
+ });
125
+
110
126
  function currentQuery(page: number): TableQuery {
111
127
  return {
112
128
  page,
@@ -21,9 +21,15 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
21
21
  };
22
22
  initialFilters?: Partial<FilterSnapshot>;
23
23
  onPaginationChange?: (query: TableQuery) => void;
24
+ /** Filtered + sorted rows before page slicing (client mode), or the
25
+ * current page's rows as-is (server mode). Read-only for callers. */
26
+ visibleRows?: T[];
27
+ /** Current ordering + filters snapshot, kept in sync for callers that
28
+ * need to replicate the active query (e.g. exporting server-side). */
29
+ query?: TableQuery;
24
30
  };
25
31
  exports: {};
26
- bindings: "selected";
32
+ bindings: "selected" | "visibleRows" | "query";
27
33
  slots: {};
28
34
  events: {};
29
35
  };
@@ -31,7 +37,7 @@ declare class __sveltets_Render<T extends object = Record<string, unknown>> {
31
37
  props(): ReturnType<typeof $$render<T>>['props'];
32
38
  events(): ReturnType<typeof $$render<T>>['events'];
33
39
  slots(): ReturnType<typeof $$render<T>>['slots'];
34
- bindings(): "selected";
40
+ bindings(): "selected" | "visibleRows" | "query";
35
41
  exports(): {};
36
42
  }
37
43
  interface $$IsomorphicComponent {
@@ -0,0 +1,17 @@
1
+ import type { ColumnDefinition } from '../../types/crud.js';
2
+ /**
3
+ * Minimal duck-typed subset of the SheetJS (`xlsx`) module API used for
4
+ * building a workbook. Runeforge never imports `xlsx` itself — the consumer
5
+ * installs it and passes the resolved module in, so the dependency stays
6
+ * fully optional and never affects consumers who don't use Excel export.
7
+ */
8
+ export interface XlsxModule {
9
+ utils: {
10
+ aoa_to_sheet: (data: unknown[][]) => unknown;
11
+ book_new: () => unknown;
12
+ book_append_sheet: (workbook: unknown, worksheet: unknown, name?: string) => void;
13
+ };
14
+ writeFile: (workbook: unknown, filename: string) => void;
15
+ }
16
+ export declare function downloadCsv<T extends object>(rows: T[], columns: ColumnDefinition<T>[], filename: string): void;
17
+ export declare function downloadXlsx<T extends object>(rows: T[], columns: ColumnDefinition<T>[], filename: string, xlsx: XlsxModule): void;
@@ -0,0 +1,31 @@
1
+ import { cellRenderedText } from './utils.js';
2
+ function buildTable(rows, columns) {
3
+ const headers = columns.map((col) => col.title ?? col.attribute);
4
+ const body = rows.map((row) => columns.map((col) => cellRenderedText(row, col)));
5
+ return { headers, body };
6
+ }
7
+ function escapeCsvCell(value) {
8
+ return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
9
+ }
10
+ function triggerDownload(blob, filename) {
11
+ const url = URL.createObjectURL(blob);
12
+ const link = document.createElement('a');
13
+ link.href = url;
14
+ link.download = filename;
15
+ link.click();
16
+ URL.revokeObjectURL(url);
17
+ }
18
+ export function downloadCsv(rows, columns, filename) {
19
+ const { headers, body } = buildTable(rows, columns);
20
+ const csv = [headers, ...body]
21
+ .map((line) => line.map(escapeCsvCell).join(','))
22
+ .join('\r\n');
23
+ triggerDownload(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), `${filename}.csv`);
24
+ }
25
+ export function downloadXlsx(rows, columns, filename, xlsx) {
26
+ const { headers, body } = buildTable(rows, columns);
27
+ const worksheet = xlsx.utils.aoa_to_sheet([headers, ...body]);
28
+ const workbook = xlsx.utils.book_new();
29
+ xlsx.utils.book_append_sheet(workbook, worksheet);
30
+ xlsx.writeFile(workbook, `${filename}.xlsx`);
31
+ }
package/dist/i18n/en.js CHANGED
@@ -15,6 +15,10 @@ export const en = {
15
15
  edit: 'Edit',
16
16
  delete: 'Delete',
17
17
  create: 'Create',
18
+ searchPlaceholder: 'Search...',
19
+ export: 'Export',
20
+ exportCsv: 'Export as CSV',
21
+ exportExcel: 'Export as Excel',
18
22
  save: 'Save',
19
23
  saveAndContinue: 'Save and continue',
20
24
  cancel: 'Cancel',
@@ -22,5 +26,5 @@ export const en = {
22
26
  confirm: 'Confirm',
23
27
  deleteConfirm: (count, actionLabel) => `Are you sure you want to ${String(actionLabel).toLowerCase()} ${count} item${count === 1 ? '' : 's'}?`,
24
28
  required: (field) => `${field} is required`,
25
- serverError: 'Unexpected server error.',
29
+ serverError: 'Unexpected server error.'
26
30
  };
package/dist/i18n/es.js CHANGED
@@ -15,6 +15,10 @@ export const es = {
15
15
  edit: 'Editar',
16
16
  delete: 'Eliminar',
17
17
  create: 'Crear',
18
+ searchPlaceholder: 'Buscar...',
19
+ export: 'Exportar',
20
+ exportCsv: 'Exportar a CSV',
21
+ exportExcel: 'Exportar a Excel',
18
22
  save: 'Guardar',
19
23
  saveAndContinue: 'Guardar y continuar',
20
24
  cancel: 'Cancelar',
@@ -22,5 +26,5 @@ export const es = {
22
26
  confirm: 'Confirmar',
23
27
  deleteConfirm: (count, actionLabel) => `¿Seguro que querés ${String(actionLabel).toLowerCase()} ${count} elemento${count === 1 ? '' : 's'}?`,
24
28
  required: (field) => `${field} es requerido`,
25
- serverError: 'Error inesperado del servidor.',
29
+ serverError: 'Error inesperado del servidor.'
26
30
  };
@@ -15,6 +15,10 @@ export interface RuneforgeStrings {
15
15
  edit: string;
16
16
  delete: string;
17
17
  create: string;
18
+ searchPlaceholder: string;
19
+ export: string;
20
+ exportCsv: string;
21
+ exportExcel: string;
18
22
  save: string;
19
23
  saveAndContinue: string;
20
24
  cancel: string;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ let { size = '1em', class: cls = '' }: { size?: string | number; class?: string } = $props();
3
+ </script>
4
+ <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16" fill="currentColor" class={cls}>
5
+ <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"/>
6
+ <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z"/>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ size?: string | number;
3
+ class?: string;
4
+ };
5
+ declare const Download: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Download = ReturnType<typeof Download>;
7
+ export default Download;
@@ -10,7 +10,7 @@
10
10
  * setIconSet(bootstrapIconSet);
11
11
  */
12
12
  import * as Icons from 'svelte-bootstrap-icons';
13
- const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, X, } = Icons;
13
+ const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, X, Download, } = Icons;
14
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
15
  function asIcon(c) { return c; }
16
16
  export const bootstrapIconSet = {
@@ -28,6 +28,7 @@ export const bootstrapIconSet = {
28
28
  folder: asIcon(Folder),
29
29
  passwordShow: asIcon(Eye),
30
30
  passwordHide: asIcon(EyeSlash),
31
+ download: asIcon(Download),
31
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
33
  getByName: (name) => asIcon(Icons[name]) ?? null,
33
34
  };
@@ -12,6 +12,7 @@ import Home from '../defaults/Home.svelte';
12
12
  import Folder from '../defaults/Folder.svelte';
13
13
  import PasswordShow from '../defaults/PasswordShow.svelte';
14
14
  import PasswordHide from '../defaults/PasswordHide.svelte';
15
+ import Download from '../defaults/Download.svelte';
15
16
  export const defaultIconSet = {
16
17
  sortNone: SortNone,
17
18
  sortAsc: SortAsc,
@@ -27,4 +28,5 @@ export const defaultIconSet = {
27
28
  folder: Folder,
28
29
  passwordShow: PasswordShow,
29
30
  passwordHide: PasswordHide,
31
+ download: Download,
30
32
  };
@@ -18,5 +18,6 @@ export interface CRUDIconSet {
18
18
  folder: IconComponent;
19
19
  passwordShow: IconComponent;
20
20
  passwordHide: IconComponent;
21
+ download: IconComponent;
21
22
  getByName?: (name: string) => IconComponent | null;
22
23
  }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export type { BreadcrumbItem } from './types/breadcrumb.js';
2
2
  export { AttributeType } from './types/attribute.js';
3
3
  export type { AttributeMetadata, InterfaceMetadata, SelectOption, OptionsResolver, FormatterResolver } from './types/attribute.js';
4
4
  export type { CellProps, CellComponent, CellFormatter, SortDirection, IndexedRow, DistinctEntry, PaginatedEnvelope, ServerPagination, FilterSnapshot, TableQuery } from './types/table.js';
5
- export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, RowAction } from './types/crud.js';
5
+ export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, CustomBulkAction, RowAction, SearchConfiguration } from './types/crud.js';
6
6
  export type { RuneforgeConfig } from './config/context.js';
7
7
  export { setConfig, getConfig } from './config/context.js';
8
8
  export type { CRUDIconSet, IconComponent } from './icons/types.js';
@@ -39,6 +39,7 @@ export { default as CRUDList } from './components/crud/views/List.svelte';
39
39
  export { default as CRUDCreate } from './components/crud/views/Create.svelte';
40
40
  export { default as CRUDRead } from './components/crud/views/Read.svelte';
41
41
  export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
42
+ export { default as SearchInput } from './components/crud/SearchInput.svelte';
42
43
  export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
43
44
  export { resolveOptions, resolveFormatter, inferType } from './components/crud/utils/resolution.js';
44
45
  export { fieldLabel, initials } from './components/crud/utils/misc.js';
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export { default as CRUDList } from './components/crud/views/List.svelte';
36
36
  export { default as CRUDCreate } from './components/crud/views/Create.svelte';
37
37
  export { default as CRUDRead } from './components/crud/views/Read.svelte';
38
38
  export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
39
+ export { default as SearchInput } from './components/crud/SearchInput.svelte';
39
40
  // CRUD utilities
40
41
  export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
41
42
  export { resolveOptions, resolveFormatter, inferType } from './components/crud/utils/resolution.js';
@@ -47,3 +47,16 @@ export interface RowAction<T extends object = Record<string, unknown>> {
47
47
  condition?: (item: T) => boolean;
48
48
  run: (item: T) => void;
49
49
  }
50
+ export interface CustomBulkAction<T extends object = Record<string, unknown>> {
51
+ label: string;
52
+ icon: any;
53
+ endpoint: string;
54
+ variant?: string;
55
+ confirm?: boolean;
56
+ condition?: (items: T[]) => boolean;
57
+ }
58
+ export interface SearchConfiguration {
59
+ param?: string;
60
+ placeholder?: string;
61
+ debounceMs?: number;
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runeforge",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "SvelteKit toolkit for building metadata-driven CRUD interfaces with tables, forms, and actions",
5
5
  "license": "MIT",
6
6
  "author": "Ezequiel Puerta",