dalila 1.9.21 → 1.9.22

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 (67) hide show
  1. package/README.md +3 -5
  2. package/dist/components/ui/ui-types.d.ts +8 -8
  3. package/dist/core/signal.d.ts +9 -1
  4. package/dist/core/signal.js +63 -87
  5. package/dist/form/field-array.d.ts +12 -0
  6. package/dist/form/field-array.js +306 -0
  7. package/dist/form/form-dom.d.ts +6 -0
  8. package/dist/form/form-dom.js +52 -0
  9. package/dist/form/form-types.d.ts +24 -15
  10. package/dist/form/form.d.ts +7 -53
  11. package/dist/form/form.js +49 -932
  12. package/dist/form/index.d.ts +2 -2
  13. package/dist/form/index.js +1 -2
  14. package/dist/form/parse-form-data.d.ts +13 -0
  15. package/dist/form/parse-form-data.js +88 -0
  16. package/dist/form/path-utils.d.ts +24 -0
  17. package/dist/form/path-utils.js +91 -0
  18. package/dist/form/path-watchers.d.ts +23 -0
  19. package/dist/form/path-watchers.js +82 -0
  20. package/dist/form/validation-pipeline.d.ts +29 -0
  21. package/dist/form/validation-pipeline.js +223 -0
  22. package/dist/router/index.d.ts +1 -1
  23. package/dist/router/index.js +1 -1
  24. package/dist/router/location-utils.d.ts +19 -0
  25. package/dist/router/location-utils.js +60 -0
  26. package/dist/router/lru-cache.d.ts +17 -0
  27. package/dist/router/lru-cache.js +63 -0
  28. package/dist/router/preload-metadata.d.ts +26 -0
  29. package/dist/router/preload-metadata.js +65 -0
  30. package/dist/router/router-lifecycle.d.ts +12 -0
  31. package/dist/router/router-lifecycle.js +31 -0
  32. package/dist/router/router-mount-lifecycle.d.ts +11 -0
  33. package/dist/router/router-mount-lifecycle.js +50 -0
  34. package/dist/router/router-prefetch.d.ts +22 -0
  35. package/dist/router/router-prefetch.js +86 -0
  36. package/dist/router/router-preload-cache.d.ts +25 -0
  37. package/dist/router/router-preload-cache.js +68 -0
  38. package/dist/router/router-render-utils.d.ts +14 -0
  39. package/dist/router/router-render-utils.js +11 -0
  40. package/dist/router/router-validation.d.ts +4 -0
  41. package/dist/router/router-validation.js +100 -0
  42. package/dist/router/router-view-composer.d.ts +16 -0
  43. package/dist/router/router-view-composer.js +41 -0
  44. package/dist/router/router.d.ts +12 -3
  45. package/dist/router/router.js +241 -621
  46. package/dist/runtime/array-directive-dom.d.ts +4 -0
  47. package/dist/runtime/array-directive-dom.js +30 -0
  48. package/dist/runtime/bind.js +112 -834
  49. package/dist/runtime/internal/components/component-props.d.ts +15 -0
  50. package/dist/runtime/internal/components/component-props.js +69 -0
  51. package/dist/runtime/internal/components/component-slots.d.ts +10 -0
  52. package/dist/runtime/internal/components/component-slots.js +68 -0
  53. package/dist/runtime/internal/list/list-clone-factory.d.ts +17 -0
  54. package/dist/runtime/internal/list/list-clone-factory.js +35 -0
  55. package/dist/runtime/internal/list/list-clone-registry.d.ts +12 -0
  56. package/dist/runtime/internal/list/list-clone-registry.js +43 -0
  57. package/dist/runtime/internal/list/list-keying.d.ts +13 -0
  58. package/dist/runtime/internal/list/list-keying.js +65 -0
  59. package/dist/runtime/internal/list/list-metadata.d.ts +11 -0
  60. package/dist/runtime/internal/list/list-metadata.js +21 -0
  61. package/dist/runtime/internal/list/list-reconcile.d.ts +3 -0
  62. package/dist/runtime/internal/list/list-reconcile.js +25 -0
  63. package/dist/runtime/internal/list/list-scheduler.d.ts +16 -0
  64. package/dist/runtime/internal/list/list-scheduler.js +45 -0
  65. package/dist/runtime/internal/virtual/virtual-list-helpers.d.ts +48 -0
  66. package/dist/runtime/internal/virtual/virtual-list-helpers.js +291 -0
  67. package/package.json +6 -5
package/README.md CHANGED
@@ -58,7 +58,9 @@ bind(document.getElementById('app')!, ctx);
58
58
  ### Core
59
59
 
60
60
  - [Signals](./docs/core/signals.md)
61
+ - [Effects Guide](./docs/core/effects-guide.md)
61
62
  - [Scopes](./docs/core/scope.md)
63
+ - [Scopes Guide](./docs/core/scopes-guide.md)
62
64
  - [Persist](./docs/core/persist.md)
63
65
  - [Context](./docs/context.md)
64
66
  - [Scheduler](./docs/core/scheduler.md)
@@ -88,11 +90,6 @@ bind(document.getElementById('app')!, ctx);
88
90
  - [Template Check CLI](./docs/cli/check.md)
89
91
  - [Devtools Extension](./devtools-extension/README.md)
90
92
 
91
- Firefox extension workflows:
92
-
93
- - `npm run devtools:firefox:run` — launch Firefox with extension loaded for dev
94
- - `npm run devtools:firefox:build` — package extension artifact for submission/signing
95
-
96
93
  ## Packages
97
94
 
98
95
  ```txt
@@ -110,6 +107,7 @@ npm install
110
107
  npm run build
111
108
  npm run serve # Dev server with HMR
112
109
  npm test
110
+ npm run test:watch
113
111
  ```
114
112
 
115
113
  ## License
@@ -1,4 +1,4 @@
1
- import type { Signal } from "../../core/signal.js";
1
+ import type { ComputedSignal, ReadonlySignal, Signal } from "../../core/signal.js";
2
2
  export interface DialogOptions {
3
3
  closeOnBackdrop?: boolean;
4
4
  closeOnEscape?: boolean;
@@ -56,9 +56,9 @@ export interface Tabs {
56
56
  _attachTo(el: HTMLElement): void;
57
57
  }
58
58
  export interface TabBindings {
59
- tabClass: Signal<string>;
60
- selected: Signal<string>;
61
- visible: Signal<boolean>;
59
+ tabClass: ComputedSignal<string>;
60
+ selected: ComputedSignal<string>;
61
+ visible: ComputedSignal<boolean>;
62
62
  }
63
63
  export interface DropdownOptions {
64
64
  closeOnSelect?: boolean;
@@ -84,7 +84,7 @@ export interface Combobox {
84
84
  query: Signal<string>;
85
85
  value: Signal<string>;
86
86
  label: Signal<string>;
87
- filtered: Signal<ComboboxOption[]>;
87
+ filtered: ReadonlySignal<ComboboxOption[]>;
88
88
  highlightedIndex: Signal<number>;
89
89
  show(): void;
90
90
  close(): void;
@@ -103,7 +103,7 @@ export interface Accordion {
103
103
  toggle(itemId: string): void;
104
104
  open(itemId: string): void;
105
105
  close(itemId: string): void;
106
- isOpen(itemId: string): Signal<boolean>;
106
+ isOpen(itemId: string): ReadonlySignal<boolean>;
107
107
  _attachTo(el: HTMLElement): void;
108
108
  }
109
109
  export interface CalendarDay {
@@ -125,8 +125,8 @@ export interface Calendar {
125
125
  year: Signal<number>;
126
126
  month: Signal<number>;
127
127
  selected: Signal<Date | null>;
128
- title: Signal<string>;
129
- days: Signal<CalendarDay[]>;
128
+ title: ReadonlySignal<string>;
129
+ days: ReadonlySignal<CalendarDay[]>;
130
130
  dayLabels: string[];
131
131
  prev(): void;
132
132
  next(): void;
@@ -24,6 +24,10 @@ export interface ReadonlySignal<T> {
24
24
  /** Subscribe to value changes manually (outside of effects). Returns unsubscribe function. */
25
25
  on(callback: (value: T) => void): () => void;
26
26
  }
27
+ export interface ComputedSignal<T> extends ReadonlySignal<T> {
28
+ /** Nominal marker to improve IntelliSense/readability in docs and types. */
29
+ readonly __dalilaComputed?: true;
30
+ }
27
31
  export interface DebounceSignalOptions {
28
32
  /** Emit immediately on the first update in a burst. Default: false */
29
33
  leading?: boolean;
@@ -107,7 +111,11 @@ export declare function effect(fn: () => void): () => void;
107
111
  * Subscription:
108
112
  * - other effects can subscribe to the computed like a normal signal
109
113
  */
110
- export declare function computed<T>(fn: () => T): Signal<T>;
114
+ export declare function computed<T>(fn: () => T): ComputedSignal<T>;
115
+ /** Short alias for `debounceSignal()`. */
116
+ export declare function debounce<T>(source: ReadonlySignal<T>, waitMs: number, options?: DebounceSignalOptions): ReadonlySignal<T>;
117
+ /** Short alias for `throttleSignal()`. */
118
+ export declare function throttle<T>(source: ReadonlySignal<T>, waitMs: number, options?: ThrottleSignalOptions): ReadonlySignal<T>;
111
119
  /**
112
120
  * Async effect with cancellation.
113
121
  *
@@ -100,6 +100,31 @@ function trySubscribeActiveEffect(subscribers, signalRef) {
100
100
  (activeEffect.deps ?? (activeEffect.deps = new Set())).add(subscribers);
101
101
  trackDependency(signalRef, activeEffect);
102
102
  }
103
+ function cleanupEffectDeps(run) {
104
+ if (!run.deps)
105
+ return;
106
+ for (const depSet of run.deps) {
107
+ depSet.delete(run);
108
+ untrackDependencyBySet(depSet, run);
109
+ }
110
+ run.deps.clear();
111
+ }
112
+ function runTrackedEffectBody(run, owningScope, exec) {
113
+ const prevEffect = activeEffect;
114
+ const prevScope = activeScope;
115
+ activeEffect = run;
116
+ activeScope = owningScope ?? null;
117
+ try {
118
+ if (owningScope)
119
+ withScope(owningScope, exec);
120
+ else
121
+ exec();
122
+ }
123
+ finally {
124
+ activeEffect = prevEffect;
125
+ activeScope = prevScope;
126
+ }
127
+ }
103
128
  /**
104
129
  * Create a signal: a mutable value with automatic dependency tracking.
105
130
  *
@@ -348,20 +373,11 @@ export function throttleSignal(source, waitMs, options = {}) {
348
373
  */
349
374
  export function effect(fn) {
350
375
  const owningScope = getCurrentScope();
351
- const cleanupDeps = () => {
352
- if (!run.deps)
353
- return;
354
- for (const depSet of run.deps) {
355
- depSet.delete(run);
356
- untrackDependencyBySet(depSet, run);
357
- }
358
- run.deps.clear();
359
- };
360
376
  const dispose = () => {
361
377
  if (run.disposed)
362
378
  return;
363
379
  run.disposed = true;
364
- cleanupDeps();
380
+ cleanupEffectDeps(run);
365
381
  run.queued = false;
366
382
  trackEffectDispose(run);
367
383
  };
@@ -370,22 +386,12 @@ export function effect(fn) {
370
386
  return;
371
387
  trackEffectRunStart(run);
372
388
  // Dynamic deps: unsubscribe from previous reads.
373
- cleanupDeps();
374
- const prevEffect = activeEffect;
375
- const prevScope = activeScope;
376
- activeEffect = run;
377
- activeScope = owningScope ?? null;
389
+ cleanupEffectDeps(run);
378
390
  try {
379
- // Run inside owning scope so resources created by the effect are scoped.
380
- if (owningScope)
381
- withScope(owningScope, fn);
382
- else
383
- fn();
391
+ runTrackedEffectBody(run, owningScope ?? null, fn);
384
392
  }
385
393
  finally {
386
394
  trackEffectRunEnd(run);
387
- activeEffect = prevEffect;
388
- activeScope = prevScope;
389
395
  }
390
396
  });
391
397
  registerEffect(run, 'effect', owningScope);
@@ -439,34 +445,33 @@ export function computed(fn) {
439
445
  if (markDirty.deps)
440
446
  markDirty.deps.clear();
441
447
  };
448
+ const recomputeIfDirty = () => {
449
+ if (!dirty)
450
+ return;
451
+ trackComputedRunStart(signalRef);
452
+ cleanupDeps();
453
+ const prevEffect = activeEffect;
454
+ const prevScope = activeScope;
455
+ // Collect deps into markDirty.
456
+ activeEffect = markDirty;
457
+ activeScope = null;
458
+ try {
459
+ value = fn();
460
+ dirty = false;
461
+ if (markDirty.deps)
462
+ trackedDeps = new Set(markDirty.deps);
463
+ }
464
+ finally {
465
+ trackComputedRunEnd(signalRef);
466
+ activeEffect = prevEffect;
467
+ activeScope = prevScope;
468
+ }
469
+ };
442
470
  const read = () => {
443
471
  trackSignalRead(signalRef);
444
472
  // Allow effects to subscribe to this computed (same rules as signal()).
445
473
  trySubscribeActiveEffect(subscribers, signalRef);
446
- if (dirty) {
447
- trackComputedRunStart(signalRef);
448
- cleanupDeps();
449
- const prevEffect = activeEffect;
450
- const prevScope = activeScope;
451
- // Collect deps into markDirty.
452
- activeEffect = markDirty;
453
- // During dependency collection for computed:
454
- // - we want to subscribe to its dependencies regardless of the caller scope.
455
- // - the computed's deps belong to the computed itself, not to whoever read it.
456
- activeScope = null;
457
- try {
458
- value = fn();
459
- dirty = false;
460
- // Snapshot the current dep sets for later unsubscription.
461
- if (markDirty.deps)
462
- trackedDeps = new Set(markDirty.deps);
463
- }
464
- finally {
465
- trackComputedRunEnd(signalRef);
466
- activeEffect = prevEffect;
467
- activeScope = prevScope;
468
- }
469
- }
474
+ recomputeIfDirty();
470
475
  return value;
471
476
  };
472
477
  read.set = () => {
@@ -477,25 +482,7 @@ export function computed(fn) {
477
482
  };
478
483
  read.peek = () => {
479
484
  // For computed, peek still needs to compute if dirty, but without tracking
480
- if (dirty) {
481
- trackComputedRunStart(signalRef);
482
- cleanupDeps();
483
- const prevEffect = activeEffect;
484
- const prevScope = activeScope;
485
- activeEffect = markDirty;
486
- activeScope = null;
487
- try {
488
- value = fn();
489
- dirty = false;
490
- if (markDirty.deps)
491
- trackedDeps = new Set(markDirty.deps);
492
- }
493
- finally {
494
- trackComputedRunEnd(signalRef);
495
- activeEffect = prevEffect;
496
- activeScope = prevScope;
497
- }
498
- }
485
+ recomputeIfDirty();
499
486
  return value;
500
487
  };
501
488
  read.on = (callback) => {
@@ -525,6 +512,14 @@ export function computed(fn) {
525
512
  registerEffect(markDirty, 'effect', owningScope);
526
513
  return read;
527
514
  }
515
+ /** Short alias for `debounceSignal()`. */
516
+ export function debounce(source, waitMs, options = {}) {
517
+ return debounceSignal(source, waitMs, options);
518
+ }
519
+ /** Short alias for `throttleSignal()`. */
520
+ export function throttle(source, waitMs, options = {}) {
521
+ return throttleSignal(source, waitMs, options);
522
+ }
528
523
  /**
529
524
  * Async effect with cancellation.
530
525
  *
@@ -536,22 +531,13 @@ export function computed(fn) {
536
531
  export function effectAsync(fn) {
537
532
  const owningScope = getCurrentScope();
538
533
  let controller = null;
539
- const cleanupDeps = () => {
540
- if (!run.deps)
541
- return;
542
- for (const depSet of run.deps) {
543
- depSet.delete(run);
544
- untrackDependencyBySet(depSet, run);
545
- }
546
- run.deps.clear();
547
- };
548
534
  const dispose = () => {
549
535
  if (run.disposed)
550
536
  return;
551
537
  run.disposed = true;
552
538
  controller?.abort();
553
539
  controller = null;
554
- cleanupDeps();
540
+ cleanupEffectDeps(run);
555
541
  run.queued = false;
556
542
  trackEffectDispose(run);
557
543
  };
@@ -562,22 +548,12 @@ export function effectAsync(fn) {
562
548
  // Abort previous run (if any), then create a new controller for this run.
563
549
  controller?.abort();
564
550
  controller = new AbortController();
565
- cleanupDeps();
566
- const prevEffect = activeEffect;
567
- const prevScope = activeScope;
568
- activeEffect = run;
569
- activeScope = owningScope ?? null;
551
+ cleanupEffectDeps(run);
570
552
  try {
571
- const exec = () => fn(controller.signal);
572
- if (owningScope)
573
- withScope(owningScope, exec);
574
- else
575
- exec();
553
+ runTrackedEffectBody(run, owningScope ?? null, () => fn(controller.signal));
576
554
  }
577
555
  finally {
578
556
  trackEffectRunEnd(run);
579
- activeEffect = prevEffect;
580
- activeScope = prevScope;
581
557
  }
582
558
  });
583
559
  registerEffect(run, 'effectAsync', owningScope);
@@ -0,0 +1,12 @@
1
+ import { type Signal } from '../core/signal.js';
2
+ import type { Scope } from '../core/scope.js';
3
+ import type { FieldArray, FieldErrors } from './form-types.js';
4
+ export interface FieldArrayOptions {
5
+ form: HTMLFormElement | null;
6
+ scope: Scope | null;
7
+ onMutate?: () => void;
8
+ errors?: Signal<FieldErrors>;
9
+ touchedSet?: Signal<Set<string>>;
10
+ dirtySet?: Signal<Set<string>>;
11
+ }
12
+ export declare function createFieldArray<TItem = unknown>(basePath: string, options: FieldArrayOptions): FieldArray<TItem>;
@@ -0,0 +1,306 @@
1
+ import { signal } from '../core/signal.js';
2
+ export function createFieldArray(basePath, options) {
3
+ const keys = signal([]);
4
+ const values = signal(new Map());
5
+ let keyCounter = 0;
6
+ function generateKey() {
7
+ return `${basePath}_${keyCounter++}`;
8
+ }
9
+ function remapMetaState(oldIndices, newIndices) {
10
+ if (!options.errors && !options.touchedSet && !options.dirtySet)
11
+ return;
12
+ const indexMap = new Map();
13
+ for (let i = 0; i < oldIndices.length; i++) {
14
+ indexMap.set(oldIndices[i], newIndices[i]);
15
+ }
16
+ if (options.errors) {
17
+ options.errors.update((prev) => {
18
+ const next = {};
19
+ for (const [path, message] of Object.entries(prev)) {
20
+ const newPath = remapPath(path, indexMap);
21
+ next[newPath] = message;
22
+ }
23
+ return next;
24
+ });
25
+ }
26
+ if (options.touchedSet) {
27
+ options.touchedSet.update((prev) => {
28
+ const next = new Set();
29
+ for (const path of prev) {
30
+ next.add(remapPath(path, indexMap));
31
+ }
32
+ return next;
33
+ });
34
+ }
35
+ if (options.dirtySet) {
36
+ options.dirtySet.update((prev) => {
37
+ const next = new Set();
38
+ for (const path of prev) {
39
+ next.add(remapPath(path, indexMap));
40
+ }
41
+ return next;
42
+ });
43
+ }
44
+ }
45
+ function remapPath(path, indexMap) {
46
+ const regex = new RegExp(`^${escapeRegExp(basePath)}\\[(\\d+)\\](.*)$`);
47
+ const match = path.match(regex);
48
+ if (!match)
49
+ return path;
50
+ const oldIndex = parseInt(match[1], 10);
51
+ const rest = match[2];
52
+ const newIndex = indexMap.get(oldIndex);
53
+ if (newIndex === undefined)
54
+ return path;
55
+ return `${basePath}[${newIndex}]${rest}`;
56
+ }
57
+ function escapeRegExp(string) {
58
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
59
+ }
60
+ function fields() {
61
+ const currentKeys = keys();
62
+ const currentValues = values();
63
+ return currentKeys.map((key) => ({
64
+ key,
65
+ value: currentValues.get(key),
66
+ }));
67
+ }
68
+ function length() {
69
+ return keys().length;
70
+ }
71
+ function _getIndex(key) {
72
+ return keys().indexOf(key);
73
+ }
74
+ function _translatePath(path) {
75
+ const match = path.match(/^([^\[]+)\[(\d+)\](.*)$/);
76
+ if (!match)
77
+ return null;
78
+ const [, arrayPath, indexStr, rest] = match;
79
+ if (arrayPath !== basePath)
80
+ return null;
81
+ const index = parseInt(indexStr, 10);
82
+ const currentKeys = keys();
83
+ const key = currentKeys[index];
84
+ if (!key)
85
+ return null;
86
+ return `${arrayPath}:${key}${rest}`;
87
+ }
88
+ function append(value) {
89
+ const items = Array.isArray(value) ? value : [value];
90
+ const newKeys = items.map(() => generateKey());
91
+ keys.update((prev) => [...prev, ...newKeys]);
92
+ values.update((prev) => {
93
+ const next = new Map(prev);
94
+ newKeys.forEach((key, i) => next.set(key, items[i]));
95
+ return next;
96
+ });
97
+ options.onMutate?.();
98
+ }
99
+ function remove(key) {
100
+ const removeIndex = _getIndex(key);
101
+ const currentLength = keys().length;
102
+ keys.update((prev) => prev.filter((k) => k !== key));
103
+ values.update((prev) => {
104
+ const next = new Map(prev);
105
+ next.delete(key);
106
+ return next;
107
+ });
108
+ if (removeIndex >= 0) {
109
+ const prefix = `${basePath}[${removeIndex}]`;
110
+ if (options.errors) {
111
+ options.errors.update((prev) => {
112
+ const next = {};
113
+ for (const [path, message] of Object.entries(prev)) {
114
+ if (!path.startsWith(prefix))
115
+ next[path] = message;
116
+ }
117
+ return next;
118
+ });
119
+ }
120
+ if (options.touchedSet) {
121
+ options.touchedSet.update((prev) => {
122
+ const next = new Set();
123
+ for (const path of prev) {
124
+ if (!path.startsWith(prefix))
125
+ next.add(path);
126
+ }
127
+ return next;
128
+ });
129
+ }
130
+ if (options.dirtySet) {
131
+ options.dirtySet.update((prev) => {
132
+ const next = new Set();
133
+ for (const path of prev) {
134
+ if (!path.startsWith(prefix))
135
+ next.add(path);
136
+ }
137
+ return next;
138
+ });
139
+ }
140
+ const oldIndices = [];
141
+ const newIndices = [];
142
+ for (let i = removeIndex + 1; i < currentLength; i++) {
143
+ oldIndices.push(i);
144
+ newIndices.push(i - 1);
145
+ }
146
+ if (oldIndices.length > 0)
147
+ remapMetaState(oldIndices, newIndices);
148
+ }
149
+ options.onMutate?.();
150
+ }
151
+ function removeAt(index) {
152
+ if (index < 0 || index >= keys().length)
153
+ return;
154
+ const key = keys()[index];
155
+ if (key)
156
+ remove(key);
157
+ }
158
+ function insert(index, value) {
159
+ const len = keys().length;
160
+ if (index < 0 || index > len)
161
+ return;
162
+ const key = generateKey();
163
+ const currentLength = len;
164
+ const oldIndices = [];
165
+ const newIndices = [];
166
+ for (let i = index; i < currentLength; i++) {
167
+ oldIndices.push(i);
168
+ newIndices.push(i + 1);
169
+ }
170
+ if (oldIndices.length > 0)
171
+ remapMetaState(oldIndices, newIndices);
172
+ keys.update((prev) => {
173
+ const next = [...prev];
174
+ next.splice(index, 0, key);
175
+ return next;
176
+ });
177
+ values.update((prev) => {
178
+ const next = new Map(prev);
179
+ next.set(key, value);
180
+ return next;
181
+ });
182
+ options.onMutate?.();
183
+ }
184
+ function move(fromIndex, toIndex) {
185
+ const len = keys().length;
186
+ if (fromIndex < 0 || fromIndex >= len || toIndex < 0 || toIndex >= len)
187
+ return;
188
+ if (fromIndex === toIndex)
189
+ return;
190
+ const oldIndices = [];
191
+ const newIndices = [];
192
+ if (fromIndex < toIndex) {
193
+ oldIndices.push(fromIndex);
194
+ newIndices.push(toIndex);
195
+ for (let i = fromIndex + 1; i <= toIndex; i++) {
196
+ oldIndices.push(i);
197
+ newIndices.push(i - 1);
198
+ }
199
+ }
200
+ else {
201
+ oldIndices.push(fromIndex);
202
+ newIndices.push(toIndex);
203
+ for (let i = toIndex; i < fromIndex; i++) {
204
+ oldIndices.push(i);
205
+ newIndices.push(i + 1);
206
+ }
207
+ }
208
+ remapMetaState(oldIndices, newIndices);
209
+ keys.update((prev) => {
210
+ const next = [...prev];
211
+ const [item] = next.splice(fromIndex, 1);
212
+ next.splice(toIndex, 0, item);
213
+ return next;
214
+ });
215
+ options.onMutate?.();
216
+ }
217
+ function swap(indexA, indexB) {
218
+ const len = keys().length;
219
+ if (indexA < 0 || indexA >= len || indexB < 0 || indexB >= len)
220
+ return;
221
+ if (indexA === indexB)
222
+ return;
223
+ remapMetaState([indexA, indexB], [indexB, indexA]);
224
+ keys.update((prev) => {
225
+ const next = [...prev];
226
+ [next[indexA], next[indexB]] = [next[indexB], next[indexA]];
227
+ return next;
228
+ });
229
+ options.onMutate?.();
230
+ }
231
+ function replace(newValues) {
232
+ const newKeys = newValues.map(() => generateKey());
233
+ if (options.errors) {
234
+ options.errors.update((prev) => {
235
+ const next = {};
236
+ for (const [path, message] of Object.entries(prev)) {
237
+ if (!path.startsWith(`${basePath}[`))
238
+ next[path] = message;
239
+ }
240
+ return next;
241
+ });
242
+ }
243
+ if (options.touchedSet) {
244
+ options.touchedSet.update((prev) => {
245
+ const next = new Set();
246
+ for (const path of prev) {
247
+ if (!path.startsWith(`${basePath}[`))
248
+ next.add(path);
249
+ }
250
+ return next;
251
+ });
252
+ }
253
+ if (options.dirtySet) {
254
+ options.dirtySet.update((prev) => {
255
+ const next = new Set();
256
+ for (const path of prev) {
257
+ if (!path.startsWith(`${basePath}[`))
258
+ next.add(path);
259
+ }
260
+ return next;
261
+ });
262
+ }
263
+ keys.set(newKeys);
264
+ values.set(new Map(newKeys.map((key, i) => [key, newValues[i]])));
265
+ options.onMutate?.();
266
+ }
267
+ function update(key, value) {
268
+ values.update((prev) => {
269
+ const next = new Map(prev);
270
+ next.set(key, value);
271
+ return next;
272
+ });
273
+ options.onMutate?.();
274
+ }
275
+ function updateAt(index, value) {
276
+ if (index < 0 || index >= keys().length)
277
+ return;
278
+ const key = keys()[index];
279
+ if (key)
280
+ update(key, value);
281
+ }
282
+ function clear() {
283
+ replace([]);
284
+ }
285
+ if (options.scope) {
286
+ options.scope.onCleanup(() => {
287
+ clear();
288
+ });
289
+ }
290
+ return {
291
+ fields,
292
+ append,
293
+ remove,
294
+ removeAt,
295
+ insert,
296
+ move,
297
+ swap,
298
+ replace,
299
+ update,
300
+ updateAt,
301
+ clear,
302
+ length,
303
+ _getIndex,
304
+ _translatePath,
305
+ };
306
+ }
@@ -0,0 +1,6 @@
1
+ export declare function findFormFieldElement(formElement: HTMLFormElement | null, path: string): HTMLElement | null;
2
+ /**
3
+ * Reset all `[d-field]` DOM controls to the provided defaults.
4
+ * Keeps field arrays and meta-state handling to the caller.
5
+ */
6
+ export declare function resetFormDomFields<T>(formElement: HTMLFormElement | null, defaultValues: Partial<T>): void;