@anzusystems/common-admin 1.47.0-beta.dev-1778754008 → 1.47.0-beta.dev-1782072279

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/dist/labs.d.ts CHANGED
@@ -6,6 +6,7 @@ import { ComponentProvideOptions } from 'vue';
6
6
  import { ComputedRef } from 'vue';
7
7
  import { DefineComponent } from 'vue';
8
8
  import { InjectionKey } from 'vue';
9
+ import { MaybeRefOrGetter } from 'vue';
9
10
  import { Method } from 'axios';
10
11
  import { PublicProps } from 'vue';
11
12
  import { Ref } from 'vue';
@@ -278,35 +279,17 @@ declare const __VLS_export_16: <TItem>(__VLS_props: NonNullable<Awaited<typeof _
278
279
  declare const __VLS_export_17: <TItem extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal_4<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
279
280
  props: PublicProps & __VLS_PrettifyLocal_4<(Props<TItem> & {
280
281
  modelValue: TItem[];
281
- unsavedKeys?: Set<ListEditorKey>;
282
282
  }) & {
283
283
  onDeleted?: ((item: ListViewItem<TItem>) => any) | undefined;
284
284
  onClose?: ((item: ListViewItem<TItem>) => any) | undefined;
285
285
  onEdit?: ((item: ListViewItem<TItem>) => any) | undefined;
286
- onAdd?: ((positionHint: PositionHint | undefined) => any) | undefined;
287
286
  "onUpdate:modelValue"?: ((value: TItem[]) => any) | undefined;
288
- onAdded?: ((payload: {
289
- item: TItem;
290
- index: number;
291
- }) => any) | undefined;
292
287
  "onItem-saved"?: ((item: ListViewItem<TItem>) => any) | undefined;
293
288
  "onItem-expand"?: ((item: ListViewItem<TItem>, expanded: boolean) => any) | undefined;
294
- "onUpdate:unsavedKeys"?: ((value: Set<ListEditorKey>) => any) | undefined;
295
289
  }> & (typeof globalThis extends {
296
290
  __VLS_PROPS_FALLBACK: infer P;
297
291
  } ? P : {});
298
- expose: (exposed: ShallowUnwrapRef<{
299
- addItem: (data: TItem, positionHint?: PositionHint) => TItem[];
300
- deleteItem: (idOrItem: ListEditorKey | TItem) => TItem[];
301
- updateItem: (idOrItem: ListEditorKey | TItem, data: TItem) => TItem[];
302
- moveItem: (fromIndex: number, toIndex: number) => TItem[];
303
- recalculatePositions: (items: TItem[]) => TItem[];
304
- viewItems: ComputedRef<ListViewItem<TItem>[]>;
305
- resetDirtyBaseline: () => void;
306
- hasUnsavedChanges: ComputedRef<boolean>;
307
- unsavedCount: ComputedRef<number>;
308
- clearUnsavedState: (key?: ListEditorKey) => void;
309
- }>) => void;
292
+ expose: (exposed: ShallowUnwrapRef<ListEditorHandle<TItem>>) => void;
310
293
  attrs: any;
311
294
  slots: {
312
295
  header?: (props: HeaderSlotProps) => unknown;
@@ -321,10 +304,7 @@ declare const __VLS_export_17: <TItem extends Record<string, any>>(__VLS_props:
321
304
  'before-item'?: (props: RowSlotProps<TItem>) => unknown;
322
305
  'after-item'?: (props: RowSlotProps<TItem>) => unknown;
323
306
  };
324
- emit: (((evt: "deleted", item: ListViewItem<TItem>) => void) & ((evt: "close", item: ListViewItem<TItem>) => void) & ((evt: "edit", item: ListViewItem<TItem>) => void) & ((evt: "add", positionHint: PositionHint | undefined) => void) & ((evt: "added", payload: {
325
- item: TItem;
326
- index: number;
327
- }) => void) & ((evt: "item-saved", item: ListViewItem<TItem>) => void) & ((evt: "item-expand", item: ListViewItem<TItem>, expanded: boolean) => void)) & (((event: "update:modelValue", value: TItem[]) => void) & ((event: "update:unsavedKeys", value: Set<ListEditorKey>) => void));
307
+ emit: (((evt: "deleted", item: ListViewItem<TItem>) => void) & ((evt: "close", item: ListViewItem<TItem>) => void) & ((evt: "edit", item: ListViewItem<TItem>) => void) & ((evt: "item-saved", item: ListViewItem<TItem>) => void) & ((evt: "item-expand", item: ListViewItem<TItem>, expanded: boolean) => void)) & ((event: "update:modelValue", value: TItem[]) => void);
328
308
  }>) => VNode & {
329
309
  __ctx?: Awaited<typeof __VLS_setup>;
330
310
  };
@@ -333,18 +313,12 @@ declare const __VLS_export_18: <TItem extends Record<string, any>>(__VLS_props:
333
313
  props: PublicProps & __VLS_PrettifyLocal_5<(Props_2<TItem> & {
334
314
  modelValue: TItem[];
335
315
  mode?: ReorderMode;
336
- unsavedKeys?: Set<ListEditorKey>;
337
316
  }) & {
338
317
  onDeleted?: ((item: ListViewItem<TItem>) => any) | undefined;
339
318
  onClose?: ((item: ListViewItem<TItem>) => any) | undefined;
340
319
  onEdit?: ((item: ListViewItem<TItem>) => any) | undefined;
341
- onAdd?: ((positionHint: PositionHint | undefined) => any) | undefined;
342
320
  "onUpdate:modelValue"?: ((value: TItem[]) => any) | undefined;
343
321
  "onUpdate:mode"?: ((value: ReorderMode) => any) | undefined;
344
- onAdded?: ((payload: {
345
- item: TItem;
346
- index: number;
347
- }) => any) | undefined;
348
322
  "onItem-saved"?: ((item: ListViewItem<TItem>) => any) | undefined;
349
323
  "onItem-expand"?: ((item: ListViewItem<TItem>, expanded: boolean) => any) | undefined;
350
324
  "onReorder-start"?: (() => any) | undefined;
@@ -352,21 +326,10 @@ declare const __VLS_export_18: <TItem extends Record<string, any>>(__VLS_props:
352
326
  "onReorder-applied"?: ((items: TItem[]) => any) | undefined;
353
327
  "onReorder-apply-error"?: ((error: unknown) => any) | undefined;
354
328
  "onReorder-end"?: (() => any) | undefined;
355
- "onUpdate:unsavedKeys"?: ((value: Set<ListEditorKey>) => any) | undefined;
356
329
  }> & (typeof globalThis extends {
357
330
  __VLS_PROPS_FALLBACK: infer P;
358
331
  } ? P : {});
359
- expose: (exposed: ShallowUnwrapRef<{
360
- addItem: (data: TItem, positionHint?: PositionHint) => TItem[];
361
- deleteItem: (idOrItem: ListEditorKey | TItem) => TItem[];
362
- updateItem: (idOrItem: ListEditorKey | TItem, data: TItem) => TItem[];
363
- moveItem: (fromIndex: number, toIndex: number) => TItem[];
364
- recalculatePositions: (items: TItem[]) => TItem[];
365
- viewItems: ComputedRef<ListViewItem<TItem>[]>;
366
- resetDirtyBaseline: () => void;
367
- hasUnsavedChanges: ComputedRef<boolean>;
368
- unsavedCount: ComputedRef<number>;
369
- clearUnsavedState: (key?: ListEditorKey) => void;
332
+ expose: (exposed: ShallowUnwrapRef<ListEditorHandle<TItem> & {
370
333
  enterReorderMode: () => void;
371
334
  cancelReorderMode: () => void;
372
335
  applyReorder: () => Promise<void>;
@@ -388,10 +351,7 @@ declare const __VLS_export_18: <TItem extends Record<string, any>>(__VLS_props:
388
351
  'before-item'?: (props: RowSlotProps_2<TItem>) => unknown;
389
352
  'after-item'?: (props: RowSlotProps_2<TItem>) => unknown;
390
353
  };
391
- emit: (((evt: "deleted", item: ListViewItem<TItem>) => void) & ((evt: "close", item: ListViewItem<TItem>) => void) & ((evt: "edit", item: ListViewItem<TItem>) => void) & ((evt: "add", positionHint: PositionHint | undefined) => void) & ((evt: "added", payload: {
392
- item: TItem;
393
- index: number;
394
- }) => void) & ((evt: "item-saved", item: ListViewItem<TItem>) => void) & ((evt: "item-expand", item: ListViewItem<TItem>, expanded: boolean) => void) & ((evt: "reorder-start") => void) & ((evt: "reorder-cancel") => void) & ((evt: "reorder-applied", items: TItem[]) => void) & ((evt: "reorder-apply-error", error: unknown) => void) & ((evt: "reorder-end") => void)) & (((event: "update:modelValue", value: TItem[]) => void) & ((event: "update:mode", value: ReorderMode) => void) & ((event: "update:unsavedKeys", value: Set<ListEditorKey>) => void));
354
+ emit: (((evt: "deleted", item: ListViewItem<TItem>) => void) & ((evt: "close", item: ListViewItem<TItem>) => void) & ((evt: "edit", item: ListViewItem<TItem>) => void) & ((evt: "item-saved", item: ListViewItem<TItem>) => void) & ((evt: "item-expand", item: ListViewItem<TItem>, expanded: boolean) => void) & ((evt: "reorder-start") => void) & ((evt: "reorder-cancel") => void) & ((evt: "reorder-applied", items: TItem[]) => void) & ((evt: "reorder-apply-error", error: unknown) => void) & ((evt: "reorder-end") => void)) & (((event: "update:modelValue", value: TItem[]) => void) & ((event: "update:mode", value: ReorderMode) => void));
395
355
  }>) => VNode & {
396
356
  __ctx?: Awaited<typeof __VLS_setup>;
397
357
  };
@@ -400,7 +360,6 @@ declare const __VLS_export_19: <TItem extends Record<string, any>>(__VLS_props:
400
360
  props: PublicProps & __VLS_PrettifyLocal_6<(Props_3<TItem> & {
401
361
  modelValue: NestedTree<TItem>;
402
362
  mode?: ReorderMode_2;
403
- unsavedKeys?: Set<ListEditorKey>;
404
363
  }) & {
405
364
  onDeleted?: ((item: NestedViewItem<TItem>) => any) | undefined;
406
365
  onClose?: ((item: NestedViewItem<TItem>) => any) | undefined;
@@ -417,44 +376,27 @@ declare const __VLS_export_19: <TItem extends Record<string, any>>(__VLS_props:
417
376
  "onReorder-applied"?: ((tree: NestedTree<TItem>) => any) | undefined;
418
377
  "onReorder-apply-error"?: ((error: unknown) => any) | undefined;
419
378
  "onReorder-end"?: (() => any) | undefined;
420
- "onUpdate:unsavedKeys"?: ((value: Set<ListEditorKey>) => any) | undefined;
421
379
  "onAdd-child"?: ((parent: NestedViewItem<TItem>) => any) | undefined;
422
380
  }> & (typeof globalThis extends {
423
381
  __VLS_PROPS_FALLBACK: infer P;
424
382
  } ? P : {});
425
- expose: (exposed: ShallowUnwrapRef<{
426
- addItem: (data: TItem, hint?: PositionHint & {
427
- parentId?: ListEditorKey | null;
428
- asFirstChild?: boolean;
429
- childrenAllowed?: boolean;
430
- }) => NestedTree<TItem>;
383
+ expose: (exposed: ShallowUnwrapRef<NestedListEditorHandle<TItem> & {
431
384
  addAfterId: (targetId: ListEditorKey | null, data: TItem, childrenAllowed: boolean) => NestedTree<TItem>;
432
385
  addChildToId: (targetId: ListEditorKey, data: TItem, childrenAllowed: boolean) => NestedTree<TItem>;
433
386
  removeById: (id: ListEditorKey) => void;
434
387
  updateData: (id: ListEditorKey, data: TItem, _children?: unknown, _position?: unknown, _markUnsaved?: unknown) => void;
435
- updateItem: (id: ListEditorKey, data: TItem, markDirty?: boolean) => NestedTree<TItem>;
436
- deleteItem: (id: ListEditorKey) => NestedTree<TItem>;
437
- moveUp: (id: ListEditorKey) => NestedTree<TItem> | null;
438
- moveDown: (id: ListEditorKey) => NestedTree<TItem> | null;
439
- moveTop: (id: ListEditorKey) => NestedTree<TItem> | null;
440
- moveBottom: (id: ListEditorKey) => NestedTree<TItem> | null;
441
- indent: (id: ListEditorKey) => NestedTree<TItem> | null;
442
- outdent: (id: ListEditorKey) => NestedTree<TItem> | null;
443
- moveTo: (id: ListEditorKey, targetParentId: ListEditorKey | null, targetIndex: number) => NestedTree<TItem> | null;
444
- recalculatePositions: (model: NestedTree<TItem>) => NestedTree<TItem>;
445
- viewItems: ComputedRef<NestedViewItem<TItem>[]>;
446
388
  resetDirtyBaseline: () => void;
447
389
  hasUnsavedChanges: ComputedRef<boolean>;
448
390
  unsavedCount: ComputedRef<number>;
449
- clearUnsavedState: (key?: ListEditorKey) => void;
391
+ clearUnsavedState: () => void;
450
392
  enterReorderMode: () => void;
451
393
  cancelReorderMode: () => void;
452
394
  applyReorder: () => Promise<void>;
453
- expand: (id: ListEditorKey) => Set<ListEditorKey> & Omit<Set<ListEditorKey>, keyof Set<any>>;
454
- collapse: (id: ListEditorKey) => boolean;
395
+ expand: (id: ListEditorKey) => void;
396
+ collapse: (id: ListEditorKey) => void;
455
397
  toggleExpand: (id: ListEditorKey) => void;
456
- expandDetail: (id: ListEditorKey) => Set<ListEditorKey> & Omit<Set<ListEditorKey>, keyof Set<any>>;
457
- collapseDetail: (id: ListEditorKey) => boolean;
398
+ expandDetail: (id: ListEditorKey) => void;
399
+ collapseDetail: (id: ListEditorKey) => void;
458
400
  }>) => void;
459
401
  attrs: any;
460
402
  slots: {
@@ -472,7 +414,7 @@ declare const __VLS_export_19: <TItem extends Record<string, any>>(__VLS_props:
472
414
  'before-item'?: (props: RowSlotProps_3<TItem>) => unknown;
473
415
  'after-item'?: (props: RowSlotProps_3<TItem>) => unknown;
474
416
  };
475
- emit: (((evt: "deleted", item: NestedViewItem<TItem>) => void) & ((evt: "close", item: NestedViewItem<TItem>) => void) & ((evt: "edit", item: NestedViewItem<TItem>) => void) & ((evt: "add", hint: NestedPositionHint | undefined) => void) & ((evt: "indent", item: NestedViewItem<TItem>) => void) & ((evt: "outdent", item: NestedViewItem<TItem>) => void) & ((evt: "item-saved", item: NestedViewItem<TItem>) => void) & ((evt: "item-expand", item: NestedViewItem<TItem>, expanded: boolean) => void) & ((evt: "reorder-start") => void) & ((evt: "reorder-cancel") => void) & ((evt: "reorder-applied", tree: NestedTree<TItem>) => void) & ((evt: "reorder-apply-error", error: unknown) => void) & ((evt: "reorder-end") => void) & ((evt: "add-child", parent: NestedViewItem<TItem>) => void)) & (((event: "update:modelValue", value: NestedTree<TItem>) => void) & ((event: "update:mode", value: ReorderMode_2) => void) & ((event: "update:unsavedKeys", value: Set<ListEditorKey>) => void));
417
+ emit: (((evt: "deleted", item: NestedViewItem<TItem>) => void) & ((evt: "close", item: NestedViewItem<TItem>) => void) & ((evt: "edit", item: NestedViewItem<TItem>) => void) & ((evt: "add", hint: NestedPositionHint | undefined) => void) & ((evt: "indent", item: NestedViewItem<TItem>) => void) & ((evt: "outdent", item: NestedViewItem<TItem>) => void) & ((evt: "item-saved", item: NestedViewItem<TItem>) => void) & ((evt: "item-expand", item: NestedViewItem<TItem>, expanded: boolean) => void) & ((evt: "reorder-start") => void) & ((evt: "reorder-cancel") => void) & ((evt: "reorder-applied", tree: NestedTree<TItem>) => void) & ((evt: "reorder-apply-error", error: unknown) => void) & ((evt: "reorder-end") => void) & ((evt: "add-child", parent: NestedViewItem<TItem>) => void)) & (((event: "update:modelValue", value: NestedTree<TItem>) => void) & ((event: "update:mode", value: ReorderMode_2) => void));
476
418
  }>) => VNode & {
477
419
  __ctx?: Awaited<typeof __VLS_setup>;
478
420
  };
@@ -1011,6 +953,8 @@ declare interface DecoratedViewItem<T> extends ListViewItem<T> {
1011
953
  expanded: boolean;
1012
954
  loading: boolean;
1013
955
  dirty: boolean;
956
+ unsaved: boolean;
957
+ validationState: ListEditorValidationState;
1014
958
  }
1015
959
 
1016
960
  declare interface DecoratedViewItem_2<T> extends ListViewItem<T> {
@@ -1020,6 +964,7 @@ declare interface DecoratedViewItem_2<T> extends ListViewItem<T> {
1020
964
  dirty: boolean;
1021
965
  moved: boolean;
1022
966
  unsaved: boolean;
967
+ validationState: ListEditorValidationState;
1023
968
  canMoveUp: boolean;
1024
969
  canMoveDown: boolean;
1025
970
  }
@@ -1161,6 +1106,8 @@ declare interface GeneralFilterOptions {
1161
1106
 
1162
1107
  export declare const generateListQuery: (pagination: Ref<Pagination>, filterData: FilterData<any>, filterConfig: FilterConfig<any>) => string;
1163
1108
 
1109
+ export declare type GetKey<TItem> = keyof TItem | ((item: TItem) => ListEditorKey);
1110
+
1164
1111
  declare interface HeaderSlotProps {
1165
1112
  title: string | null;
1166
1113
  }
@@ -1220,6 +1167,41 @@ export declare interface ListEditorApi<TItem> {
1220
1167
  recalculatePositions: (items: TItem[]) => TItem[];
1221
1168
  }
1222
1169
 
1170
+ export declare interface ListEditorChanges<TItem> {
1171
+ added: TItem[];
1172
+ updated: TItem[];
1173
+ moved: TItem[];
1174
+ deleted: TItem[];
1175
+ reparented: TItem[];
1176
+ }
1177
+
1178
+ export declare interface ListEditorHandle<TItem extends Record<string, any>> {
1179
+ items: ComputedRef<TItem[]>;
1180
+ hasUnsaved: ComputedRef<boolean>;
1181
+ hasErrors: ComputedRef<boolean>;
1182
+ invalidKeys: ComputedRef<Set<ListEditorKey>>;
1183
+ /** Amber: is this row added / edited / moved since the last commit? */
1184
+ isUnsaved: (key: ListEditorKey) => boolean;
1185
+ /** Red rail (gated): 'invalid' only once the row is unsaved or `validateAll()` ran. */
1186
+ rowState: (item: TItem, key: ListEditorKey) => ListEditorValidationState;
1187
+ /** Force-show all invalid rows + return whether the list is valid. The save guard. */
1188
+ validateAll: () => boolean;
1189
+ /** Normalized ordered array for a full-DTO save. */
1190
+ getPayload: () => TItem[];
1191
+ /** Change-set for partial-save endpoints. */
1192
+ getChanges: () => ListEditorChanges<TItem>;
1193
+ /** Adopt saved rows as the new baseline; backfill keys; clear unsaved/submitted. */
1194
+ commit: (savedItems?: TItem[]) => void;
1195
+ /** Discard unsaved edits back to the last committed baseline (or given items). */
1196
+ reset: (items?: TItem[]) => void;
1197
+ addItem: (item?: TItem, hint?: PositionHint) => void;
1198
+ updateItem: (key: ListEditorKey, next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void;
1199
+ deleteItem: (key: ListEditorKey) => void;
1200
+ moveItem: (fromIndex: number, toIndex: number) => void;
1201
+ /** Escape hatch: a row form (e.g. vuelidate) reports its own validity instead of `validate`. */
1202
+ registerValidity: (key: ListEditorKey, isValid: () => boolean) => () => void;
1203
+ }
1204
+
1223
1205
  export declare type ListEditorKey = DocId | IntegerId | string;
1224
1206
 
1225
1207
  export declare const ListEditorValidationKey: InjectionKey<ListEditorValidationRegistry>;
@@ -1229,6 +1211,13 @@ export declare interface ListEditorValidationRegistry {
1229
1211
  unregister: (key: ListEditorKey) => void;
1230
1212
  }
1231
1213
 
1214
+ /** Per-row validity. `true` (or `{ valid: true }`) = VALID. Only `valid: false` blocks save. */
1215
+ export declare type ListEditorValidationResult = boolean | {
1216
+ valid: boolean;
1217
+ state?: 'invalid' | 'warning';
1218
+ message?: string;
1219
+ };
1220
+
1232
1221
  export declare type ListEditorValidationState = 'valid' | 'invalid' | 'warning' | null;
1233
1222
 
1234
1223
  export declare interface ListViewItem<TItem> {
@@ -1281,6 +1270,71 @@ export declare interface NestedListEditorApi<TItem extends Record<string, any>>
1281
1270
  calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number;
1282
1271
  }
1283
1272
 
1273
+ /** Flattened ordered row carrying its resolved key (for change-sets + payload). */
1274
+ export declare interface NestedListEditorChanges<TItem> {
1275
+ added: TItem[];
1276
+ updated: TItem[];
1277
+ moved: TItem[];
1278
+ deleted: TItem[];
1279
+ /**
1280
+ * Rows whose parent changed since the last commit (BUG-13 — persisted even
1281
+ * when the position number is unchanged).
1282
+ */
1283
+ reparented: TItem[];
1284
+ }
1285
+
1286
+ /**
1287
+ * Tree-shaped state controller for the nested list editor (v2). A superset of
1288
+ * the flat `ListEditorHandle`: same dirty/validate/commit/reset semantics, but
1289
+ * the row set is a flattened ordered projection of a `NestedTree` and the
1290
+ * mutators are tree ops (indent / outdent / moveTo / addChild …) delegated to
1291
+ * `useNestedListEditor`. Each flattened payload row carries its resolved
1292
+ * `position` and parent key written onto it.
1293
+ */
1294
+ export declare interface NestedListEditorHandle<TItem extends Record<string, any>> {
1295
+ /** Flattened ordered rows (depth-first), matching the rendered order. */
1296
+ items: ComputedRef<TItem[]>;
1297
+ /** Tree view rows (key/index/raw/depth/parent…), as consumed by the renderer. */
1298
+ viewItems: ComputedRef<NestedViewItem<TItem>[]>;
1299
+ hasUnsaved: ComputedRef<boolean>;
1300
+ hasErrors: ComputedRef<boolean>;
1301
+ invalidKeys: ComputedRef<Set<ListEditorKey>>;
1302
+ /** Amber: is this row added / edited / moved / reparented since the last commit? */
1303
+ isUnsaved: (key: ListEditorKey) => boolean;
1304
+ /** Red rail (gated): 'invalid' only once the row is unsaved or `validateAll()` ran. */
1305
+ rowState: (item: TItem, key: ListEditorKey) => ListEditorValidationState;
1306
+ /** Force-show all invalid rows + return whether the tree is valid. The save guard. */
1307
+ validateAll: () => boolean;
1308
+ /** Flattened ordered array (each row carrying its resolved position + parent key) for a full-DTO save. */
1309
+ getPayload: () => TItem[];
1310
+ /** Change-set for partial-save endpoints (added/updated/moved/deleted/reparented), keyed. */
1311
+ getChanges: () => NestedListEditorChanges<TItem>;
1312
+ /** Adopt the saved tree as the new baseline; backfill keys; clear dirty/submitted. */
1313
+ commit: (saved?: NestedTree<TItem>) => void;
1314
+ /** Discard unsaved edits back to the last committed baseline (or a given tree). */
1315
+ reset: (tree?: NestedTree<TItem>) => void;
1316
+ addItem: (item?: TItem, hint?: NestedPositionHint) => void;
1317
+ addAfter: (afterKey: ListEditorKey, item?: TItem, childrenAllowed?: boolean) => void;
1318
+ addChild: (parentKey: ListEditorKey, item?: TItem, childrenAllowed?: boolean) => void;
1319
+ updateItem: (key: ListEditorKey, data: TItem, markDirty?: boolean) => void;
1320
+ deleteItem: (key: ListEditorKey) => void;
1321
+ moveUp: (key: ListEditorKey) => boolean;
1322
+ moveDown: (key: ListEditorKey) => boolean;
1323
+ moveTop: (key: ListEditorKey) => boolean;
1324
+ moveBottom: (key: ListEditorKey) => boolean;
1325
+ indent: (key: ListEditorKey) => boolean;
1326
+ outdent: (key: ListEditorKey) => boolean;
1327
+ moveTo: (key: ListEditorKey, targetParentKey: ListEditorKey | null, targetIndex: number) => boolean;
1328
+ recalculatePositions: () => void;
1329
+ /** Escape hatch: a row form (e.g. vuelidate) reports its own validity instead of `validate`. */
1330
+ registerValidity: (key: ListEditorKey, isValid: () => boolean) => () => void;
1331
+ findNode: (key: ListEditorKey) => {
1332
+ node: NestedTreeNode<TItem> | null;
1333
+ parent: NestedTreeNode<TItem> | null;
1334
+ };
1335
+ calculateSubtreeDepth: (node: NestedTreeNode<TItem>) => number;
1336
+ }
1337
+
1284
1338
  export declare interface NestedPositionHint {
1285
1339
  parentId?: ListEditorKey | null;
1286
1340
  afterId?: ListEditorKey;
@@ -1360,11 +1414,46 @@ export declare interface PositionHint {
1360
1414
  index?: number;
1361
1415
  }
1362
1416
 
1417
+ export declare type PositionOption<TItem> = false | keyof TItem | {
1418
+ field: keyof TItem;
1419
+ multiplier?: number;
1420
+ };
1421
+
1363
1422
  declare interface Props<TItem extends Record<string, any>> {
1364
- keyField?: string;
1365
- positionField?: string;
1366
- positionMultiplier?: number;
1367
- updatePosition?: boolean;
1423
+ /**
1424
+ * New-row factory. Add / "add after" insert `factory()` through the controller
1425
+ * (positions renumbered) — no consumer `@add` push handler is needed.
1426
+ */
1427
+ factory?: () => TItem;
1428
+ /**
1429
+ * Stable row identity. Default `'id'`; never the position field. Typed `string`
1430
+ * not `keyof TItem` because the latter compiles to a Boolean-only runtime prop
1431
+ * type that silently coerces `get-key="id"` to `true`.
1432
+ */
1433
+ getKey?: string | ((item: TItem) => ListEditorKey);
1434
+ /**
1435
+ * Managed order field. Default `'position'`; `false` opts out. `string` must be
1436
+ * listed before `false` (and not be `keyof TItem`): otherwise Vue's runtime
1437
+ * type goes Boolean-first and coerces `position="position"` to `true`.
1438
+ */
1439
+ position?: string | false | {
1440
+ field: string;
1441
+ multiplier?: number;
1442
+ };
1443
+ /**
1444
+ * Extra fields to drop from the dirty content-hash (position is always dropped).
1445
+ * Use when a SEPARATE nested editor already tracks a child collection, so editing
1446
+ * a child doesn't flip the parent row amber. Still saved; only the diff ignores it.
1447
+ */
1448
+ dirtyExclude?: string[];
1449
+ /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */
1450
+ validate?: (item: TItem) => ListEditorValidationResult;
1451
+ /**
1452
+ * Opt-in lifted controller from `useListEditorController()` — pass it so editor
1453
+ * state survives this component's unmount/remount. Omitted: the editor owns one
1454
+ * internally. Either way the `ListEditorHandle` is reachable via `useTemplateRef`.
1455
+ */
1456
+ editor?: ListEditorHandle<TItem>;
1368
1457
  readonly?: boolean;
1369
1458
  disabled?: boolean;
1370
1459
  loading?: boolean;
@@ -1382,49 +1471,64 @@ declare interface Props<TItem extends Record<string, any>> {
1382
1471
  emptyTitle?: string | null;
1383
1472
  disableRowClick?: boolean;
1384
1473
  disableDeleteConfirm?: boolean;
1385
- /** Disable unsaved-state tracking — no dirty markers, never feeds `unsaved-keys`. */
1474
+ /** Disable unsaved-state tracking — no dirty markers, never reads as unsaved. */
1386
1475
  disableUnsaved?: boolean;
1387
1476
  deleteConfirmTitle?: string | null;
1388
1477
  deleteConfirmText?: string | null;
1389
1478
  closeVariant?: 'auto' | 'icon' | 'labeled';
1390
1479
  /**
1391
- * Render every row's `#item` slot expanded by default — no edit pencil, no
1392
- * inline Save/Cancel footer, no row-click toggle. Use when the editor has
1393
- * no reorder affordance and the consumer wants all forms visible at once
1394
- * (e.g. ThirdPartyTracker, bookmarks dialog).
1480
+ * Render every `#item` slot expanded — no edit pencil, inline footer, or row-click
1481
+ * toggle. Use when all forms should be visible at once (e.g. ThirdPartyTracker).
1395
1482
  */
1396
1483
  defaultExpanded?: boolean;
1397
1484
  loadingKeys?: Set<ListEditorKey> | null;
1398
- getValidationState?: (item: TItem, key: ListEditorKey, index: number) => ListEditorValidationState;
1399
1485
  onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean;
1400
1486
  onDelete?: (item: TItem) => Promise<void> | void;
1401
1487
  onItemSave?: (item: TItem) => Promise<void> | void;
1402
1488
  /**
1403
- * Editor-managed add: the add button and "add after this item" insert
1404
- * `itemFactory()` into the model directly (positions renumbered when
1405
- * `update-position` is on) and emit `added` — the `add` event does NOT fire,
1406
- * so no consumer push handler is needed.
1407
- */
1408
- itemFactory?: () => TItem;
1409
- /**
1410
- * Editor-managed delete: a confirmed delete removes the row from the model
1411
- * itself. `deleted` still fires as a notification — consumers keep side
1412
- * effects but must not splice the model themselves.
1413
- */
1414
- manageDelete?: boolean;
1415
- /**
1416
- * Registers this editor as a named unsaved-changes section under the given
1417
- * (already translated) label — replaces the per-consumer `useUnsavedSection`
1418
- * call for the common one-editor case.
1489
+ * Registers this editor as a named unsaved-changes section under this (already
1490
+ * translated) label replaces a per-consumer `useUnsavedSection` call.
1419
1491
  */
1420
1492
  unsavedSectionLabel?: string;
1421
1493
  }
1422
1494
 
1423
1495
  declare interface Props_2<TItem extends Record<string, any>> {
1424
- keyField?: string;
1425
- positionField?: string;
1426
- positionMultiplier?: number;
1427
- updatePosition?: boolean;
1496
+ /**
1497
+ * New-row factory (required). The add button + "add after this item" insert
1498
+ * `factory()` into the model directly through the controller (positions
1499
+ * renumbered) — no consumer `@add` push handler is needed.
1500
+ */
1501
+ factory?: () => TItem;
1502
+ /**
1503
+ * Stable row identity. Default `'id'`. Never point this at the position field.
1504
+ * Typed `string` (not `keyof TItem`): a bare `keyof TItem` on a generic
1505
+ * component compiles to a Boolean-only runtime type that coerces `get-key="id"` to `true`.
1506
+ */
1507
+ getKey?: string | ((item: TItem) => ListEditorKey);
1508
+ /**
1509
+ * Managed order field. Default `'position'`. `false` opts out. Typed `string`
1510
+ * before `false` so the runtime prop type is `[String, Boolean, Object]`:
1511
+ * `keyof TItem` would be Boolean-only, and `false` listed first would coerce
1512
+ * `position="position"` (value == prop name) to `true`.
1513
+ */
1514
+ position?: string | false | {
1515
+ field: string;
1516
+ multiplier?: number;
1517
+ };
1518
+ /**
1519
+ * Extra fields to drop from the dirty content-hash (position is always
1520
+ * dropped). Use when a SEPARATE nested editor tracks a row's child collection,
1521
+ * so editing a child doesn't flip the parent amber. The field is still saved.
1522
+ */
1523
+ dirtyExclude?: string[];
1524
+ /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */
1525
+ validate?: (item: TItem) => ListEditorValidationResult;
1526
+ /**
1527
+ * Opt-in lifted state controller (from consumer's `useListEditorController()`)
1528
+ * so editor state survives this component's unmount/remount (pinned-widget
1529
+ * relocation). When omitted the editor owns an internal controller.
1530
+ */
1531
+ editor?: ListEditorHandle<TItem>;
1428
1532
  readonly?: boolean;
1429
1533
  disabled?: boolean;
1430
1534
  loading?: boolean;
@@ -1449,61 +1553,67 @@ declare interface Props_2<TItem extends Record<string, any>> {
1449
1553
  showReorderToggle?: boolean;
1450
1554
  disableReorder?: boolean;
1451
1555
  disableDrag?: boolean;
1452
- /** Disable unsaved-state tracking — no dirty/moved markers, never feeds `unsaved-keys`. */
1453
- disableUnsaved?: boolean;
1454
1556
  showMoveToPosition?: boolean;
1455
1557
  /**
1456
- * Embedded mode — this editor is nested inside another editor's `#item`
1457
- * slot and follows a shared `v-model:mode` from the outer editor. When
1458
- * set, the editor:
1459
- * - hides its own Reorder button + Cancel/Apply toolbar
1460
- * - skips the snapshot/restore (the outer editor's deep snapshot covers
1461
- * nested data, so cancel at the top reverts everything)
1462
- * - paints lighter chrome so it visually reads as part of the parent row
1463
- * Pair with `v-model:mode` bound to the same ref the outer editor uses.
1558
+ * Embedded mode — nested inside another editor's `#item` slot, following a
1559
+ * shared `v-model:mode`. Hides own Reorder button + toolbar, skips the
1560
+ * snapshot/restore (the outer editor's deep snapshot covers nested data), and
1561
+ * paints lighter chrome. Pair with `v-model:mode` bound to the outer ref.
1464
1562
  */
1465
1563
  embedded?: boolean;
1466
1564
  /**
1467
- * Allow rows to remain inline-editable while the editor is in reorder
1468
- * mode. By default, entering reorder closes any open inline edit and the
1469
- * `#item` slot body is hidden this keeps the visual focus on dragging.
1470
- * Set this when the open row's body needs to render in reorder mode (e.g.
1471
- * the parent editor of a shared-reorder pair, where the open question
1472
- * exposes its embedded answers list for dragging).
1565
+ * Keep rows inline-editable in reorder mode. By default entering reorder
1566
+ * closes any open edit and hides the `#item` body to focus on dragging. Set
1567
+ * for a shared-reorder pair's parent, where the open row exposes its embedded
1568
+ * answers list for dragging.
1473
1569
  */
1474
1570
  allowEditInReorder?: boolean;
1475
- getValidationState?: (item: TItem, key: ListEditorKey, index: number) => ListEditorValidationState;
1476
1571
  onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean;
1477
1572
  onDelete?: (item: TItem) => Promise<void> | void;
1478
1573
  onItemSave?: (item: TItem) => Promise<void> | void;
1479
1574
  onReorderApply?: (items: TItem[]) => Promise<void> | void;
1575
+ /** Register as a named unsaved-changes section under this (translated) label. */
1576
+ unsavedSectionLabel?: string;
1577
+ }
1578
+
1579
+ declare interface Props_3<TItem extends Record<string, any>> {
1580
+ maxDepth: number;
1480
1581
  /**
1481
- * Editor-managed add: the add button and "add after this item" insert
1482
- * `itemFactory()` into the model directly (positions renumbered when
1483
- * `update-position` is on) and emit `added` — the `add` event does NOT fire,
1484
- * so no consumer push handler is needed.
1582
+ * New-row factory; if set, add/add-after/add-inside insert through the
1583
+ * controller (no `@add` handler needed). Optional for read-only trees.
1485
1584
  */
1486
- itemFactory?: () => TItem;
1585
+ factory?: () => TItem;
1487
1586
  /**
1488
- * Editor-managed delete: a confirmed delete removes the row from the model
1489
- * itself. `deleted` still fires as a notification consumers keep side
1490
- * effects but must not splice the model themselves.
1587
+ * Stable row identity. Default `'id'`. Never point at the position field.
1588
+ * Typed `string` (not `keyof TItem`): a bare keyof compiles to a Boolean-only
1589
+ * runtime type that silently coerces `get-key="id"` to `true`.
1491
1590
  */
1492
- manageDelete?: boolean;
1591
+ getKey?: string | ((item: TItem) => ListEditorKey);
1493
1592
  /**
1494
- * Registers this editor as a named unsaved-changes section under the given
1495
- * (already translated) label replaces the per-consumer `useUnsavedSection`
1496
- * call for the common one-editor case.
1593
+ * Managed order field. Default `'position'`; `false` opts out. Typed
1594
+ * `[String, Boolean, Object]` with `string` before `false`: a bare keyof
1595
+ * compiles Boolean-only, and `false`-first would coerce `position="position"`
1596
+ * (value == prop name) to `true`. Object form folds in `{ field, multiplier }`.
1497
1597
  */
1498
- unsavedSectionLabel?: string;
1499
- }
1500
-
1501
- declare interface Props_3<TItem extends Record<string, any>> {
1502
- maxDepth: number;
1503
- keyField?: string;
1504
- positionField?: string;
1598
+ position?: string | false | {
1599
+ field: string;
1600
+ multiplier?: number;
1601
+ };
1602
+ /** Parent-key field written onto reparented rows. Default `'parent'`. */
1505
1603
  parentField?: string;
1506
- positionMultiplier?: number;
1604
+ /**
1605
+ * Extra fields dropped from the dirty content-hash (position + parent always
1606
+ * dropped), e.g. a child collection tracked by a separate editor.
1607
+ */
1608
+ dirtyExclude?: string[];
1609
+ /** Per-row validity — `true` (or `{ valid: true }`) = VALID. Drives the red rail + save guard. */
1610
+ validate?: (item: TItem) => ListEditorValidationResult;
1611
+ /**
1612
+ * Opt-in lifted controller (`useNestedListEditorController()`) so state
1613
+ * survives unmount/remount; omitted = internal controller. Same handle
1614
+ * reachable via `useTemplateRef`.
1615
+ */
1616
+ editor?: NestedListEditorHandle<TItem>;
1507
1617
  readonly?: boolean;
1508
1618
  disabled?: boolean;
1509
1619
  loading?: boolean;
@@ -1519,7 +1629,6 @@ declare interface Props_3<TItem extends Record<string, any>> {
1519
1629
  showMoveToPosition?: boolean;
1520
1630
  showChangeParent?: boolean;
1521
1631
  showExpandToggle?: boolean;
1522
- getValidationState?: (item: TItem, key: ListEditorKey, index: number) => ListEditorValidationState;
1523
1632
  addLabel?: string | null;
1524
1633
  emptyTitle?: string | null;
1525
1634
  disableRowClick?: boolean;
@@ -1531,17 +1640,11 @@ declare interface Props_3<TItem extends Record<string, any>> {
1531
1640
  showReorderToggle?: boolean;
1532
1641
  disableReorder?: boolean;
1533
1642
  disableDrag?: boolean;
1534
- /** Disable unsaved-state tracking — no dirty/moved markers, never feeds `unsaved-keys`. */
1535
- disableUnsaved?: boolean;
1536
1643
  onDeleteConfirm?: (item: TItem) => Promise<boolean> | boolean;
1537
1644
  onDelete?: (item: TItem) => Promise<void> | void;
1538
1645
  onItemSave?: (item: TItem) => Promise<void> | void;
1539
1646
  onReorderApply?: (tree: NestedTree<TItem>) => Promise<void> | void;
1540
- /**
1541
- * Registers this editor as a named unsaved-changes section under the given
1542
- * (already translated) label — replaces the per-consumer `useUnsavedSection`
1543
- * call for the common one-editor case.
1544
- */
1647
+ /** Registers this editor as a named unsaved-changes section under the given (translated) label. */
1545
1648
  unsavedSectionLabel?: string;
1546
1649
  }
1547
1650
 
@@ -1557,6 +1660,10 @@ declare interface Props_4 {
1557
1660
  dirtyLabels?: string[];
1558
1661
  }
1559
1662
 
1663
+ /**
1664
+ * Non-mutating renumber to sequential positions matching array order — the same
1665
+ * renumbering the editors apply on commit. Unchanged items stay reference-equal.
1666
+ */
1560
1667
  export declare const renumberPositions: <TItem extends Record<string, any>>(items: TItem[], options?: RenumberPositionsOptions) => TItem[];
1561
1668
 
1562
1669
  export declare interface RenumberPositionsOptions {
@@ -1600,7 +1707,7 @@ declare interface RowActions<TItem> {
1600
1707
  delete: () => Promise<void>;
1601
1708
  addAfter: () => void;
1602
1709
  toggleExpand: () => void;
1603
- update: (data: TItem) => TItem[];
1710
+ update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void;
1604
1711
  }
1605
1712
 
1606
1713
  declare interface RowActions_2<TItem> {
@@ -1615,7 +1722,7 @@ declare interface RowActions_2<TItem> {
1615
1722
  moveDown: () => void;
1616
1723
  moveTop: () => void;
1617
1724
  moveBottom: () => void;
1618
- update: (data: TItem) => TItem[];
1725
+ update: (next: TItem | Partial<TItem> | ((current: TItem) => TItem)) => void;
1619
1726
  }
1620
1727
 
1621
1728
  declare interface RowActions_3<TItem> {
@@ -1634,13 +1741,11 @@ declare interface RowActions_3<TItem> {
1634
1741
  moveBottom: () => void;
1635
1742
  indent: () => void;
1636
1743
  outdent: () => void;
1637
- update: (data: TItem) => NestedTree<TItem>;
1744
+ update: (data: TItem) => void;
1638
1745
  }
1639
1746
 
1640
1747
  declare interface RowSlotProps<TItem extends Record<string, any>> {
1641
- item: DecoratedViewItem<TItem> & {
1642
- validationState: ListEditorValidationState;
1643
- };
1748
+ item: DecoratedViewItem<TItem>;
1644
1749
  raw: TItem;
1645
1750
  index: number;
1646
1751
  key: ListEditorKey;
@@ -1656,9 +1761,7 @@ declare interface RowSlotProps<TItem extends Record<string, any>> {
1656
1761
  }
1657
1762
 
1658
1763
  declare interface RowSlotProps_2<TItem extends Record<string, any>> {
1659
- item: DecoratedViewItem_2<TItem> & {
1660
- validationState: ListEditorValidationState;
1661
- };
1764
+ item: DecoratedViewItem_2<TItem>;
1662
1765
  raw: TItem;
1663
1766
  index: number;
1664
1767
  key: ListEditorKey;
@@ -1742,13 +1845,7 @@ export declare interface SortableNestedItem<TData extends SortableItemWithParent
1742
1845
  };
1743
1846
  }
1744
1847
 
1745
- /**
1746
- * Returns a copy of the list ordered by ascending `position`. Use after
1747
- * fetching/saving collections whose backend returns rows in storage order —
1748
- * a shared, non-mutating alternative to scattered
1749
- * `items.sort((a, b) => a.position - b.position)` calls (in-place `sort`
1750
- * mutates the source and can trigger unintended reactivity).
1751
- */
1848
+ /** Non-mutating sort by ascending `position` (in-place `sort` would trigger reactivity). */
1752
1849
  export declare const sortByPosition: <TItem extends {
1753
1850
  position: number;
1754
1851
  }>(items: TItem[]) => TItem[];
@@ -1939,6 +2036,45 @@ export declare function useJobApi<JobType extends JobBase = JobBase>(client: ()
1939
2036
  */
1940
2037
  export declare function useListEditor<TItem extends Record<string, any>>(model: Ref<TItem[]>, options?: UseListEditorOptions): ListEditorApi<TItem>;
1941
2038
 
2039
+ /**
2040
+ * Component-owned state controller for the list editors (v2). Owns row keys,
2041
+ * declared dirty tracking (no fragile whole-list JSON diff on volatile fields),
2042
+ * validation summary, position renumbering, and the save lifecycle — exposed as
2043
+ * a `ListEditorHandle`. Created internally by an editor (default) or lifted by a
2044
+ * consumer via `useListEditor()` + `:editor` so the state survives unmount/remount.
2045
+ */
2046
+ export declare function useListEditorController<TItem extends Record<string, any>>(options: UseListEditorControllerOptions<TItem>): ListEditorHandle<TItem>;
2047
+
2048
+ export declare interface UseListEditorControllerOptions<TItem extends Record<string, any>> {
2049
+ /** Read the live list (the editor's v-model, or a consumer store getter). */
2050
+ get: () => TItem[];
2051
+ /** Write the list back. */
2052
+ set: (items: TItem[]) => void;
2053
+ /** New-row factory. Optional for read-only / no-add lists. Should mint a stable temp id. */
2054
+ factory?: () => TItem;
2055
+ /** Stable row identity. Default `'id'`. Never point this at the position field. */
2056
+ getKey?: GetKey<TItem>;
2057
+ /** Managed order field. Default `'position'`. `false` opts out of position management. */
2058
+ position?: PositionOption<TItem>;
2059
+ /**
2060
+ * Extra top-level fields to drop from the dirty content-hash (position is
2061
+ * always dropped). For a child collection a separate nested editor already
2062
+ * tracks, so editing a child doesn't flip the parent row amber. Still in the
2063
+ * payload — only the dirty comparison ignores it. Reactive (accepts a getter/ref).
2064
+ */
2065
+ dirtyExclude?: MaybeRefOrGetter<string[]>;
2066
+ /** Per-row validity — `true` = valid. Drives the red rail + `hasErrors`. */
2067
+ validate?: (item: TItem) => ListEditorValidationResult;
2068
+ /** Custom dirty predicate (override the default content-diff). */
2069
+ isDirty?: (current: TItem, saved: TItem | undefined) => boolean;
2070
+ /** Custom save payload (overrides `getPayload`). */
2071
+ payload?: (items: TItem[]) => TItem[];
2072
+ /** Resolve a saved row's key on commit (default: existing key, else mint a temp id). */
2073
+ commitKey?: (saved: TItem, previous: TItem | undefined) => ListEditorKey;
2074
+ /** Normalize server rows before they become the new baseline on commit. */
2075
+ normalizeSaved?: (savedItems: TItem[]) => TItem[];
2076
+ }
2077
+
1942
2078
  export declare function useListEditorItemValidation(options: UseListEditorItemValidationOptions): void;
1943
2079
 
1944
2080
  declare interface UseListEditorItemValidationOptions {
@@ -2006,8 +2142,50 @@ export declare function useListEditorVuelidateSentinel(v$: Ref<VuelidateSentinel
2006
2142
  */
2007
2143
  export declare function useNestedListEditor<TItem extends Record<string, any>>(model: Ref<NestedTree<TItem>>, options: UseNestedListEditorOptions): NestedListEditorApi<TItem>;
2008
2144
 
2145
+ export declare function useNestedListEditorController<TItem extends Record<string, any>>(options: UseNestedListEditorControllerOptions<TItem>): NestedListEditorHandle<TItem>;
2146
+
2147
+ export declare interface UseNestedListEditorControllerOptions<TItem extends Record<string, any>> {
2148
+ /** Read the live tree (the editor's v-model, or a consumer store getter). */
2149
+ get: () => NestedTree<TItem>;
2150
+ /** Write the tree back. */
2151
+ set: (tree: NestedTree<TItem>) => void;
2152
+ /** New-row factory. Optional for read-only / no-add trees. Should mint a stable temp id. */
2153
+ factory?: () => TItem;
2154
+ /** Stable row identity. Default `'id'`. Never point this at the position field. */
2155
+ getKey?: GetKey<TItem>;
2156
+ /** Managed order field. Default `'position'`. `false` opts out of position management. */
2157
+ position?: PositionOption<TItem>;
2158
+ /** Parent-key field written onto reparented rows. Default `'parent'`. */
2159
+ parentField?: string;
2160
+ /** Maximum nesting depth (1-based). Enforced by indent/outdent/moveTo. */
2161
+ maxDepth: number;
2162
+ /**
2163
+ * Extra top-level fields to drop from the dirty content-hash (position +
2164
+ * parent are always dropped). Same purpose as the flat controller — a child
2165
+ * collection tracked by a separate editor shouldn't flip its owner amber.
2166
+ * Accepts a getter/ref so a `computed` set is honoured reactively.
2167
+ */
2168
+ dirtyExclude?: MaybeRefOrGetter<string[]>;
2169
+ /** Per-row validity — `true` = valid. Drives the red rail + `hasErrors`. */
2170
+ validate?: (item: TItem) => ListEditorValidationResult;
2171
+ /** Custom dirty predicate (override the default `meta.dirty ∪ content-diff`). */
2172
+ isDirty?: (current: TItem, saved: TItem | undefined) => boolean;
2173
+ /** Custom save payload (overrides `getPayload`). Receives the flattened ordered rows. */
2174
+ payload?: (items: TItem[]) => TItem[];
2175
+ /** Resolve a saved row's key on commit (default: existing key, else mint a temp id). */
2176
+ commitKey?: (saved: TItem, previous: TItem | undefined) => ListEditorKey;
2177
+ /** Normalize the server tree before it becomes the new baseline on commit. */
2178
+ normalizeSaved?: (saved: NestedTree<TItem>) => NestedTree<TItem>;
2179
+ }
2180
+
2009
2181
  declare interface UseNestedListEditorOptions {
2010
2182
  keyField?: string;
2183
+ /**
2184
+ * Resolve a row's key; takes precedence over `keyField`. Pass the controller's
2185
+ * `keyOf` so tree ops key rows the same way as dirty/validity tracking — incl.
2186
+ * a FUNCTION get-key, which `keyField` lookup would miss.
2187
+ */
2188
+ getKey?: (data: any) => ListEditorKey;
2011
2189
  positionField?: string;
2012
2190
  parentField?: string;
2013
2191
  positionMultiplier?: number;