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.
- package/README.md +3 -5
- package/dist/components/ui/ui-types.d.ts +8 -8
- package/dist/core/signal.d.ts +9 -1
- package/dist/core/signal.js +63 -87
- package/dist/form/field-array.d.ts +12 -0
- package/dist/form/field-array.js +306 -0
- package/dist/form/form-dom.d.ts +6 -0
- package/dist/form/form-dom.js +52 -0
- package/dist/form/form-types.d.ts +24 -15
- package/dist/form/form.d.ts +7 -53
- package/dist/form/form.js +49 -932
- package/dist/form/index.d.ts +2 -2
- package/dist/form/index.js +1 -2
- package/dist/form/parse-form-data.d.ts +13 -0
- package/dist/form/parse-form-data.js +88 -0
- package/dist/form/path-utils.d.ts +24 -0
- package/dist/form/path-utils.js +91 -0
- package/dist/form/path-watchers.d.ts +23 -0
- package/dist/form/path-watchers.js +82 -0
- package/dist/form/validation-pipeline.d.ts +29 -0
- package/dist/form/validation-pipeline.js +223 -0
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.js +1 -1
- package/dist/router/location-utils.d.ts +19 -0
- package/dist/router/location-utils.js +60 -0
- package/dist/router/lru-cache.d.ts +17 -0
- package/dist/router/lru-cache.js +63 -0
- package/dist/router/preload-metadata.d.ts +26 -0
- package/dist/router/preload-metadata.js +65 -0
- package/dist/router/router-lifecycle.d.ts +12 -0
- package/dist/router/router-lifecycle.js +31 -0
- package/dist/router/router-mount-lifecycle.d.ts +11 -0
- package/dist/router/router-mount-lifecycle.js +50 -0
- package/dist/router/router-prefetch.d.ts +22 -0
- package/dist/router/router-prefetch.js +86 -0
- package/dist/router/router-preload-cache.d.ts +25 -0
- package/dist/router/router-preload-cache.js +68 -0
- package/dist/router/router-render-utils.d.ts +14 -0
- package/dist/router/router-render-utils.js +11 -0
- package/dist/router/router-validation.d.ts +4 -0
- package/dist/router/router-validation.js +100 -0
- package/dist/router/router-view-composer.d.ts +16 -0
- package/dist/router/router-view-composer.js +41 -0
- package/dist/router/router.d.ts +12 -3
- package/dist/router/router.js +241 -621
- package/dist/runtime/array-directive-dom.d.ts +4 -0
- package/dist/runtime/array-directive-dom.js +30 -0
- package/dist/runtime/bind.js +112 -834
- package/dist/runtime/internal/components/component-props.d.ts +15 -0
- package/dist/runtime/internal/components/component-props.js +69 -0
- package/dist/runtime/internal/components/component-slots.d.ts +10 -0
- package/dist/runtime/internal/components/component-slots.js +68 -0
- package/dist/runtime/internal/list/list-clone-factory.d.ts +17 -0
- package/dist/runtime/internal/list/list-clone-factory.js +35 -0
- package/dist/runtime/internal/list/list-clone-registry.d.ts +12 -0
- package/dist/runtime/internal/list/list-clone-registry.js +43 -0
- package/dist/runtime/internal/list/list-keying.d.ts +13 -0
- package/dist/runtime/internal/list/list-keying.js +65 -0
- package/dist/runtime/internal/list/list-metadata.d.ts +11 -0
- package/dist/runtime/internal/list/list-metadata.js +21 -0
- package/dist/runtime/internal/list/list-reconcile.d.ts +3 -0
- package/dist/runtime/internal/list/list-reconcile.js +25 -0
- package/dist/runtime/internal/list/list-scheduler.d.ts +16 -0
- package/dist/runtime/internal/list/list-scheduler.js +45 -0
- package/dist/runtime/internal/virtual/virtual-list-helpers.d.ts +48 -0
- package/dist/runtime/internal/virtual/virtual-list-helpers.js +291 -0
- 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:
|
|
60
|
-
selected:
|
|
61
|
-
visible:
|
|
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:
|
|
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):
|
|
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:
|
|
129
|
-
days:
|
|
128
|
+
title: ReadonlySignal<string>;
|
|
129
|
+
days: ReadonlySignal<CalendarDay[]>;
|
|
130
130
|
dayLabels: string[];
|
|
131
131
|
prev(): void;
|
|
132
132
|
next(): void;
|
package/dist/core/signal.d.ts
CHANGED
|
@@ -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):
|
|
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
|
*
|
package/dist/core/signal.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
374
|
-
const prevEffect = activeEffect;
|
|
375
|
-
const prevScope = activeScope;
|
|
376
|
-
activeEffect = run;
|
|
377
|
-
activeScope = owningScope ?? null;
|
|
389
|
+
cleanupEffectDeps(run);
|
|
378
390
|
try {
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
566
|
-
const prevEffect = activeEffect;
|
|
567
|
-
const prevScope = activeScope;
|
|
568
|
-
activeEffect = run;
|
|
569
|
-
activeScope = owningScope ?? null;
|
|
551
|
+
cleanupEffectDeps(run);
|
|
570
552
|
try {
|
|
571
|
-
|
|
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;
|