runeforge 0.0.53 → 0.0.55

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 (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1342 -1222
  3. package/dist/components/Avatar.svelte +31 -31
  4. package/dist/components/IconRenderer.svelte +22 -22
  5. package/dist/components/Modal.svelte +75 -75
  6. package/dist/components/common/Header.svelte +40 -40
  7. package/dist/components/crud/EmbeddedField.svelte +175 -175
  8. package/dist/components/crud/Field.svelte +376 -376
  9. package/dist/components/crud/GenericCRUD.svelte +426 -435
  10. package/dist/components/crud/GenericCRUD.svelte.d.ts +6 -9
  11. package/dist/components/crud/SearchInput.svelte +53 -53
  12. package/dist/components/crud/columns/Avatar.svelte +15 -15
  13. package/dist/components/crud/columns/Icon.svelte +8 -8
  14. package/dist/components/crud/utils/reorder.d.ts +4 -0
  15. package/dist/components/crud/utils/reorder.js +13 -0
  16. package/dist/components/crud/views/Create.svelte +232 -232
  17. package/dist/components/crud/views/Read.svelte +124 -124
  18. package/dist/components/crud/views/Update.svelte +208 -208
  19. package/dist/components/crud/views/list/List.svelte +291 -0
  20. package/dist/components/crud/views/{List.svelte.d.ts → list/List.svelte.d.ts} +4 -14
  21. package/dist/components/crud/views/list/Modals.svelte +56 -0
  22. package/dist/components/crud/views/list/Modals.svelte.d.ts +36 -0
  23. package/dist/components/crud/views/list/Table.svelte +176 -0
  24. package/dist/components/crud/views/list/Table.svelte.d.ts +59 -0
  25. package/dist/components/crud/views/list/Toolbar.svelte +341 -0
  26. package/dist/components/crud/views/list/Toolbar.svelte.d.ts +46 -0
  27. package/dist/components/form/Button.svelte +27 -27
  28. package/dist/components/form/Label.svelte +37 -37
  29. package/dist/components/form/MultiSelect.svelte +248 -248
  30. package/dist/components/form/PasswordInput.svelte +68 -68
  31. package/dist/components/form/Required.svelte +1 -1
  32. package/dist/components/form/Select.svelte +209 -209
  33. package/dist/components/form/Tree.svelte +62 -62
  34. package/dist/components/form/TreeNode.svelte +66 -66
  35. package/dist/components/navigation/Breadcrumbs.svelte +111 -111
  36. package/dist/components/table/ColumnFilter.svelte +168 -168
  37. package/dist/components/table/PaginatedTable.svelte +536 -215
  38. package/dist/components/table/PaginatedTable.svelte.d.ts +11 -2
  39. package/dist/components/table/Paginator.svelte +113 -113
  40. package/dist/components/table/SortHeader.svelte +43 -43
  41. package/dist/components/table/TableBody.svelte +150 -70
  42. package/dist/components/table/TableBody.svelte.d.ts +14 -1
  43. package/dist/components/table/TableHeader.svelte +88 -83
  44. package/dist/components/table/TableHeader.svelte.d.ts +1 -0
  45. package/dist/components/table/sortable.d.ts +27 -0
  46. package/dist/components/table/sortable.js +55 -0
  47. package/dist/components/table/utils.d.ts +28 -1
  48. package/dist/components/table/utils.js +75 -0
  49. package/dist/i18n/en.js +2 -0
  50. package/dist/i18n/es.js +2 -0
  51. package/dist/i18n/types.d.ts +2 -0
  52. package/dist/icons/defaults/Clear.svelte +6 -6
  53. package/dist/icons/defaults/Create.svelte +6 -6
  54. package/dist/icons/defaults/Delete.svelte +6 -6
  55. package/dist/icons/defaults/Download.svelte +7 -7
  56. package/dist/icons/defaults/Edit.svelte +7 -7
  57. package/dist/icons/defaults/Filter.svelte +6 -6
  58. package/dist/icons/defaults/FilterActive.svelte +6 -6
  59. package/dist/icons/defaults/Folder.svelte +6 -6
  60. package/dist/icons/defaults/Grip.svelte +7 -0
  61. package/dist/icons/defaults/Grip.svelte.d.ts +7 -0
  62. package/dist/icons/defaults/Home.svelte +6 -6
  63. package/dist/icons/defaults/PasswordHide.svelte +9 -9
  64. package/dist/icons/defaults/PasswordShow.svelte +7 -7
  65. package/dist/icons/defaults/SortAsc.svelte +6 -6
  66. package/dist/icons/defaults/SortDesc.svelte +6 -6
  67. package/dist/icons/defaults/SortNone.svelte +6 -6
  68. package/dist/icons/defaults/View.svelte +7 -7
  69. package/dist/icons/sets/bootstrap.js +2 -1
  70. package/dist/icons/sets/default.js +2 -0
  71. package/dist/icons/types.d.ts +3 -0
  72. package/dist/index.d.ts +5 -4
  73. package/dist/index.js +3 -2
  74. package/dist/types/attribute.d.ts +9 -0
  75. package/dist/types/crud.d.ts +72 -1
  76. package/dist/types/table.d.ts +56 -0
  77. package/package.json +3 -1
  78. package/dist/components/crud/views/List.svelte +0 -584
@@ -1,6 +1,6 @@
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="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
6
- </svg>
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="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
6
+ </svg>
@@ -1,6 +1,6 @@
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="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708"/>
6
- </svg>
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="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708"/>
6
+ </svg>
@@ -1,7 +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="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"/>
6
- <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0"/>
7
- </svg>
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="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"/>
6
+ <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0"/>
7
+ </svg>
@@ -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';
@@ -75,6 +75,15 @@ export type AttributeMetadata = {
75
75
  excludedFromRead?: boolean;
76
76
  sortable?: boolean;
77
77
  filterable?: boolean;
78
+ /** List column filter only: a static, exhaustive set of choices shown in
79
+ * the column filter's checkbox list, instead of the values sampled off
80
+ * currently loaded rows. For a bounded, known set (e.g. an enum-like text
81
+ * column) that the loaded page might not fully represent — especially
82
+ * under [server-side pagination](#server-side-pagination-sorting--filtering),
83
+ * where the sample is only a handful of values off the current page. `value`
84
+ * is matched against the rendered cell text (and sent server-side as-is);
85
+ * `label` is only what's displayed, falling back to `value`. */
86
+ filterOptions?: SelectOption[];
78
87
  groupedAs?: string;
79
88
  min?: number;
80
89
  max?: number;
@@ -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;
@@ -11,6 +12,8 @@ export type ColumnDefinition<T extends object = Record<string, unknown>> = {
11
12
  formatter?: CellFormatter<T, T[K]>;
12
13
  sortable?: boolean;
13
14
  filterable?: boolean;
15
+ /** Static, exhaustive filter choices — see `AttributeMetadata.filterOptions`. */
16
+ filterOptions?: SelectOption[];
14
17
  /** Embedded columns only: sub-field definitions for each item, used to
15
18
  * render a default cell summary and to expand the column into one
16
19
  * sub-column per field on CSV/XLSX export. */
@@ -182,3 +185,71 @@ export interface SearchConfiguration {
182
185
  placeholder?: string;
183
186
  debounceMs?: number;
184
187
  }
188
+ export interface ExportConfiguration<T extends object = Record<string, unknown>> {
189
+ callback?: (query: TableQuery) => Promise<T[]>;
190
+ xlsx?: XlsxModule;
191
+ }
192
+ /** Turns on drag-to-reorder rows in the list view — off by default, enabled
193
+ * by providing this object (`enabled: false` keeps the configuration in
194
+ * place but disables dragging, e.g. temporarily). Not supported alongside
195
+ * [server-side pagination](#server-side-pagination-sorting--filtering): the
196
+ * whole row set needs to be reachable client-side for indices to stay
197
+ * meaningful, so `reorder` is ignored whenever a `PaginatedEnvelope` is used. */
198
+ export interface ReorderConfiguration<T extends object = Record<string, unknown>> {
199
+ enabled?: boolean;
200
+ /** The attribute that stores each row's order index. Establishes the
201
+ * default ascending order (when `compare` is absent) and is written to
202
+ * its new sequential 0-based value on every row whose position changes
203
+ * after a drag. */
204
+ attribute: keyof T & string;
205
+ /** Resolved `sortablejs` default export, e.g. `import Sortable from
206
+ * 'sortablejs'`. Runeforge never bundles `sortablejs` itself — install it
207
+ * separately and pass it in, so the dependency stays fully optional. */
208
+ sortable: SortableModule;
209
+ /** Overrides the plain `attribute`-ascending order for composite orders
210
+ * — e.g. a row's true resting position depends first on a related
211
+ * record's own order, and only then on this row's `attribute`. Takes
212
+ * full ownership of row order while reorder is active (column-header
213
+ * sorting is unavailable, same as the plain `attribute` case). */
214
+ compare?: (a: T, b: T) => number;
215
+ /** POSTed once per drag that settles with at least one changed row — a
216
+ * single request, not one per row — then the list is refreshed. FormData
217
+ * field `changes` carries a JSON-encoded array of `{ id, value }` pairs,
218
+ * one per row whose `attribute` changed (`value` being its new
219
+ * sequential position). Provide this or `callback`. */
220
+ endpoint?: string;
221
+ /** Alternative to `endpoint`: receives the rows whose `attribute` value
222
+ * changed, already updated to their new index. */
223
+ callback?: (items: T[]) => void | Promise<void>;
224
+ /** Drag-handle icon shown at the start of each row. Falls back to the
225
+ * active icon set's `grip` icon. */
226
+ icon?: any;
227
+ /** Lets dragging one row of the current checkbox selection move the
228
+ * whole selection together, via SortableJS's `MultiDrag` plugin (must be
229
+ * mounted on the `sortable` module you pass in). Off by default. */
230
+ multiDrag?: boolean;
231
+ /** While dragging, hovering over the edge zone for this long flips a
232
+ * page instead of requiring one long drag across the whole list.
233
+ * Default `2000`. */
234
+ pageFlipThresholdMs?: number;
235
+ }
236
+ /** Groups the list view's row-level and selection-level custom actions —
237
+ * `custom` replaces the old top-level `actions` prop, `bulk` replaces
238
+ * `customBulkActions`. */
239
+ export interface ListActions<T extends object = Record<string, unknown>> {
240
+ /** Extra per-row actions — see Custom row actions. */
241
+ custom?: CustomAction<T>[];
242
+ /** Extra actions on the current selection — see Custom bulk actions. */
243
+ bulk?: CustomBulkAction<T>[];
244
+ }
245
+ /** Groups the list view's opt-in behaviors. Replaces the old top-level
246
+ * `search`, `enableExport`/`onExport`/`xlsx` props. */
247
+ export interface ListConfig<T extends object = Record<string, unknown>> {
248
+ /** Free-text search box — see Free-text search. */
249
+ search?: SearchConfiguration;
250
+ /** CSV/Excel export. Presence of this object enables the export button —
251
+ * even as an empty `{}` — replacing the old `enableExport` boolean. */
252
+ export?: ExportConfiguration<T>;
253
+ /** Drag-to-reorder rows — see Reordering rows. */
254
+ reorder?: ReorderConfiguration<T>;
255
+ }
@@ -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.55",
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",