runeforge 0.0.53 → 0.0.54

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.
Files changed (37) hide show
  1. package/README.md +128 -26
  2. package/dist/components/crud/GenericCRUD.svelte +12 -22
  3. package/dist/components/crud/GenericCRUD.svelte.d.ts +6 -9
  4. package/dist/components/crud/utils/reorder.d.ts +4 -0
  5. package/dist/components/crud/utils/reorder.js +13 -0
  6. package/dist/components/crud/views/list/List.svelte +291 -0
  7. package/dist/components/crud/views/{List.svelte.d.ts → list/List.svelte.d.ts} +4 -14
  8. package/dist/components/crud/views/list/Modals.svelte +56 -0
  9. package/dist/components/crud/views/list/Modals.svelte.d.ts +36 -0
  10. package/dist/components/crud/views/list/Table.svelte +176 -0
  11. package/dist/components/crud/views/list/Table.svelte.d.ts +59 -0
  12. package/dist/components/crud/views/list/Toolbar.svelte +341 -0
  13. package/dist/components/crud/views/list/Toolbar.svelte.d.ts +46 -0
  14. package/dist/components/table/PaginatedTable.svelte +361 -16
  15. package/dist/components/table/PaginatedTable.svelte.d.ts +11 -2
  16. package/dist/components/table/TableBody.svelte +83 -3
  17. package/dist/components/table/TableBody.svelte.d.ts +14 -1
  18. package/dist/components/table/TableHeader.svelte +9 -4
  19. package/dist/components/table/TableHeader.svelte.d.ts +1 -0
  20. package/dist/components/table/sortable.d.ts +27 -0
  21. package/dist/components/table/sortable.js +55 -0
  22. package/dist/components/table/utils.d.ts +19 -1
  23. package/dist/components/table/utils.js +40 -0
  24. package/dist/i18n/en.js +2 -0
  25. package/dist/i18n/es.js +2 -0
  26. package/dist/i18n/types.d.ts +2 -0
  27. package/dist/icons/defaults/Grip.svelte +7 -0
  28. package/dist/icons/defaults/Grip.svelte.d.ts +7 -0
  29. package/dist/icons/sets/bootstrap.js +2 -1
  30. package/dist/icons/sets/default.js +2 -0
  31. package/dist/icons/types.d.ts +3 -0
  32. package/dist/index.d.ts +5 -4
  33. package/dist/index.js +3 -2
  34. package/dist/types/crud.d.ts +70 -1
  35. package/dist/types/table.d.ts +56 -0
  36. package/package.json +3 -1
  37. package/dist/components/crud/views/List.svelte +0 -584
@@ -13,6 +13,7 @@ declare function $$render<T extends object>(): {
13
13
  distinctValues: Record<string, DistinctEntry<T>[]>;
14
14
  hasRowActions?: boolean;
15
15
  actionsLabel?: string;
16
+ reorderActive?: boolean;
16
17
  onchange?: () => void;
17
18
  };
18
19
  exports: {};
@@ -0,0 +1,27 @@
1
+ import type { Action } from 'svelte/action';
2
+ import type { SortableModule } from '../../types/table.js';
3
+ /** The indices SortableJS reports a drag settled at — `oldIndex`/`newIndex`
4
+ * for a single-row drag, `oldIndicies`/`newIndicies` (one entry per row)
5
+ * instead when `multiDrag` moved a whole selection together. */
6
+ export interface SortEndIndices {
7
+ oldIndex?: number;
8
+ newIndex?: number;
9
+ oldIndicies: number[];
10
+ newIndicies: number[];
11
+ }
12
+ export interface SortableRowsParams {
13
+ enabled: boolean;
14
+ /** The resolved `sortablejs` module — required whenever `enabled` is true.
15
+ * Runeforge never imports `sortablejs` itself; the caller supplies it. */
16
+ sortable?: SortableModule;
17
+ multiDrag?: boolean;
18
+ onStart?: () => void;
19
+ onEnd?: (indices: SortEndIndices) => void;
20
+ }
21
+ /** Mounts SortableJS on a `<tbody>` to drive drag-to-reorder, restricting
22
+ * drag initiation to elements carrying `data-reorder-handle`. The settled
23
+ * order is computed from SortableJS's own before/after indices (see
24
+ * TableBody) rather than read back from the DOM — `multiDrag` in particular
25
+ * doesn't reliably leave the DOM in the dropped state, only its event data
26
+ * reflects it correctly. */
27
+ export declare const sortableRows: Action<HTMLElement, SortableRowsParams>;
@@ -0,0 +1,55 @@
1
+ // A real Tailwind utility (not a hand-rolled class) so it's picked up by the
2
+ // consuming project's own Tailwind build — see the README's "Tailwind source
3
+ // scanning" note about `@source`-ing this package's `dist` output.
4
+ const MULTI_DRAG_SELECTED_CLASS = 'bg-primary/10';
5
+ /** Mounts SortableJS on a `<tbody>` to drive drag-to-reorder, restricting
6
+ * drag initiation to elements carrying `data-reorder-handle`. The settled
7
+ * order is computed from SortableJS's own before/after indices (see
8
+ * TableBody) rather than read back from the DOM — `multiDrag` in particular
9
+ * doesn't reliably leave the DOM in the dropped state, only its event data
10
+ * reflects it correctly. */
11
+ export const sortableRows = (node, params) => {
12
+ let instance;
13
+ function setup(p) {
14
+ instance?.destroy();
15
+ instance = undefined;
16
+ if (!p.enabled || !p.sortable)
17
+ return;
18
+ instance = p.sortable.create(node, {
19
+ handle: '[data-reorder-handle]',
20
+ animation: 150,
21
+ // Mouse/touch-simulated dragging instead of native HTML5 DnD: avoids
22
+ // native drag-ghost/Firefox quirks and (usefully) makes drags
23
+ // reproducible with plain mouse events in end-to-end tests. Not used
24
+ // together with `multiDrag`, though — SortableJS's MultiDrag plugin
25
+ // relies on native HTML5 DnD to track a multi-item drag correctly;
26
+ // paired with `forceFallback` it reports the drop as a no-op.
27
+ forceFallback: !p.multiDrag,
28
+ // Keeps the floating drag clone out of the `<tbody>` — otherwise it's
29
+ // an extra element carrying the same `data-row-key` as the row it was
30
+ // cloned from, which could confuse the multi-drag selection sync query.
31
+ fallbackOnBody: true,
32
+ multiDrag: p.multiDrag || undefined,
33
+ selectedClass: p.multiDrag ? MULTI_DRAG_SELECTED_CLASS : undefined,
34
+ onStart: () => p.onStart?.(),
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ onEnd: (evt) => {
37
+ p.onEnd?.({
38
+ oldIndex: evt.oldIndex,
39
+ newIndex: evt.newIndex,
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ oldIndicies: (evt.oldIndicies ?? []).map((e) => e.index),
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ newIndicies: (evt.newIndicies ?? []).map((e) => e.index),
44
+ });
45
+ },
46
+ });
47
+ }
48
+ setup(params);
49
+ return {
50
+ update: setup,
51
+ destroy() {
52
+ instance?.destroy();
53
+ },
54
+ };
55
+ };
@@ -1,7 +1,25 @@
1
1
  import type { ColumnDefinition } from '../../types/crud.js';
2
- import type { DistinctEntry } from '../../types/table.js';
2
+ import type { DistinctEntry, IndexedRow } from '../../types/table.js';
3
3
  export declare function cellRenderedText<T extends object>(row: T, col: ColumnDefinition<T>): string;
4
4
  export declare function isSortable<T extends object>(col: ColumnDefinition<T>): boolean;
5
5
  export declare function isFilterable<T extends object>(col: ColumnDefinition<T>): boolean;
6
6
  export declare function compare(a: unknown, b: unknown): number;
7
7
  export declare function distinctEntries<T extends object>(data: T[], columns: ColumnDefinition<T>[]): Record<string, DistinctEntry<T>[]>;
8
+ /** Returns a copy of `rows` with the item at `from` moved to `to` — pure
9
+ * array surgery, handy for building custom reorder UIs on `PaginatedTable`.
10
+ * Out-of-range indices are a no-op. */
11
+ export declare function moveIndexedRow<T extends object>(rows: IndexedRow<T>[], from: number, to: number): IndexedRow<T>[];
12
+ /** Generalizes `moveIndexedRow` to moving a *set* of items (SortableJS's
13
+ * `multiDrag`) to `toIndex` in one go, preserving their relative order.
14
+ * `toIndex` is a position in the *result* array, same convention as
15
+ * `moveIndexedRow`'s `to` (and SortableJS's own `newIndex`) — not an index
16
+ * into the pre-move array. Duplicate/out-of-range `fromIndices` are ignored;
17
+ * an empty valid set is a no-op. */
18
+ export declare function moveIndexedRows<T extends object>(rows: IndexedRow<T>[], fromIndices: number[], toIndex: number): IndexedRow<T>[];
19
+ /** Resolves the comparator establishing reorder mode's row order: `compare`
20
+ * when given (composite orders — a related record's order first, then this
21
+ * row's own `attribute`), otherwise plain ascending by `attribute`. */
22
+ export declare function resolveReorderComparator<T extends object>(reorder: {
23
+ attribute: keyof T & string;
24
+ compare?: (a: T, b: T) => number;
25
+ }): (a: T, b: T) => number;
@@ -42,3 +42,43 @@ export function distinctEntries(data, columns) {
42
42
  }
43
43
  return result;
44
44
  }
45
+ /** Returns a copy of `rows` with the item at `from` moved to `to` — pure
46
+ * array surgery, handy for building custom reorder UIs on `PaginatedTable`.
47
+ * Out-of-range indices are a no-op. */
48
+ export function moveIndexedRow(rows, from, to) {
49
+ if (from === to || from < 0 || to < 0 || from >= rows.length || to >= rows.length)
50
+ return rows;
51
+ const next = [...rows];
52
+ const [moved] = next.splice(from, 1);
53
+ next.splice(to, 0, moved);
54
+ return next;
55
+ }
56
+ /** Generalizes `moveIndexedRow` to moving a *set* of items (SortableJS's
57
+ * `multiDrag`) to `toIndex` in one go, preserving their relative order.
58
+ * `toIndex` is a position in the *result* array, same convention as
59
+ * `moveIndexedRow`'s `to` (and SortableJS's own `newIndex`) — not an index
60
+ * into the pre-move array. Duplicate/out-of-range `fromIndices` are ignored;
61
+ * an empty valid set is a no-op. */
62
+ export function moveIndexedRows(rows, fromIndices, toIndex) {
63
+ const validFrom = [...new Set(fromIndices)]
64
+ .filter((i) => i >= 0 && i < rows.length)
65
+ .sort((a, b) => a - b);
66
+ if (validFrom.length === 0)
67
+ return rows;
68
+ const fromSet = new Set(validFrom);
69
+ const moved = validFrom.map((i) => rows[i]);
70
+ const remaining = rows.filter((_, i) => !fromSet.has(i));
71
+ const insertAt = Math.min(remaining.length, Math.max(0, toIndex));
72
+ const next = [...remaining];
73
+ next.splice(insertAt, 0, ...moved);
74
+ return next;
75
+ }
76
+ /** Resolves the comparator establishing reorder mode's row order: `compare`
77
+ * when given (composite orders — a related record's order first, then this
78
+ * row's own `attribute`), otherwise plain ascending by `attribute`. */
79
+ export function resolveReorderComparator(reorder) {
80
+ if (reorder.compare)
81
+ return reorder.compare;
82
+ const { attribute } = reorder;
83
+ return (a, b) => compare(a[attribute], b[attribute]);
84
+ }
package/dist/i18n/en.js CHANGED
@@ -20,6 +20,8 @@ export const en = {
20
20
  delete: 'Delete',
21
21
  create: 'Create',
22
22
  searchPlaceholder: 'Search...',
23
+ reorder: 'Drag to reorder',
24
+ reorderPage: (page) => `Page ${page}`,
23
25
  export: 'Export',
24
26
  exportCsv: 'Export as CSV',
25
27
  exportExcel: 'Export as Excel',
package/dist/i18n/es.js CHANGED
@@ -20,6 +20,8 @@ export const es = {
20
20
  delete: 'Eliminar',
21
21
  create: 'Crear',
22
22
  searchPlaceholder: 'Buscar...',
23
+ reorder: 'Arrastrar para reordenar',
24
+ reorderPage: (page) => `Pág ${page}`,
23
25
  export: 'Exportar',
24
26
  exportCsv: 'Exportar a CSV',
25
27
  exportExcel: 'Exportar a Excel',
@@ -20,6 +20,8 @@ export interface RuneforgeStrings {
20
20
  delete: string;
21
21
  create: string;
22
22
  searchPlaceholder: string;
23
+ reorder: string;
24
+ reorderPage: (page: number) => string;
23
25
  export: string;
24
26
  exportCsv: string;
25
27
  exportExcel: string;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ let { size = '1em', class: cls = '' }: { size?: string | number; class?: string } = $props();
3
+ </script>
4
+
5
+ <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16" fill="currentColor" class={cls}>
6
+ <path d="M7 2a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0M7 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0M7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ size?: string | number;
3
+ class?: string;
4
+ };
5
+ declare const Grip: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Grip = ReturnType<typeof Grip>;
7
+ export default Grip;
@@ -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, Download, } = Icons;
13
+ const { ChevronExpand, CaretUpFill, CaretDownFill, Funnel, FunnelFill, Plus, Eye, PencilSquare, Trash3, HouseDoor, Folder, EyeSlash, X, Download, GripVertical, } = Icons;
14
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
15
  function asIcon(c) { return c; }
16
16
  export const bootstrapIconSet = {
@@ -29,6 +29,7 @@ export const bootstrapIconSet = {
29
29
  passwordShow: asIcon(Eye),
30
30
  passwordHide: asIcon(EyeSlash),
31
31
  download: asIcon(Download),
32
+ grip: asIcon(GripVertical),
32
33
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
34
  getByName: (name) => asIcon(Icons[name]) ?? null,
34
35
  };
@@ -13,6 +13,7 @@ import Folder from '../defaults/Folder.svelte';
13
13
  import PasswordShow from '../defaults/PasswordShow.svelte';
14
14
  import PasswordHide from '../defaults/PasswordHide.svelte';
15
15
  import Download from '../defaults/Download.svelte';
16
+ import Grip from '../defaults/Grip.svelte';
16
17
  export const defaultIconSet = {
17
18
  sortNone: SortNone,
18
19
  sortAsc: SortAsc,
@@ -29,4 +30,5 @@ export const defaultIconSet = {
29
30
  passwordShow: PasswordShow,
30
31
  passwordHide: PasswordHide,
31
32
  download: Download,
33
+ grip: Grip,
32
34
  };
@@ -19,5 +19,8 @@ export interface CRUDIconSet {
19
19
  passwordShow: IconComponent;
20
20
  passwordHide: IconComponent;
21
21
  download: IconComponent;
22
+ /** Drag handle shown at the start of each row when list reordering is
23
+ * enabled. Optional so existing custom icon sets keep compiling. */
24
+ grip?: IconComponent;
22
25
  getByName?: (name: string) => IconComponent | null;
23
26
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export type { BreadcrumbItem } from './types/breadcrumb.js';
2
2
  export { AttributeType } from './types/attribute.js';
3
3
  export type { AttributeMetadata, InterfaceMetadata, SelectOption, OptionsResolver, FormatterResolver, EmbeddedItemLabelResolver } from './types/attribute.js';
4
- export type { CellProps, CellComponent, CellFormatter, SortDirection, IndexedRow, DistinctEntry, PaginatedEnvelope, ServerPagination, FilterSnapshot, TableQuery } from './types/table.js';
5
- export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, BaseCustomBulkAction, ViewBasedCustomBulkAction, EndpointBasedCustomBulkAction, CustomBulkAction, RowAction, SearchConfiguration } from './types/crud.js';
4
+ export type { CellProps, CellComponent, CellFormatter, SortDirection, IndexedRow, DistinctEntry, PaginatedEnvelope, ServerPagination, FilterSnapshot, TableQuery, ReorderOptions } from './types/table.js';
5
+ export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, BaseCustomBulkAction, ViewBasedCustomBulkAction, EndpointBasedCustomBulkAction, CustomBulkAction, RowAction, SearchConfiguration, ExportConfiguration, ReorderConfiguration, ListActions, ListConfig } 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';
@@ -31,14 +31,14 @@ export { default as SortHeader } from './components/table/SortHeader.svelte';
31
31
  export { default as Paginator } from './components/table/Paginator.svelte';
32
32
  export { default as ColumnFilter } from './components/table/ColumnFilter.svelte';
33
33
  export { SortState, FilterState, snapshotFilter } from './components/table/state.svelte.js';
34
- export { cellRenderedText, isSortable, isFilterable, compare, distinctEntries } from './components/table/utils.js';
34
+ export { cellRenderedText, isSortable, isFilterable, compare, distinctEntries, moveIndexedRow } from './components/table/utils.js';
35
35
  export { default as GenericCRUD } from './components/crud/GenericCRUD.svelte';
36
36
  export { default as Field } from './components/crud/Field.svelte';
37
37
  export { default as EmbeddedField } from './components/crud/EmbeddedField.svelte';
38
38
  export { default as Header } from './components/common/Header.svelte';
39
39
  export { default as AvatarCell } from './components/crud/columns/Avatar.svelte';
40
40
  export { default as IconCell } from './components/crud/columns/Icon.svelte';
41
- export { default as CRUDList } from './components/crud/views/List.svelte';
41
+ export { default as CRUDList } from './components/crud/views/list/List.svelte';
42
42
  export { default as CRUDCreate } from './components/crud/views/Create.svelte';
43
43
  export { default as CRUDRead } from './components/crud/views/Read.svelte';
44
44
  export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
@@ -50,5 +50,6 @@ export { formatBoolean, formatDatetime, formatInstance } from './components/crud
50
50
  export { groupFields } from './components/crud/utils/grouping.js';
51
51
  export type { FieldGroup } from './components/crud/utils/grouping.js';
52
52
  export { validateAll } from './components/crud/utils/validation.js';
53
+ export { computeReorderChanges } from './components/crud/utils/reorder.js';
53
54
  export { emptyRecord, defaultItemLabel } from './components/crud/utils/embedded.js';
54
55
  export { buildChildrenByParent, collectDescendantIds } from './components/crud/utils/tree.js';
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ export { default as SortHeader } from './components/table/SortHeader.svelte';
27
27
  export { default as Paginator } from './components/table/Paginator.svelte';
28
28
  export { default as ColumnFilter } from './components/table/ColumnFilter.svelte';
29
29
  export { SortState, FilterState, snapshotFilter } from './components/table/state.svelte.js';
30
- export { cellRenderedText, isSortable, isFilterable, compare, distinctEntries } from './components/table/utils.js';
30
+ export { cellRenderedText, isSortable, isFilterable, compare, distinctEntries, moveIndexedRow } from './components/table/utils.js';
31
31
  // CRUD components
32
32
  export { default as GenericCRUD } from './components/crud/GenericCRUD.svelte';
33
33
  export { default as Field } from './components/crud/Field.svelte';
@@ -35,7 +35,7 @@ export { default as EmbeddedField } from './components/crud/EmbeddedField.svelte
35
35
  export { default as Header } from './components/common/Header.svelte';
36
36
  export { default as AvatarCell } from './components/crud/columns/Avatar.svelte';
37
37
  export { default as IconCell } from './components/crud/columns/Icon.svelte';
38
- export { default as CRUDList } from './components/crud/views/List.svelte';
38
+ export { default as CRUDList } from './components/crud/views/list/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';
@@ -47,5 +47,6 @@ export { fieldLabel, initials } from './components/crud/utils/misc.js';
47
47
  export { formatBoolean, formatDatetime, formatInstance } from './components/crud/utils/formatters.js';
48
48
  export { groupFields } from './components/crud/utils/grouping.js';
49
49
  export { validateAll } from './components/crud/utils/validation.js';
50
+ export { computeReorderChanges } from './components/crud/utils/reorder.js';
50
51
  export { emptyRecord, defaultItemLabel } from './components/crud/utils/embedded.js';
51
52
  export { buildChildrenByParent, collectDescendantIds } from './components/crud/utils/tree.js';
@@ -1,7 +1,8 @@
1
1
  import type { Component } from 'svelte';
2
2
  import type { FullAutoFill } from 'svelte/elements';
3
3
  import type { AttributeType, SearchResolver, RequiredResolver, SelectOption } from './attribute.js';
4
- import type { CellComponent, CellFormatter } from './table.js';
4
+ import type { CellComponent, CellFormatter, SortableModule, TableQuery } from './table.js';
5
+ import type { XlsxModule } from '../components/table/export.js';
5
6
  export type ColumnDefinition<T extends object = Record<string, unknown>> = {
6
7
  [K in keyof T & string]: {
7
8
  attribute: K;
@@ -182,3 +183,71 @@ export interface SearchConfiguration {
182
183
  placeholder?: string;
183
184
  debounceMs?: number;
184
185
  }
186
+ export interface ExportConfiguration<T extends object = Record<string, unknown>> {
187
+ callback?: (query: TableQuery) => Promise<T[]>;
188
+ xlsx?: XlsxModule;
189
+ }
190
+ /** Turns on drag-to-reorder rows in the list view — off by default, enabled
191
+ * by providing this object (`enabled: false` keeps the configuration in
192
+ * place but disables dragging, e.g. temporarily). Not supported alongside
193
+ * [server-side pagination](#server-side-pagination-sorting--filtering): the
194
+ * whole row set needs to be reachable client-side for indices to stay
195
+ * meaningful, so `reorder` is ignored whenever a `PaginatedEnvelope` is used. */
196
+ export interface ReorderConfiguration<T extends object = Record<string, unknown>> {
197
+ enabled?: boolean;
198
+ /** The attribute that stores each row's order index. Establishes the
199
+ * default ascending order (when `compare` is absent) and is written to
200
+ * its new sequential 0-based value on every row whose position changes
201
+ * after a drag. */
202
+ attribute: keyof T & string;
203
+ /** Resolved `sortablejs` default export, e.g. `import Sortable from
204
+ * 'sortablejs'`. Runeforge never bundles `sortablejs` itself — install it
205
+ * separately and pass it in, so the dependency stays fully optional. */
206
+ sortable: SortableModule;
207
+ /** Overrides the plain `attribute`-ascending order for composite orders
208
+ * — e.g. a row's true resting position depends first on a related
209
+ * record's own order, and only then on this row's `attribute`. Takes
210
+ * full ownership of row order while reorder is active (column-header
211
+ * sorting is unavailable, same as the plain `attribute` case). */
212
+ compare?: (a: T, b: T) => number;
213
+ /** POSTed once per drag that settles with at least one changed row — a
214
+ * single request, not one per row — then the list is refreshed. FormData
215
+ * field `changes` carries a JSON-encoded array of `{ id, value }` pairs,
216
+ * one per row whose `attribute` changed (`value` being its new
217
+ * sequential position). Provide this or `callback`. */
218
+ endpoint?: string;
219
+ /** Alternative to `endpoint`: receives the rows whose `attribute` value
220
+ * changed, already updated to their new index. */
221
+ callback?: (items: T[]) => void | Promise<void>;
222
+ /** Drag-handle icon shown at the start of each row. Falls back to the
223
+ * active icon set's `grip` icon. */
224
+ icon?: any;
225
+ /** Lets dragging one row of the current checkbox selection move the
226
+ * whole selection together, via SortableJS's `MultiDrag` plugin (must be
227
+ * mounted on the `sortable` module you pass in). Off by default. */
228
+ multiDrag?: boolean;
229
+ /** While dragging, hovering over the edge zone for this long flips a
230
+ * page instead of requiring one long drag across the whole list.
231
+ * Default `2000`. */
232
+ pageFlipThresholdMs?: number;
233
+ }
234
+ /** Groups the list view's row-level and selection-level custom actions —
235
+ * `custom` replaces the old top-level `actions` prop, `bulk` replaces
236
+ * `customBulkActions`. */
237
+ export interface ListActions<T extends object = Record<string, unknown>> {
238
+ /** Extra per-row actions — see Custom row actions. */
239
+ custom?: CustomAction<T>[];
240
+ /** Extra actions on the current selection — see Custom bulk actions. */
241
+ bulk?: CustomBulkAction<T>[];
242
+ }
243
+ /** Groups the list view's opt-in behaviors. Replaces the old top-level
244
+ * `search`, `enableExport`/`onExport`/`xlsx` props. */
245
+ export interface ListConfig<T extends object = Record<string, unknown>> {
246
+ /** Free-text search box — see Free-text search. */
247
+ search?: SearchConfiguration;
248
+ /** CSV/Excel export. Presence of this object enables the export button —
249
+ * even as an empty `{}` — replacing the old `enableExport` boolean. */
250
+ export?: ExportConfiguration<T>;
251
+ /** Drag-to-reorder rows — see Reordering rows. */
252
+ reorder?: ReorderConfiguration<T>;
253
+ }
@@ -45,3 +45,59 @@ export interface TableQuery {
45
45
  ordering: string | null;
46
46
  filters: FilterSnapshot;
47
47
  }
48
+ /** An instance created by `SortableModule.create()` — the only bit of it
49
+ * runeforge ever touches directly. */
50
+ export interface SortableInstance {
51
+ destroy(): void;
52
+ }
53
+ /** Programmatic selection API SortableJS's `MultiDrag` plugin exposes as
54
+ * `Sortable.utils` — used to mirror this table's own row-selection
55
+ * (checkboxes) into the plugin's selection state, so `multiDrag` drags
56
+ * whatever's currently checked instead of relying on its own click/modifier
57
+ * based selection UX. */
58
+ export interface SortableUtils {
59
+ select(element: HTMLElement): void;
60
+ deselect(element: HTMLElement): void;
61
+ }
62
+ /** Minimal duck-typed subset of the `sortablejs` module API used to drive
63
+ * drag-to-reorder. Runeforge never imports `sortablejs` itself — install it
64
+ * separately and pass the resolved default export in via `reorder.sortable`
65
+ * (or `ReorderConfiguration.sortable` on `GenericCRUD`), so the dependency
66
+ * stays fully optional, mirroring `xlsx` for export. Pass `MultiDrag`
67
+ * pre-mounted (`Sortable.mount(new MultiDrag())`) on the module you hand in
68
+ * if you're using `multiDrag`. */
69
+ export interface SortableModule {
70
+ create(element: HTMLElement, options?: Record<string, any>): SortableInstance;
71
+ utils?: SortableUtils;
72
+ }
73
+ /** Enables the drag-to-reorder row layer on `PaginatedTable`/`TableBody`.
74
+ * Presence of this object is what turns dragging on — per-column filtering
75
+ * is suppressed while it's active (dragging needs the full, unfiltered row
76
+ * set reachable), and row order is fully owned by `compare`/`attribute`
77
+ * rather than column-header sorting. Pagination stays on; crossing a page
78
+ * boundary mid-drag is done via the edge hover zones (see
79
+ * `pageFlipThresholdMs`), not by disabling pagination. */
80
+ export interface ReorderOptions<T extends object = Record<string, unknown>> {
81
+ /** The attribute establishing the default ascending order, used when
82
+ * `compare` is not provided. */
83
+ attribute: keyof T & string;
84
+ /** Resolved `sortablejs` module — see the interface doc above. */
85
+ sortable: SortableModule;
86
+ /** Overrides the plain `attribute`-ascending order for cases where the
87
+ * true resting order depends on more than the row's own attribute (e.g.
88
+ * a parent's order first, then this row's own order within it). Takes
89
+ * full ownership of row order while reorder is active. */
90
+ compare?: (a: T, b: T) => number;
91
+ /** Drag-handle icon shown at the start of each row. Falls back to the
92
+ * active icon set's `grip` icon. */
93
+ icon?: any;
94
+ /** Lets dragging one row of the current checkbox selection move the
95
+ * whole selection together, via SortableJS's `MultiDrag` plugin (must be
96
+ * mounted on the `sortable` module you pass in). Off by default. */
97
+ multiDrag?: boolean;
98
+ /** While dragging, hovering the pointer over the narrow zone at either
99
+ * edge of the table for this long flips to the previous/next page —
100
+ * lets a row travel across pages without an unwieldy long drag. Repeated
101
+ * hovering (without leaving the zone) keeps flipping. Default `2000`. */
102
+ pageFlipThresholdMs?: number;
103
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runeforge",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
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",
@@ -49,6 +49,7 @@
49
49
  "@sveltejs/vite-plugin-svelte": "^7.1.2",
50
50
  "@tailwindcss/vite": "^4.3.1",
51
51
  "@types/node": "^22",
52
+ "@types/sortablejs": "^1.15.9",
52
53
  "daisyui": "^5.5.23",
53
54
  "eslint": "^10.4.1",
54
55
  "eslint-config-prettier": "^10.1.8",
@@ -57,6 +58,7 @@
57
58
  "prettier": "^3.8.3",
58
59
  "prettier-plugin-svelte": "^4.1.0",
59
60
  "publint": "^0.3.21",
61
+ "sortablejs": "^1.15.7",
60
62
  "svelte": "^5.56.1",
61
63
  "svelte-bootstrap-icons": "^3.3.0",
62
64
  "svelte-check": "^4.6.0",