@nanahoshi/mona-ui 0.24.0 → 0.24.2

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 (35) hide show
  1. package/README.md +2 -0
  2. package/fesm2022/nanahoshi-mona-ui-button-group.mjs +1 -1
  3. package/fesm2022/nanahoshi-mona-ui-button.mjs +122 -60
  4. package/fesm2022/nanahoshi-mona-ui-calendar.mjs +1 -2
  5. package/fesm2022/nanahoshi-mona-ui-chip.mjs +1 -1
  6. package/fesm2022/nanahoshi-mona-ui-color-gradient.mjs +1 -1
  7. package/fesm2022/nanahoshi-mona-ui-color-picker.mjs +1 -1
  8. package/fesm2022/nanahoshi-mona-ui-common.mjs +265 -159
  9. package/fesm2022/nanahoshi-mona-ui-date-picker.mjs +1 -1
  10. package/fesm2022/nanahoshi-mona-ui-datetime-picker.mjs +1 -1
  11. package/fesm2022/nanahoshi-mona-ui-dialog.mjs +1 -1
  12. package/fesm2022/nanahoshi-mona-ui-dropdown-button.mjs +1 -1
  13. package/fesm2022/nanahoshi-mona-ui-editor.mjs +13 -13
  14. package/fesm2022/nanahoshi-mona-ui-filter.mjs +1 -1
  15. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component-CnPhVIr4.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-x_euoNnS.mjs} +2 -2
  16. package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-DxY6bfTX.mjs → nanahoshi-mona-ui-grid-grid-list.component-C1_6vq77.mjs} +4 -4
  17. package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-CaO5Lae6.mjs → nanahoshi-mona-ui-grid-grid-row.component-BhukVWIy.mjs} +3 -3
  18. package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-jNYtqg5B.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-XQKOPPVs.mjs} +4 -4
  19. package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-9FtfCutf.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-Iw-wvKGl.mjs} +13 -12
  20. package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
  21. package/fesm2022/nanahoshi-mona-ui-list-box.mjs +1 -1
  22. package/fesm2022/nanahoshi-mona-ui-notification.mjs +1 -1
  23. package/fesm2022/nanahoshi-mona-ui-numeric-text-box.mjs +2 -2
  24. package/fesm2022/nanahoshi-mona-ui-pager.mjs +1 -1
  25. package/fesm2022/nanahoshi-mona-ui-sheet.mjs +1 -1
  26. package/fesm2022/nanahoshi-mona-ui-split-button.mjs +1 -1
  27. package/fesm2022/nanahoshi-mona-ui-splitter.mjs +1 -1
  28. package/fesm2022/nanahoshi-mona-ui-tabs.mjs +1 -1
  29. package/fesm2022/nanahoshi-mona-ui-text-box.mjs +1 -1
  30. package/fesm2022/nanahoshi-mona-ui-time-picker.mjs +1 -1
  31. package/fesm2022/nanahoshi-mona-ui-time-selector.mjs +1 -1
  32. package/fesm2022/nanahoshi-mona-ui-window.mjs +1 -1
  33. package/package.json +1 -1
  34. package/types/nanahoshi-mona-ui-button.d.ts +44 -20
  35. package/types/nanahoshi-mona-ui-common.d.ts +116 -73
@@ -1,9 +1,15 @@
1
1
  import * as _angular_core from '@angular/core';
2
+ import { Signal } from '@angular/core';
2
3
  import * as class_variance_authority_types from 'class-variance-authority/types';
3
4
  import { VariantInputs } from '@nanahoshi/mona-ui/internal';
4
5
  import { VariantProps } from 'class-variance-authority';
5
6
  import { Subject } from 'rxjs';
6
7
 
8
+ /**
9
+ * Values accepted by the `aria-haspopup` input of `ButtonDirective`. `"false"` (the default) omits the attribute.
10
+ */
11
+ type ButtonAriaHasPopup = "dialog" | "false" | "grid" | "listbox" | "menu" | "tree" | "true";
12
+
7
13
  declare const buttonThemeVariants: (props?: ({
8
14
  disabled?: boolean | null | undefined;
9
15
  iconOnly?: boolean | null | undefined;
@@ -18,41 +24,44 @@ type ButtonVariantsInput = VariantInputs<ButtonVariantProps>;
18
24
 
19
25
  declare class ButtonDirective implements ButtonVariantsInput {
20
26
  #private;
21
- protected readonly displaysPressedState: _angular_core.Signal<boolean>;
27
+ protected readonly baseClass: _angular_core.Signal<string>;
22
28
  protected readonly effectiveDisabled: _angular_core.Signal<boolean>;
23
29
  protected readonly effectiveLook: _angular_core.Signal<"default" | "primary" | "success" | "error" | "warning" | "info" | "outline" | "secondary" | "ghost" | "link" | "clear" | null | undefined>;
24
30
  protected readonly effectiveRounded: _angular_core.Signal<"full" | "large" | "medium" | "none" | "small" | null | undefined>;
25
31
  protected readonly effectiveSize: _angular_core.Signal<"large" | "medium" | "small" | null | undefined>;
26
32
  protected readonly effectiveToggleable: _angular_core.Signal<boolean>;
27
- protected readonly isInButtonGroup: _angular_core.Signal<boolean>;
28
- protected readonly baseClass: _angular_core.Signal<string>;
29
33
  protected readonly loadingSize: _angular_core.Signal<12 | 16 | 14>;
30
34
  /**
31
35
  * @description Value forwarded to the `aria-haspopup` attribute. Set when this button triggers a popup, menu,
32
36
  * listbox, tree, grid, or dialog. Omit or leave as `"false"` for plain action buttons.
33
37
  * @default "false"
34
38
  */
35
- readonly ariaHasPopup: _angular_core.InputSignal<string>;
39
+ readonly ariaHasPopup: _angular_core.InputSignal<ButtonAriaHasPopup>;
36
40
  /**
37
41
  * @description Renders the component with reduced visual emphasis and removes pointer interaction.
38
- * When inside a `ButtonGroup`, the group's disabled state takes precedence.
42
+ * Accepts the bare attribute form (`<button monaButton disabled>`).
43
+ * When inside a `ButtonGroup`, the button is disabled when either it or the group is disabled.
39
44
  * @default false
40
45
  */
41
- readonly disabled: _angular_core.ModelSignal<boolean>;
46
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
42
47
  /**
43
48
  * @description Removes horizontal padding and fixes a square aspect ratio sized to match `size`.
44
- * Provide `aria-label` on the host when using icon-only buttons.
49
+ * Provide `aria-label` on the host when using icon-only buttons; in dev mode, an icon-only button without an
50
+ * accessible name logs a warning.
45
51
  * @default false
46
52
  */
47
- readonly iconOnly: _angular_core.InputSignal<boolean>;
53
+ readonly iconOnly: _angular_core.InputSignalWithTransform<boolean, unknown>;
48
54
  /**
49
55
  * @description Displays a loading indicator and prevents interaction while an operation is in progress.
50
- * Sets `aria-busy="true"` on the host.
56
+ * Sets `aria-busy="true"` and `aria-disabled="true"` on the host. The button stays focusable, so keyboard focus
57
+ * isn't lost when loading starts, and clicks (including form submission) are blocked until it ends.
51
58
  * @default false
52
59
  */
53
- readonly loading: _angular_core.ModelSignal<boolean>;
60
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
54
61
  /**
55
62
  * @description Visual style variant of the button. When inside a `ButtonGroup`, the group's look takes precedence.
63
+ * `"outline"` looks the same as `"default"`. `"clear"` shows no selected state, so use another look for
64
+ * toggle buttons.
56
65
  * @default "default"
57
66
  */
58
67
  readonly look: _angular_core.ModelSignal<"default" | "primary" | "success" | "error" | "warning" | "info" | "outline" | "secondary" | "ghost" | "link" | "clear" | null | undefined>;
@@ -75,23 +84,23 @@ declare class ButtonDirective implements ButtonVariantsInput {
75
84
  */
76
85
  readonly size: _angular_core.InputSignal<"large" | "medium" | "small" | null | undefined>;
77
86
  /**
78
- * @description Tab index of the host element. Automatically overridden to `-1` when the button is disabled or loading.
79
- * Accepts a numeric string and converts it to a number.
87
+ * @description Tab index of the host element. Automatically overridden to `-1` when the button is disabled.
88
+ * Accepts a numeric string and converts it to a number; a value that is not a number falls back to `0`.
80
89
  * @default 0
81
90
  */
82
91
  readonly tabindex: _angular_core.InputSignalWithTransform<number, string | number>;
92
+ /**
93
+ * @description Enables toggle behavior — each click flips the `selected` state and `aria-pressed` is managed.
94
+ * Accepts the bare attribute form. Buttons in a `ButtonGroup` are toggleable unless this is set to `false`.
95
+ * @default undefined
96
+ */
97
+ readonly toggleable: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
83
98
  /**
84
99
  * @description Native `type` attribute of the button. Defaults to `"button"` to prevent accidental
85
100
  * form submission; set to `"submit"` or `"reset"` explicitly when that behavior is intended.
86
101
  * @default "button"
87
102
  */
88
103
  readonly type: _angular_core.InputSignal<"button" | "reset" | "submit">;
89
- /**
90
- * @description Enables toggle behavior — each click flips the `selected` state and `aria-pressed` is managed.
91
- * Must use `[toggleable]="true"` binding syntax, not bare attribute form.
92
- * @default undefined
93
- */
94
- readonly toggleable: _angular_core.InputSignal<boolean | undefined>;
95
104
  /**
96
105
  * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
97
106
  * @default ""
@@ -99,19 +108,34 @@ declare class ButtonDirective implements ButtonVariantsInput {
99
108
  readonly userClass: _angular_core.InputSignal<string>;
100
109
  constructor();
101
110
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ButtonDirective, never>;
102
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ButtonDirective, "button[monaButton]", never, { "ariaHasPopup": { "alias": "aria-haspopup"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "iconOnly": { "alias": "iconOnly"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "look": { "alias": "look"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "selected": { "alias": "selected"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "tabindex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, { "disabled": "disabledChange"; "loading": "loadingChange"; "look": "lookChange"; "selected": "selectedChange"; }, never, never, true, never>;
111
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ButtonDirective, "button[monaButton]", never, { "ariaHasPopup": { "alias": "aria-haspopup"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "iconOnly": { "alias": "iconOnly"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "look": { "alias": "look"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "selected": { "alias": "selected"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "tabindex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, { "look": "lookChange"; "selected": "selectedChange"; }, never, never, true, never>;
103
112
  }
104
113
 
114
+ /**
115
+ * Coordinates a `mona-button-group` with its buttons: the group publishes its look, size, rounding, disabled state
116
+ * and members here, and buttons report clicks and receive selection changes through it. It is public so the
117
+ * button-group entry point can provide it; applications don't need to use it directly.
118
+ */
105
119
  declare class ButtonService {
120
+ #private;
106
121
  readonly buttonClick$: Subject<[ButtonDirective, boolean]>;
107
122
  readonly buttonSelect$: Subject<[ButtonDirective, boolean]>;
108
123
  readonly groupDisabled: _angular_core.WritableSignal<boolean>;
109
124
  readonly groupLook: _angular_core.WritableSignal<"default" | "primary" | "success" | "error" | "warning" | "info" | "outline" | "secondary" | "ghost" | "link" | "clear" | null | undefined>;
110
125
  readonly groupRounded: _angular_core.WritableSignal<"full" | "large" | "medium" | "none" | "small" | null | undefined>;
111
126
  readonly groupSize: _angular_core.WritableSignal<"large" | "medium" | "small" | null | undefined>;
127
+ /**
128
+ * The buttons that belong to the group. Only these follow the group's look, size, rounding, disabled state and
129
+ * selection; other buttons that can inject this service ignore it.
130
+ */
131
+ readonly members: Signal<readonly ButtonDirective[]>;
132
+ /**
133
+ * Sets the source of the group's members, such as the group's `contentChildren(ButtonDirective)` query.
134
+ */
135
+ setMembers(members: Signal<readonly ButtonDirective[]>): void;
112
136
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ButtonService, never>;
113
137
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<ButtonService>;
114
138
  }
115
139
 
116
140
  export { ButtonDirective, ButtonService };
117
- export type { ButtonVariantProps, ButtonVariantsInput };
141
+ export type { ButtonAriaHasPopup, ButtonVariantProps, ButtonVariantsInput };
@@ -60,7 +60,12 @@ declare class ContainsPipe implements PipeTransform {
60
60
  }
61
61
 
62
62
  declare class SlicePipe implements PipeTransform {
63
- transform<T>(value: Iterable<T>, start: number, end: number): T[];
63
+ /**
64
+ * Returns the elements from `start` up to, but not including, `end`, with the semantics of `Array.prototype.slice`:
65
+ * negative indices count from the end, and an omitted `end` slices to the end. Returns an empty array for
66
+ * `null` or `undefined`, such as the first value of the `async` pipe.
67
+ */
68
+ transform<T>(value: Iterable<T> | null | undefined, start: number, end?: number): T[];
64
69
  static ɵfac: i0.ɵɵFactoryDeclaration<SlicePipe, never>;
65
70
  static ɵpipe: i0.ɵɵPipeDeclaration<SlicePipe, "monaSlice", true>;
66
71
  }
@@ -76,10 +81,12 @@ type BuiltIn = Primitive | Date | RegExp | Function | Map<unknown, unknown> | Se
76
81
  type DeepPartial<T> = T extends BuiltIn ? T : T extends Array<infer U> ? Array<DeepPartial<U>> : {
77
82
  [P in keyof T]?: DeepPartial<T[P]>;
78
83
  };
79
- type Path<T> = T extends object ? {
80
- [K in keyof T]-?: [
81
- K
82
- ] | (Path<T[K]> extends infer P ? (P extends ReadonlyArray<PropertyKey> ? [K, ...P] : never) : never);
84
+ /**
85
+ * Every key path into `T`. Paths stop at built-in values (dates, maps, sets, functions), and index arrays by
86
+ * number, so `['tags', 0]` is a path and `['tags', 'map']` is not.
87
+ */
88
+ type Path<T> = T extends BuiltIn ? never : T extends ReadonlyArray<infer U> ? [number] | (Path<U> extends infer P ? (P extends ReadonlyArray<PropertyKey> ? [number, ...P] : never) : never) : T extends object ? {
89
+ [K in keyof T]-?: [K] | (Path<T[K]> extends infer P ? (P extends ReadonlyArray<PropertyKey> ? [K, ...P] : never) : never);
83
90
  }[keyof T] : never;
84
91
  type PathValue<T, P> = P extends readonly [infer K, ...infer R] ? K extends keyof T ? R["length"] extends 0 ? T[K] : PathValue<T[K], Extract<R, ReadonlyArray<PropertyKey>>> : never : never;
85
92
  type ArrayStrategy = {
@@ -92,18 +99,40 @@ type ArrayStrategy = {
92
99
  };
93
100
  interface DeepMergeOptions {
94
101
  arrayStrategy?: ArrayStrategy | ((key: PropertyKey, path: ReadonlyArray<PropertyKey>) => ArrayStrategy);
102
+ /**
103
+ * What an own `undefined` value in the patch does. `"skip"` (the default) keeps the base value;
104
+ * `"assign"` sets the field to `undefined`.
105
+ */
106
+ undefinedValues?: "assign" | "skip";
95
107
  }
96
108
  declare const isEmptyObject: (obj: Record<PropertyKey, unknown>) => boolean;
97
109
  declare const isPlainObject: (v: unknown) => v is Record<PropertyKey, unknown>;
110
+ /**
111
+ * Merges `patch` into a copy of `base`. Only plain objects (object literals, `JSON.parse` output) are merged
112
+ * key by key. Any other value in the patch, including a class instance, a `Date`, a `Map` or a `Set`, replaces
113
+ * the base value as a whole, so a partial patch for a class-instance field must carry the whole value.
114
+ * `__proto__` keys in the patch are ignored.
115
+ */
98
116
  declare function deepMerge<T>(base: T, patch: DeepPartial<T>, options?: DeepMergeOptions, path?: ReadonlyArray<PropertyKey>): T;
99
117
  declare function deepMerge<T>(base: DeepPartial<T>, patch: DeepPartial<T>, options?: DeepMergeOptions, path?: ReadonlyArray<PropertyKey>): DeepPartial<T>;
100
118
  declare function mergeInto<T>(base: T, patch: DeepPartial<T>, options?: DeepMergeOptions): T;
101
119
  declare function mergePatchInto<T>(base: DeepPartial<T>, patch: DeepPartial<T>, options?: DeepMergeOptions): DeepPartial<T>;
102
- declare function buildNested(keys: ReadonlyArray<PropertyKey>, leaf: unknown): Record<PropertyKey, unknown>;
103
- declare function patchAt<T, P extends Path<T>>(path: P, value: PathValue<T, P>): DeepPartial<T>;
120
+ /**
121
+ * Builds a patch that sets `leaf` at `keys`. When `current` (the value the patch will be merged into) holds an
122
+ * array along the path, the patch carries a copy of that array with the one element replaced, since arrays are
123
+ * replaced rather than merged. Without `current`, every level is an object.
124
+ */
125
+ declare function buildNested(keys: ReadonlyArray<PropertyKey>, leaf: unknown, current?: unknown): Record<PropertyKey, unknown>;
126
+ declare function patchAt<T, P extends Path<T>>(path: P, value: PathValue<T, P>, current?: T): DeepPartial<T>;
104
127
  declare function setAt<T, P extends Path<T>>(obj: T, path: P, value: PathValue<T, P>, options?: DeepMergeOptions): T;
105
128
  declare function prunePatch<T>(base: T, patch: DeepPartial<T>): DeepPartial<T> | undefined;
106
129
  declare function prunePatch<T>(base: DeepPartial<T>, patch: DeepPartial<T>): DeepPartial<T> | undefined;
130
+ /**
131
+ * Structural equality for plain objects, arrays, `Date`, `RegExp`, `Map` and `Set`. Primitives are equal when
132
+ * `===` says so, and `NaN` equals `NaN`. Dates are equal when they hold the same time (two invalid dates are
133
+ * equal). Map keys and set members are matched by `SameValueZero`, like `Map.prototype.has`. Cyclic values are
134
+ * supported.
135
+ */
107
136
  declare function deepEquals<T>(o1: T, o2: T): boolean;
108
137
 
109
138
  /**
@@ -142,18 +171,6 @@ interface IObjectPatchStore<T> {
142
171
  * pending changes (i.e. the draft is an empty object).
143
172
  */
144
173
  readonly changes: Signal<DeepPartial<T> | undefined>;
145
- /**
146
- * `true` when the current draft is structurally equal to the last
147
- * committed snapshot, i.e. no unsaved changes exist relative to the
148
- * last commit point.
149
- */
150
- readonly pristine: Signal<boolean>;
151
- /**
152
- * The live merged view of base + draft. Prefer `select()` over reading
153
- * this directly in computed signals when you only care about a specific
154
- * slice of the object, to avoid unnecessary downstream invalidations.
155
- */
156
- readonly view: Signal<T>;
157
174
  /**
158
175
  * Discard the current draft without affecting the committed snapshot.
159
176
  * The view reverts to the base value.
@@ -174,7 +191,8 @@ interface IObjectPatchStore<T> {
174
191
  * Deep-merge `patch` into the current draft. Only the fields present in
175
192
  * `patch` are affected; all other fields retain their current draft or
176
193
  * base values. Automatically prunes fields that match the base value so
177
- * the draft stays minimal.
194
+ * the draft stays minimal. A field set to `undefined` is cleared, unless
195
+ * `options.undefinedValues` is `"skip"`.
178
196
  */
179
197
  patch(patch: DeepPartial<T>, options?: DeepMergeOptions): void;
180
198
  /**
@@ -185,26 +203,17 @@ interface IObjectPatchStore<T> {
185
203
  * store.patchPath(['address', 'city'], 'London');
186
204
  */
187
205
  patchPath<P extends Path<T>>(path: P, value: PathValue<T, P>, options?: DeepMergeOptions): void;
206
+ /**
207
+ * `true` when the current draft is structurally equal to the last
208
+ * committed snapshot, i.e. no unsaved changes exist relative to the
209
+ * last commit point.
210
+ */
211
+ readonly pristine: Signal<boolean>;
188
212
  /**
189
213
  * Discard both the draft and the committed snapshot, fully resetting
190
214
  * the store to a pristine, empty state.
191
215
  */
192
216
  reset(): void;
193
- /**
194
- * Directly replace the draft with the given patch object, bypassing the
195
- * merge logic. Use with care — this does not prune against the base.
196
- * Prefer `patch()` for most use cases.
197
- */
198
- setDraft(patch: DeepPartial<T>): void;
199
- /**
200
- * Convenience wrapper over `patch` that sets a single path to a value.
201
- * Unlike the old implementation, this merges rather than replaces the
202
- * draft, so unrelated draft fields are preserved.
203
- *
204
- * @example
205
- * store.setPath(['address', 'city'], 'London');
206
- */
207
- setPath<P extends Path<T>>(path: P, value: PathValue<T, P>, options?: DeepMergeOptions): void;
208
217
  /**
209
218
  * Returns a `Signal` that emits only the slice of `view` selected by
210
219
  * `projector`. Uses `deepEquals` as the equality function, so downstream
@@ -222,6 +231,28 @@ interface IObjectPatchStore<T> {
222
231
  * readonly city = store.select(v => v.address.city);
223
232
  */
224
233
  select<R>(projector: (view: T) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
234
+ /**
235
+ * Directly replace the draft with the given patch object, bypassing the
236
+ * merge logic. Use with care — this does not prune against the base, so
237
+ * a field equal to its base value still counts in `changes` and
238
+ * `hasDraft()`, while `pristine` ignores it. Prefer `patch()` for most use
239
+ * cases.
240
+ */
241
+ setDraft(patch: DeepPartial<T>): void;
242
+ /**
243
+ * Same as `patchPath`: sets a single path to a value, merging into the
244
+ * draft, so unrelated draft fields are preserved.
245
+ *
246
+ * @example
247
+ * store.setPath(['address', 'city'], 'London');
248
+ */
249
+ setPath<P extends Path<T>>(path: P, value: PathValue<T, P>, options?: DeepMergeOptions): void;
250
+ /**
251
+ * The live merged view of base + draft. Prefer `select()` over reading
252
+ * this directly in computed signals when you only care about a specific
253
+ * slice of the object, to avoid unnecessary downstream invalidations.
254
+ */
255
+ readonly view: Signal<T>;
225
256
  }
226
257
  declare class ObjectPatchStore<T> implements IObjectPatchStore<T> {
227
258
  #private;
@@ -236,11 +267,16 @@ declare class ObjectPatchStore<T> implements IObjectPatchStore<T> {
236
267
  patch(patch: DeepPartial<T>, options?: DeepMergeOptions): void;
237
268
  patchPath<P extends Path<T>>(path: P, value: PathValue<T, P>, options?: DeepMergeOptions): void;
238
269
  reset(): void;
270
+ select<R>(projector: (view: T) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
239
271
  setDraft(patch: DeepPartial<T>): void;
240
272
  setPath<P extends Path<T>>(path: P, value: PathValue<T, P>, options?: DeepMergeOptions): void;
241
- select<R>(projector: (view: T) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
242
273
  }
243
274
  interface IterablePatchStoreOptions<TItem, TId> {
275
+ /**
276
+ * Controls what happens to uncommitted drafts when the base signal emits
277
+ * a new value. Defaults to `'wipe'`.
278
+ */
279
+ baseChangeStrategy?: BaseChangeStrategy;
244
280
  /** Extracts the stable identity key from an item. */
245
281
  idOf: (item: TItem) => TId;
246
282
  /**
@@ -248,13 +284,10 @@ interface IterablePatchStoreOptions<TItem, TId> {
248
284
  * per-row edits). Accepts either a `Map` or an `ImmutableDictionary`.
249
285
  */
250
286
  initial?: () => Map<TId, DeepPartial<TItem>> | ImmutableDictionary<TId, DeepPartial<TItem>>;
287
+ /** Default merge options applied to all patch operations unless overridden per-call. */
288
+ mergeOptions?: DeepMergeOptions;
251
289
  /** Factory that returns the authoritative base signal for this store. */
252
290
  source: () => Signal<Iterable<TItem>>;
253
- /**
254
- * Controls what happens to uncommitted drafts when the base signal emits
255
- * a new value. Defaults to `'wipe'`.
256
- */
257
- baseChangeStrategy?: BaseChangeStrategy;
258
291
  }
259
292
  interface IIterablePatchStore<TItem, TId> {
260
293
  /**
@@ -262,18 +295,6 @@ interface IIterablePatchStore<TItem, TId> {
262
295
  * with its current draft. Items without a draft are excluded.
263
296
  */
264
297
  readonly changes: Signal<TItem[]>;
265
- /**
266
- * `true` when every item's current draft is structurally equal to its
267
- * last committed snapshot.
268
- */
269
- readonly pristine: Signal<boolean>;
270
- /**
271
- * The live merged view of all base items with their drafts applied.
272
- * Items without a draft are returned as-is (same reference).
273
- * Prefer `select()` over reading this directly in computed signals when
274
- * you only care about a specific slice.
275
- */
276
- readonly view: Signal<TItem[]>;
277
298
  /**
278
299
  * Discard the draft for the item identified by `id`.
279
300
  * Has no effect if the item has no draft.
@@ -298,6 +319,8 @@ interface IIterablePatchStore<TItem, TId> {
298
319
  * Deep-merge `patch` into the draft for the item identified by `id`.
299
320
  * Automatically prunes fields that now match the base item so the draft
300
321
  * stays minimal. Removes the draft entry entirely if it becomes empty.
322
+ * A field set to `undefined` is cleared, unless `options.undefinedValues`
323
+ * is `"skip"`.
301
324
  */
302
325
  patch(id: TId, patch: DeepPartial<TItem>, options?: DeepMergeOptions): void;
303
326
  /**
@@ -314,26 +337,16 @@ interface IIterablePatchStore<TItem, TId> {
314
337
  * store.patchPath(itemId, ['address', 'city'], 'London');
315
338
  */
316
339
  patchPath<P extends Path<TItem>>(id: TId, path: P, value: PathValue<TItem, P>, options?: DeepMergeOptions): void;
340
+ /**
341
+ * `true` when every item's current draft is structurally equal to its
342
+ * last committed snapshot.
343
+ */
344
+ readonly pristine: Signal<boolean>;
317
345
  /**
318
346
  * Discard all drafts and committed snapshots, fully resetting the store
319
347
  * to a pristine, empty state.
320
348
  */
321
349
  reset(): void;
322
- /**
323
- * Directly replace the draft for `id` with the given patch object,
324
- * bypassing the merge logic. Use with care — this does not prune against
325
- * the base item. Prefer `patch()` for most use cases.
326
- */
327
- setDraft(id: TId, patch: DeepPartial<TItem>): void;
328
- /**
329
- * Convenience wrapper over `patch` that sets a single path to a value
330
- * for a specific item. Merges into the existing draft rather than
331
- * replacing it, so unrelated draft fields are preserved.
332
- *
333
- * @example
334
- * store.setPath(itemId, ['address', 'city'], 'London');
335
- */
336
- setPath<P extends Path<TItem>>(id: TId, path: P, value: PathValue<TItem, P>, options?: DeepMergeOptions): void;
337
350
  /**
338
351
  * Returns a `Signal` that emits only the slice of `view` selected by
339
352
  * `projector`. Uses `deepEquals` as the equality function by default,
@@ -347,6 +360,30 @@ interface IIterablePatchStore<TItem, TId> {
347
360
  * readonly cities = store.select(items => items.map(i => i.address.city));
348
361
  */
349
362
  select<R>(projector: (view: TItem[]) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
363
+ /**
364
+ * Directly replace the draft for `id` with the given patch object,
365
+ * bypassing the merge logic. An empty patch removes the draft. Use with
366
+ * care — this does not prune against the base item, so a field equal to
367
+ * its base value still counts in `changes`, `hasDraft()` and
368
+ * `dirtyCount()`, while `pristine` ignores it. Prefer `patch()` for most
369
+ * use cases.
370
+ */
371
+ setDraft(id: TId, patch: DeepPartial<TItem>): void;
372
+ /**
373
+ * Same as `patchPath`: sets a single path to a value for a specific item,
374
+ * merging into the existing draft, so unrelated draft fields are preserved.
375
+ *
376
+ * @example
377
+ * store.setPath(itemId, ['address', 'city'], 'London');
378
+ */
379
+ setPath<P extends Path<TItem>>(id: TId, path: P, value: PathValue<TItem, P>, options?: DeepMergeOptions): void;
380
+ /**
381
+ * The live merged view of all base items with their drafts applied.
382
+ * Items without a draft are returned as-is (same reference).
383
+ * Prefer `select()` over reading this directly in computed signals when
384
+ * you only care about a specific slice.
385
+ */
386
+ readonly view: Signal<TItem[]>;
350
387
  }
351
388
  declare class IterablePatchStore<TItem, TId> implements IIterablePatchStore<TItem, TId> {
352
389
  #private;
@@ -360,13 +397,13 @@ declare class IterablePatchStore<TItem, TId> implements IIterablePatchStore<TIte
360
397
  dirtyCount(): number;
361
398
  getCommitted(): ImmutableDictionary<TId, DeepPartial<TItem>>;
362
399
  hasDraft(id: TId): boolean;
363
- patch(id: TId, patch: DeepPartial<TItem>, options?: DeepMergeOptions): void;
364
- patchMany(patchTuple: Iterable<[TId, DeepPartial<TItem>]>, options?: DeepMergeOptions): void;
400
+ patch(id: TId, patch: DeepPartial<TItem>, options?: DeepMergeOptions | undefined): void;
401
+ patchMany(patchTuple: Iterable<[TId, DeepPartial<TItem>]>, options?: DeepMergeOptions | undefined): void;
365
402
  patchPath<P extends Path<TItem>>(id: TId, path: P, value: PathValue<TItem, P>, options?: DeepMergeOptions): void;
366
403
  reset(): void;
404
+ select<R>(projector: (view: TItem[]) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
367
405
  setDraft(id: TId, patch: DeepPartial<TItem>): void;
368
406
  setPath<P extends Path<TItem>>(id: TId, path: P, value: PathValue<TItem, P>, options?: DeepMergeOptions): void;
369
- select<R>(projector: (view: TItem[]) => R, equal?: (a: R, b: R) => boolean): Signal<R>;
370
407
  }
371
408
  declare const createIterablePatchStore: <TItem, TId>(options: IterablePatchStoreOptions<TItem, TId>) => IIterablePatchStore<TItem, TId>;
372
409
  declare const createObjectPatchStore: <T>(options: ObjectPatchStoreOptions<T>) => IObjectPatchStore<T>;
@@ -378,12 +415,18 @@ declare const classInputToClass: (input: ClassInputType) => string;
378
415
  * @param value The current value.
379
416
  * @param min The minimum value.
380
417
  * @param max The maximum value.
381
- * @param precision The number of decimal places to round the percentage to.
418
+ * @param precision The number of decimal places to round the percentage to, clamped to 0–100.
382
419
  * @default 2
383
- * @returns The percentage of the value within the range, rounded to the specified precision.
420
+ * @returns The percentage of the value within the range, rounded to the specified precision, or 0 when the range
421
+ * or the value is not finite.
384
422
  */
385
423
  declare const getPercentage: (value: number, min: number, max: number, precision?: number) => number;
386
424
 
425
+ /**
426
+ * Moves the items at `indices` by `offset` positions, keeping their relative order. Returns a copy of the array
427
+ * unchanged when the offset is 0 or not an integer, or when any index is not an integer, is out of bounds, or would
428
+ * move out of bounds. Duplicate indices count once.
429
+ */
387
430
  declare function moveIndices<T>(array: T[], indices: number[], offset: number): T[];
388
431
 
389
432
  export { AnyPipe, ContainsPipe, FilterChangeEvent, IterablePatchStore, ObjectPatchStore, PreventableEvent, SlicePipe, TakeFirstPipe, buildNested, classInputToClass, createIterablePatchStore, createObjectPatchStore, deepEquals, deepMerge, getPercentage, isEmptyObject, isPlainObject, mergeInto, mergePatchInto, moveIndices, patchAt, prunePatch, setAt };