runeforge 0.0.52 → 0.0.53

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.
@@ -8,6 +8,7 @@
8
8
  import { AUTO_EXCLUDED } from './utils/constants.js';
9
9
  import {
10
10
  resolveFormatter,
11
+ truncateFormatter,
11
12
  inferType,
12
13
  buildFieldDefinitions
13
14
  } from './utils/resolution.js';
@@ -198,7 +199,7 @@
198
199
  // embedded column without an explicit formatter falls back to
199
200
  // joining each item's label (itemLabel, or the same
200
201
  // sub-field-joining summary the embedded form list uses).
201
- const formatter =
202
+ const resolvedFormatter =
202
203
  resolveFormatter(m, data) ??
203
204
  (embeddedFields
204
205
  ? (value: unknown) =>
@@ -212,6 +213,10 @@
212
213
  .join(', ')
213
214
  : ''
214
215
  : undefined);
216
+ const formatter =
217
+ m.truncateUpTo != null
218
+ ? truncateFormatter(resolvedFormatter, m.truncateUpTo)
219
+ : resolvedFormatter;
215
220
  return {
216
221
  attribute: k as keyof T & string,
217
222
  title: m.label ?? k,
@@ -2,5 +2,6 @@ import type { FieldDefinition } from '../../../types/crud.js';
2
2
  export declare function seedField<T extends object = Record<string, unknown>>(f: FieldDefinition<T>, raw: unknown): unknown;
3
3
  export declare function emptyRecord<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[]): Record<string, unknown>;
4
4
  export declare function seedRecord<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], source: Record<string, unknown>): Record<string, unknown>;
5
+ export declare function applyDuplicateOmit<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], record: Record<string, unknown>, omit?: string[]): Record<string, unknown>;
5
6
  export declare function formatEmbeddedFieldValue<T extends object = Record<string, unknown>>(f: FieldDefinition<T>, raw: unknown): string;
6
7
  export declare function defaultItemLabel<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], item: Record<string, unknown>): string;
@@ -39,6 +39,20 @@ export function seedRecord(fields, source) {
39
39
  }
40
40
  return seeded;
41
41
  }
42
+ // Shared by Update.svelte and Create.svelte's "Duplicate" handling: returns a
43
+ // copy of the just-saved record with `omit`-listed attributes reset to their
44
+ // field default, so the new draft doesn't inherit values that only made sense
45
+ // for the original instance (e.g. a publication date).
46
+ export function applyDuplicateOmit(fields, record, omit = []) {
47
+ if (!omit.length)
48
+ return record;
49
+ const result = { ...record };
50
+ for (const f of fields) {
51
+ if (omit.includes(f.attribute))
52
+ result[f.attribute] = seedField(f, f.default);
53
+ }
54
+ return result;
55
+ }
42
56
  // Shared by defaultItemLabel and the CSV/XLSX export column expansion: renders
43
57
  // one sub-field's raw stored value as display text, resolving a select's
44
58
  // option label instead of its stored value. Booleans are left to the caller
@@ -1,4 +1,3 @@
1
1
  export declare const formatBoolean: (trueLabel?: string, falseLabel?: string) => () => (value: boolean) => string;
2
2
  export declare const formatDatetime: (format?: string, timeZone?: string) => (() => (value: Date) => string);
3
- export declare function formatTruncateTextUpTo(maxLength: number): () => (value: string) => string;
4
3
  export declare function formatInstance<T extends Record<string, unknown>>(attribute: keyof T & string, instances: T[], urlPath: string, idKey?: keyof T & string): (value: unknown) => string;
@@ -52,12 +52,6 @@ export const formatDatetime = (format = 'dd/mm/YYYY HH:MM', timeZone) => {
52
52
  };
53
53
  return () => fmt;
54
54
  };
55
- export function formatTruncateTextUpTo(maxLength) {
56
- return () => (value) => {
57
- const str = String(value ?? '');
58
- return str.length > maxLength ? str.slice(0, maxLength) + '…' : str;
59
- };
60
- }
61
55
  function escapeHtml(str) {
62
56
  return str
63
57
  .replace(/&/g, '&amp;')
@@ -3,5 +3,6 @@ import type { FieldDefinition } from '../../../types/crud.js';
3
3
  export declare function resolveOptions(m: AttributeMetadata, d: unknown): SelectOption[] | undefined;
4
4
  export declare function resolveDefault(m: AttributeMetadata, d: unknown): unknown;
5
5
  export declare function resolveFormatter(m: AttributeMetadata, d: unknown): import("../../../index.ts").CellFormatter<any, any> | undefined;
6
+ export declare function truncateFormatter<T extends object = Record<string, unknown>, V = unknown>(formatter: ((value: V, row: T) => string) | undefined, maxLength: number): (value: V, row: T) => string;
6
7
  export declare function inferType(key: string, value: unknown): AttributeType;
7
8
  export declare function buildFieldDefinitions<T extends object = Record<string, unknown>>(meta: Partial<Record<string, AttributeMetadata>>, data: unknown, excludedFlag: 'excludedFromCreate' | 'excludedFromRead' | 'excludedFromUpdate' | 'excludedFromList', excluded: Set<string>): FieldDefinition<T>[];
@@ -9,6 +9,17 @@ export function resolveDefault(m, d) {
9
9
  export function resolveFormatter(m, d) {
10
10
  return m.formatter?.(d);
11
11
  }
12
+ // List column only (GenericCRUD's resolvedColumns): wraps `formatter` (or,
13
+ // absent one, the same raw String(value) TableBody falls back to) so a column
14
+ // that needs to stay narrow can truncate its cell without that truncation
15
+ // leaking into the Read view or the Create/Update forms, which call
16
+ // `formatter` directly and never go through this wrapper.
17
+ export function truncateFormatter(formatter, maxLength) {
18
+ return (value, row) => {
19
+ const str = formatter ? formatter(value, row) : String(value ?? '');
20
+ return str.length > maxLength ? str.slice(0, maxLength) + '…' : str;
21
+ };
22
+ }
12
23
  export function inferType(key, value) {
13
24
  if (typeof value === 'boolean')
14
25
  return 'boolean';
@@ -8,7 +8,7 @@
8
8
  import { defaultIconSet } from '../../../icons/sets/default.js';
9
9
  import { validateAll } from '../utils/validation.js';
10
10
  import { groupFields } from '../utils/grouping.js';
11
- import { emptyRecord, seedRecord } from '../utils/embedded.js';
11
+ import { applyDuplicateOmit, emptyRecord, seedRecord } from '../utils/embedded.js';
12
12
  import type { ActionConfiguration, FieldDefinition } from '../../../types/crud.js';
13
13
  import { getStrings } from '../../../i18n/context.js';
14
14
 
@@ -136,6 +136,7 @@
136
136
  fieldErrors = {};
137
137
  internalError = '';
138
138
  flashSuccess();
139
+ record = applyDuplicateOmit(fields, record, creation.duplication?.omit);
139
140
  } else {
140
141
  onSuccess?.();
141
142
  }
@@ -8,7 +8,7 @@
8
8
  import { defaultIconSet } from '../../../icons/sets/default.js';
9
9
  import { validateAll } from '../utils/validation.js';
10
10
  import { groupFields } from '../utils/grouping.js';
11
- import { seedRecord } from '../utils/embedded.js';
11
+ import { applyDuplicateOmit, seedRecord } from '../utils/embedded.js';
12
12
  import type { ActionConfiguration, FieldDefinition } from '../../../types/crud.js';
13
13
  import { getStrings } from '../../../i18n/context.js';
14
14
 
@@ -123,7 +123,7 @@
123
123
  onContinue?.();
124
124
  } else if (duplicating) {
125
125
  duplicating = false;
126
- onDuplicate?.(record);
126
+ onDuplicate?.(applyDuplicateOmit(fields, record, update.duplication?.omit));
127
127
  } else {
128
128
  onSuccess?.();
129
129
  }
package/dist/index.d.ts CHANGED
@@ -44,9 +44,9 @@ export { default as CRUDRead } from './components/crud/views/Read.svelte';
44
44
  export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
45
45
  export { default as SearchInput } from './components/crud/SearchInput.svelte';
46
46
  export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
47
- export { resolveOptions, resolveFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
47
+ export { resolveOptions, resolveFormatter, truncateFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
48
48
  export { fieldLabel, initials } from './components/crud/utils/misc.js';
49
- export { formatBoolean, formatDatetime, formatTruncateTextUpTo, formatInstance } from './components/crud/utils/formatters.js';
49
+ export { formatBoolean, formatDatetime, formatInstance } from './components/crud/utils/formatters.js';
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';
package/dist/index.js CHANGED
@@ -42,9 +42,9 @@ export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
42
42
  export { default as SearchInput } from './components/crud/SearchInput.svelte';
43
43
  // CRUD utilities
44
44
  export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
45
- export { resolveOptions, resolveFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
45
+ export { resolveOptions, resolveFormatter, truncateFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
46
46
  export { fieldLabel, initials } from './components/crud/utils/misc.js';
47
- export { formatBoolean, formatDatetime, formatTruncateTextUpTo, formatInstance } from './components/crud/utils/formatters.js';
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
50
  export { emptyRecord, defaultItemLabel } from './components/crud/utils/embedded.js';
@@ -51,7 +51,15 @@ export type AttributeMetadata = {
51
51
  hidden?: boolean | HiddenResolver;
52
52
  seed?: SeedResolver;
53
53
  component?: CellComponent<any, any>;
54
+ /** Formats the raw value everywhere it's displayed read-only: the list
55
+ * column's cell, the Read view's field, and any other readonly Field. */
54
56
  formatter?: FormatterResolver;
57
+ /** List column only: truncates the cell's text to this many characters,
58
+ * appending an ellipsis, applied to `formatter`'s output (or, absent one,
59
+ * the raw stringified value) — the Read view and Create/Update forms
60
+ * always show the untruncated value. Handy for a long free-text column
61
+ * that would otherwise blow out the table's width. */
62
+ truncateUpTo?: number;
55
63
  /** Pass a function to require a field only in certain conditions, e.g. a
56
64
  * quantity that only applies to some of a select's options. */
57
65
  required?: boolean | RequiredResolver;
@@ -65,6 +65,14 @@ export interface CreateFormButtonConfiguration {
65
65
  label?: string;
66
66
  class?: string;
67
67
  }
68
+ /** The "Duplicate" button's configuration — a `CreateFormButtonConfiguration`
69
+ * plus which attributes to leave out of the duplicated draft. */
70
+ export interface DuplicationButtonConfiguration extends CreateFormButtonConfiguration {
71
+ /** Attribute names reset to the field's `default` instead of copying the
72
+ * source instance's value — e.g. a publication date that should depend on
73
+ * when the copy is itself published, not on when the original was. */
74
+ omit?: string[];
75
+ }
68
76
  export interface ActionConfiguration<T extends object = Record<string, unknown>> {
69
77
  enabled?: boolean;
70
78
  label?: string;
@@ -90,7 +98,7 @@ export interface ActionConfiguration<T extends object = Record<string, unknown>>
90
98
  * create form pre-filled with the just-saved instance's values, so a new
91
99
  * record can be started from it without retyping everything.
92
100
  * Disabled by default — pass `{ enabled: true }` to show it. */
93
- duplication?: CreateFormButtonConfiguration;
101
+ duplication?: DuplicationButtonConfiguration;
94
102
  }
95
103
  export interface CustomAction<T extends object = Record<string, unknown>> {
96
104
  label: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runeforge",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
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",