@mmstack/primitives 22.2.2 → 22.4.0
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 +30 -3
- package/fesm2022/mmstack-primitives.mjs +217 -114
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +103 -29
package/README.md
CHANGED
|
@@ -117,6 +117,12 @@ Each level's shape is resolved from what's known: a value that is currently an o
|
|
|
117
117
|
|
|
118
118
|
Top-level array support isn't exposed yet — use `indexArray` / `keyArray` for those.
|
|
119
119
|
|
|
120
|
+
**Union leaves (perf opt-in).** `noUnionLeaves: true` promises no node ever flips between a leaf and a sub-store, so each node's leaf-ness is resolved once on first access and cached instead of staying reactive. Off by default — leave it off if a value can switch between a primitive and an object/array.
|
|
121
|
+
|
|
122
|
+
**Unions are fully supported by default.** A node may flip between array ↔ record ↔ primitive ↔ `null` freely: routing (`keys`/iteration/prototype) follows the live kind, and a child signal you grabbed **before** a flip stays correct after it — reads resolve against the new shape (`undefined` through a `null` parent, no throw) and writes copy by the container's live shape, so writing through a pre-flip child never turns an array into a plain object.
|
|
123
|
+
|
|
124
|
+
> Reserved keys: `set`, `update`, `mutate`, `inline`, `asReadonly` (and `extend`, until its removal next minor) resolve to the signal's own methods, so record keys with those names aren't reachable as child stores — read them off the value (`s().set`) instead.
|
|
125
|
+
|
|
120
126
|
### `extendStore` (scoped overlay)
|
|
121
127
|
|
|
122
128
|
`extendStore(store, seed)` (on any store kind) creates a **scoped overlay** — a child store that **shares** the parent's signals for inherited keys (the same `WritableSignal`: writes go through to the parent and parent changes flow down) while keeping the seed and any new keys in a **local layer** that never propagates upward. No diffing, no syncing — local keys simply aren't wired to the parent.
|
|
@@ -145,7 +151,7 @@ const scope = extendStore(app, draft); // writes to scope.title flow out to `dra
|
|
|
145
151
|
|
|
146
152
|
A few release notes:
|
|
147
153
|
|
|
148
|
-
- The
|
|
154
|
+
- The scope inherits the parent's config (`vivify` / `noUnionLeaves`) and its injector-scoped proxy cache, so **both** inherited and local paths vivify when the parent was created with `vivify`. `extendStore` doesn't accept `vivify` / `noUnionLeaves` — they always come from the parent.
|
|
149
155
|
- Reserved names — `asReadonlyStore` and the signal methods (`set` / `update` / `mutate` / `inline` / `asReadonly`) — shadow same-named data keys, as on any store.
|
|
150
156
|
- `scope.asReadonlyStore()` returns a read-only **snapshot view** of the merge (reactive reads, no writes); it does not share sub-store identity.
|
|
151
157
|
|
|
@@ -173,7 +179,7 @@ The fork is a full store (`draft.store.user.name(...)`, `extendStore`, deep read
|
|
|
173
179
|
- **`'coarse'`** — any base change resets the whole fork. Cheapest; correct when the base is held for the fork's lifetime (e.g. a transition). The default for a mutable base.
|
|
174
180
|
- **a `ReconcileFn<T>`** — `(ancestor, mine, theirs) => merged`, for bring-your-own merge (array-by-id, Immer patches, CRDT-ish).
|
|
175
181
|
|
|
176
|
-
>
|
|
182
|
+
> The fork inherits the base's `vivify` / `noUnionLeaves` and its injector-scoped proxy cache automatically, so its write semantics match the base. Pass them explicitly only to override (advanced).
|
|
177
183
|
|
|
178
184
|
### `toWritable`
|
|
179
185
|
|
|
@@ -459,6 +465,15 @@ const t = startTransaction(() => applyBulkEdit()); // live state updates; the di
|
|
|
459
465
|
await t.done; // committed, display revealed in one frame
|
|
460
466
|
```
|
|
461
467
|
|
|
468
|
+
Every exit settles: a throwing body rolls back, and if the calling context is **destroyed
|
|
469
|
+
mid-flight** the hold is released (writes kept) and `done` resolves — a transaction can never
|
|
470
|
+
leave a surviving ancestor scope frozen.
|
|
471
|
+
|
|
472
|
+
Attribution is **per transaction**: a load already in flight when it starts is not adopted —
|
|
473
|
+
it can neither commit the transaction early nor block its settle. (The same applies to
|
|
474
|
+
`startTransition`.) A pre-existing flight re-triggered by the transaction's own writes counts
|
|
475
|
+
once it restarts.
|
|
476
|
+
|
|
462
477
|
### `holdUntilReady`
|
|
463
478
|
|
|
464
479
|
The **structural** counterpart to `keepPrevious`: where that holds a _value_ through a reload, this holds a _structure_ through a swap. Given a `target` signal and a `ready` predicate, it keeps yielding the previous value until `ready()` is true, then swaps to the current target. Mount the incoming structure off to the side so its resources can settle and flip `ready`, keep showing the held one meanwhile, and let the old one go once `ready` releases the swap. (`@mmstack/router-core`'s `<mm-transition-outlet>` is this pattern applied to routes.)
|
|
@@ -668,6 +683,18 @@ once the pointer travels past `activationThreshold`, so the same element stays
|
|
|
668
683
|
clickable. Uses `setPointerCapture`, supports a delegated `handleSelector`, and
|
|
669
684
|
cancels on Escape or via `.cancel()`.
|
|
670
685
|
|
|
686
|
+
A delegated `handleSelector` reports which child actually started the drag via
|
|
687
|
+
`drag().origin` (so one listener on a container can serve many handles), and
|
|
688
|
+
`stopPropagation: true` lets an inner sensor claim the `pointerdown` over an
|
|
689
|
+
outer one on the same tree (e.g. a nested sortable). Reads are throttled
|
|
690
|
+
(`throttle`, default 16ms); `drag.unthrottled()` exposes the un-throttled view
|
|
691
|
+
for logic that needs the exact release position.
|
|
692
|
+
|
|
693
|
+
The idle state carries the **end reason**: `cancelled` is `true` when the gesture
|
|
694
|
+
was aborted (Escape, `pointercancel`, `.cancel()`) rather than released, and stays
|
|
695
|
+
set until the next `pointerdown` — so a drag consumer can tell "drop here" from
|
|
696
|
+
"abort" (`@mmstack/dnd` uses this to cancel instead of committing).
|
|
697
|
+
|
|
671
698
|
```typescript
|
|
672
699
|
import { sensor } from '@mmstack/primitives';
|
|
673
700
|
|
|
@@ -678,7 +705,7 @@ const position = computed(() => {
|
|
|
678
705
|
const d = drag();
|
|
679
706
|
return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
|
|
680
707
|
});
|
|
681
|
-
// drag().modifiers.shift → e.g. constrain axis · drag.cancel() → revert
|
|
708
|
+
// drag().modifiers.shift → e.g. constrain axis · drag().origin → the handle · drag.cancel() → revert
|
|
682
709
|
```
|
|
683
710
|
|
|
684
711
|
### `signalFromEvent`
|