@vertz/ui 0.2.9 → 0.2.11

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/index.d.ts CHANGED
@@ -148,6 +148,20 @@ declare function ErrorBoundary(props: ErrorBoundaryProps): Node;
148
148
  * ```
149
149
  */
150
150
  declare function onMount2(callback: () => (() => void) | void): void;
151
+ interface ListTransitionProps<T> {
152
+ each: T[];
153
+ keyFn: (item: T, index: number) => string | number;
154
+ children: (item: T) => HTMLElement;
155
+ }
156
+ /**
157
+ * ListTransition component for animated list item enter/exit.
158
+ * New items get `data-presence="enter"`, removed items get `data-presence="exit"`
159
+ * with DOM removal deferred until CSS animation completes.
160
+ *
161
+ * Props are accessed as getters (not destructured) so the compiler-generated
162
+ * reactive getters are tracked by the underlying domEffect.
163
+ */
164
+ declare function ListTransition<T>(props: ListTransitionProps<T>): Node;
151
165
  interface PresenceProps {
152
166
  when: boolean;
153
167
  children: () => HTMLElement;
@@ -1284,4 +1298,4 @@ declare class EntityStore {
1284
1298
  * ```
1285
1299
  */
1286
1300
  declare function createTestStore(data: Record<string, Record<string, unknown>>): EntityStore;
1287
- export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useDialogStack, useContext, untrack, slideOutToTop, slideOutToRight, slideOutToLeft, slideOutToBottom, slideInFromTop, slideInFromRight, slideInFromLeft, slideInFromBottom, signal, setAdapter, s, resolveChildren, resetInjectedStyles, ref, queryMatch, query, parseSearchParams, palettes, onMount2 as onMount, mount, keyframes, isRenderNode, isQueryDescriptor, injectCSS, hydrate, globalCss, getInjectedCSS, getAdapter, formDataToObject, form, fadeOut, fadeIn, defineTheme, defineRoutes, css, createTestStore, createRouter, createLink, createFieldState, createDialogStack, createDOMAdapter, createContext, computed, compileTheme, children, batch, accordionUp, accordionDown, __staticText, __exitChildren, __enterChildren, __element, __append, VariantsConfig, VariantProps, VariantFunction, ValidationResult, UnwrapSignals, TypedRoutes, TypedRouter, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, StyleValue, StyleEntry, Signal, SerializedStore, SearchParamSchema, SdkMethodWithMeta, SdkMethod, RouterViewProps, RouterView, RouterOptions, RouterContext, Router, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, RenderText, RenderNode, RenderElement, RenderAdapter, Ref, ReadonlySignal, RawDeclaration, RENDER_NODE_BRAND, QueryResult, QueryOptions, QueryMatchHandlers, QueryDescriptor2 as QueryDescriptor, PresenceProps, Presence, PathWithParams, OutletContextValue, OutletContext, Outlet, NavigateOptions, MountOptions, MountHandle, MatchedRoute, LoaderData, LinkProps, LinkFactoryOptions, InferRouteMap, GlobalCSSOutput, GlobalCSSInput, FormSchema, FormOptions, FormInstance, FormDataOptions, FieldState, ExtractParams, ErrorBoundaryProps, ErrorBoundary, EntityStoreOptions, EntityStore, DisposeFn, DisposalScopeError, DialogStackContext, DialogStack, DialogHandle, DialogDismissedError, DialogComponent, Context, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };
1301
+ export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useDialogStack, useContext, untrack, slideOutToTop, slideOutToRight, slideOutToLeft, slideOutToBottom, slideInFromTop, slideInFromRight, slideInFromLeft, slideInFromBottom, signal, setAdapter, s, resolveChildren, resetInjectedStyles, ref, queryMatch, query, parseSearchParams, palettes, onMount2 as onMount, mount, keyframes, isRenderNode, isQueryDescriptor, injectCSS, hydrate, globalCss, getInjectedCSS, getAdapter, formDataToObject, form, fadeOut, fadeIn, defineTheme, defineRoutes, css, createTestStore, createRouter, createLink, createFieldState, createDialogStack, createDOMAdapter, createContext, computed, compileTheme, children, batch, accordionUp, accordionDown, __staticText, __exitChildren, __enterChildren, __element, __append, VariantsConfig, VariantProps, VariantFunction, ValidationResult, UnwrapSignals, TypedRoutes, TypedRouter, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, StyleValue, StyleEntry, Signal, SerializedStore, SearchParamSchema, SdkMethodWithMeta, SdkMethod, RouterViewProps, RouterView, RouterOptions, RouterContext, Router, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, RenderText, RenderNode, RenderElement, RenderAdapter, Ref, ReadonlySignal, RawDeclaration, RENDER_NODE_BRAND, QueryResult, QueryOptions, QueryMatchHandlers, QueryDescriptor2 as QueryDescriptor, PresenceProps, Presence, PathWithParams, OutletContextValue, OutletContext, Outlet, NavigateOptions, MountOptions, MountHandle, MatchedRoute, LoaderData, ListTransitionProps, ListTransition, LinkProps, LinkFactoryOptions, InferRouteMap, GlobalCSSOutput, GlobalCSSInput, FormSchema, FormOptions, FormInstance, FormDataOptions, FieldState, ExtractParams, ErrorBoundaryProps, ErrorBoundary, EntityStoreOptions, EntityStore, DisposeFn, DisposalScopeError, DialogStackContext, DialogStack, DialogHandle, DialogDismissedError, DialogComponent, Context, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  import {
49
49
  query,
50
50
  queryMatch
51
- } from "./shared/chunk-hh0dhmb4.js";
51
+ } from "./shared/chunk-rjjjvmcf.js";
52
52
  import"./shared/chunk-jrtrk5z4.js";
53
53
  import {
54
54
  ThemeProvider,
@@ -174,6 +174,179 @@ function onMount(callback) {
174
174
  }
175
175
  }
176
176
  }
177
+ // src/dom/list-transition.ts
178
+ function createItemProxy(itemSignal) {
179
+ if (typeof itemSignal.peek() !== "object" || itemSignal.peek() == null) {
180
+ return itemSignal.peek();
181
+ }
182
+ return new Proxy({}, {
183
+ get(_target, prop, receiver) {
184
+ const current = itemSignal.value;
185
+ if (current == null)
186
+ return;
187
+ const value = Reflect.get(current, prop, receiver);
188
+ if (typeof value === "function") {
189
+ return value.bind(current);
190
+ }
191
+ return value;
192
+ },
193
+ has(_target, prop) {
194
+ const current = itemSignal.value;
195
+ if (current == null)
196
+ return false;
197
+ return Reflect.has(current, prop);
198
+ },
199
+ ownKeys() {
200
+ const current = itemSignal.value;
201
+ if (current == null)
202
+ return [];
203
+ return Reflect.ownKeys(current);
204
+ },
205
+ getOwnPropertyDescriptor(_target, prop) {
206
+ const current = itemSignal.value;
207
+ if (current == null)
208
+ return;
209
+ return Reflect.getOwnPropertyDescriptor(current, prop);
210
+ }
211
+ });
212
+ }
213
+ function listTransition(startMarker, endMarker, items, keyFn, renderFn) {
214
+ const getItems = typeof items === "function" ? items : () => items.value;
215
+ const nodeMap = new Map;
216
+ const scopeMap = new Map;
217
+ const itemSignalMap = new Map;
218
+ const exitingNodes = new Set;
219
+ const exitingKeyMap = new Map;
220
+ const keyGeneration = new Map;
221
+ let isFirstRun = true;
222
+ const outerScope = pushScope();
223
+ try {
224
+ domEffect(() => {
225
+ const newItems = getItems() ?? [];
226
+ const newKeySet = new Set(newItems.map((item, i) => keyFn(item, i)));
227
+ if (isFirstRun) {
228
+ isFirstRun = false;
229
+ for (const [i, item] of newItems.entries()) {
230
+ const key = keyFn(item, i);
231
+ const itemSig = signal(item);
232
+ const proxy = createItemProxy(itemSig);
233
+ const scope = pushScope();
234
+ const node = renderFn(proxy);
235
+ popScope();
236
+ nodeMap.set(key, node);
237
+ scopeMap.set(key, scope);
238
+ itemSignalMap.set(key, itemSig);
239
+ endMarker.parentNode?.insertBefore(node, endMarker);
240
+ }
241
+ return;
242
+ }
243
+ for (const [key, node] of nodeMap) {
244
+ if (!newKeySet.has(key)) {
245
+ const scope = scopeMap.get(key);
246
+ if (scope) {
247
+ runCleanups(scope);
248
+ scopeMap.delete(key);
249
+ }
250
+ nodeMap.delete(key);
251
+ itemSignalMap.delete(key);
252
+ const gen = (keyGeneration.get(key) ?? 0) + 1;
253
+ keyGeneration.set(key, gen);
254
+ exitingNodes.add(node);
255
+ exitingKeyMap.set(key, node);
256
+ node.setAttribute("data-presence", "exit");
257
+ onAnimationsComplete(node, () => {
258
+ if (keyGeneration.get(key) === gen) {
259
+ node.parentNode?.removeChild(node);
260
+ exitingNodes.delete(node);
261
+ exitingKeyMap.delete(key);
262
+ }
263
+ });
264
+ }
265
+ }
266
+ const desiredNodes = [];
267
+ const enterNodes = [];
268
+ for (const [i, item] of newItems.entries()) {
269
+ const key = keyFn(item, i);
270
+ let node = nodeMap.get(key);
271
+ if (!node) {
272
+ const oldExiting = exitingKeyMap.get(key);
273
+ if (oldExiting) {
274
+ oldExiting.parentNode?.removeChild(oldExiting);
275
+ exitingNodes.delete(oldExiting);
276
+ exitingKeyMap.delete(key);
277
+ }
278
+ const gen = (keyGeneration.get(key) ?? 0) + 1;
279
+ keyGeneration.set(key, gen);
280
+ const itemSig = signal(item);
281
+ const proxy = createItemProxy(itemSig);
282
+ const scope = pushScope();
283
+ node = renderFn(proxy);
284
+ popScope();
285
+ nodeMap.set(key, node);
286
+ scopeMap.set(key, scope);
287
+ itemSignalMap.set(key, itemSig);
288
+ node.setAttribute("data-presence", "enter");
289
+ enterNodes.push({ node, key });
290
+ } else {
291
+ const itemSig = itemSignalMap.get(key);
292
+ if (itemSig) {
293
+ itemSig.value = item;
294
+ }
295
+ }
296
+ desiredNodes.push(node);
297
+ }
298
+ const parent = startMarker.parentNode;
299
+ if (parent) {
300
+ let cursor = startMarker.nextSibling;
301
+ for (const desired of desiredNodes) {
302
+ while (cursor && cursor !== endMarker && exitingNodes.has(cursor)) {
303
+ cursor = cursor.nextSibling;
304
+ }
305
+ if (cursor === desired) {
306
+ cursor = cursor.nextSibling;
307
+ } else {
308
+ parent.insertBefore(desired, cursor);
309
+ }
310
+ }
311
+ }
312
+ for (const { node: enterNode, key } of enterNodes) {
313
+ onAnimationsComplete(enterNode, () => {
314
+ if (nodeMap.get(key) === enterNode) {
315
+ enterNode.removeAttribute("data-presence");
316
+ }
317
+ });
318
+ }
319
+ });
320
+ } finally {
321
+ popScope();
322
+ }
323
+ const dispose = () => {
324
+ for (const scope of scopeMap.values()) {
325
+ runCleanups(scope);
326
+ }
327
+ scopeMap.clear();
328
+ for (const node of exitingNodes) {
329
+ node.parentNode?.removeChild(node);
330
+ }
331
+ exitingNodes.clear();
332
+ exitingKeyMap.clear();
333
+ runCleanups(outerScope);
334
+ };
335
+ _tryOnCleanup(dispose);
336
+ return dispose;
337
+ }
338
+
339
+ // src/component/list-transition.ts
340
+ function ListTransition(props) {
341
+ const startMarker = document.createComment("lt-start");
342
+ const endMarker = document.createComment("lt-end");
343
+ const fragment = document.createDocumentFragment();
344
+ fragment.appendChild(startMarker);
345
+ fragment.appendChild(endMarker);
346
+ const dispose = listTransition(startMarker, endMarker, () => props.each, props.keyFn, props.children);
347
+ _tryOnCleanup(dispose);
348
+ return Object.assign(fragment, { dispose });
349
+ }
177
350
  // src/component/presence.ts
178
351
  function Presence(props) {
179
352
  const anchor = document.createComment("presence");
@@ -773,6 +946,7 @@ export {
773
946
  Presence,
774
947
  OutletContext,
775
948
  Outlet,
949
+ ListTransition,
776
950
  ErrorBoundary,
777
951
  EntityStore,
778
952
  DisposalScopeError,
@@ -340,6 +340,11 @@ declare function clearChildren(container: Node): void;
340
340
  * Efficiently updates a container's children when the items signal changes.
341
341
  * Reuses existing DOM nodes based on key identity — no virtual DOM.
342
342
  *
343
+ * Each item is wrapped in a reactive proxy backed by a signal. When the item
344
+ * at an existing key changes (e.g., after refetch), the signal updates and
345
+ * any reactive bindings inside the node (domEffect, __child) re-run
346
+ * automatically — without re-creating the DOM node.
347
+ *
343
348
  * Compiler output target for .map() / for-each expressions in JSX.
344
349
  *
345
350
  * @param container - The parent DOM element
package/dist/internals.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  MemoryCache,
19
19
  deriveKey
20
- } from "./shared/chunk-hh0dhmb4.js";
20
+ } from "./shared/chunk-rjjjvmcf.js";
21
21
  import"./shared/chunk-jrtrk5z4.js";
22
22
  import {
23
23
  ALIGNMENT_MAP,
@@ -69,6 +69,7 @@ import {
69
69
  pushScope,
70
70
  runCleanups,
71
71
  setContextScope,
72
+ signal,
72
73
  startSignalCollection,
73
74
  stopSignalCollection
74
75
  } from "./shared/chunk-hrd0mft1.js";
@@ -189,10 +190,46 @@ function clearChildren(container) {
189
190
  }
190
191
  }
191
192
  // src/dom/list.ts
193
+ function createItemProxy(itemSignal) {
194
+ if (typeof itemSignal.peek() !== "object" || itemSignal.peek() == null) {
195
+ return itemSignal.peek();
196
+ }
197
+ return new Proxy({}, {
198
+ get(_target, prop, receiver) {
199
+ const current = itemSignal.value;
200
+ if (current == null)
201
+ return;
202
+ const value = Reflect.get(current, prop, receiver);
203
+ if (typeof value === "function") {
204
+ return value.bind(current);
205
+ }
206
+ return value;
207
+ },
208
+ has(_target, prop) {
209
+ const current = itemSignal.value;
210
+ if (current == null)
211
+ return false;
212
+ return Reflect.has(current, prop);
213
+ },
214
+ ownKeys() {
215
+ const current = itemSignal.value;
216
+ if (current == null)
217
+ return [];
218
+ return Reflect.ownKeys(current);
219
+ },
220
+ getOwnPropertyDescriptor(_target, prop) {
221
+ const current = itemSignal.value;
222
+ if (current == null)
223
+ return;
224
+ return Reflect.getOwnPropertyDescriptor(current, prop);
225
+ }
226
+ });
227
+ }
192
228
  function __list(container, items, keyFn, renderFn) {
193
229
  const getItems = typeof items === "function" ? items : () => items.value;
194
230
  const nodeMap = new Map;
195
231
  const scopeMap = new Map;
232
+ const itemSignalMap = new Map;
196
233
  const isHydrationRun = getIsHydrating();
197
234
  const outerScope = pushScope();
198
235
  let isFirstRun = true;
@@ -202,11 +239,14 @@ function __list(container, items, keyFn, renderFn) {
202
239
  isFirstRun = false;
203
240
  for (const [i, item] of newItems.entries()) {
204
241
  const key = keyFn(item, i);
242
+ const itemSig = signal(item);
243
+ const proxy = createItemProxy(itemSig);
205
244
  const scope = pushScope();
206
- const node = renderFn(item);
245
+ const node = renderFn(proxy);
207
246
  popScope();
208
247
  nodeMap.set(key, node);
209
248
  scopeMap.set(key, scope);
249
+ itemSignalMap.set(key, itemSig);
210
250
  }
211
251
  return;
212
252
  }
@@ -221,6 +261,7 @@ function __list(container, items, keyFn, renderFn) {
221
261
  }
222
262
  node.parentNode?.removeChild(node);
223
263
  nodeMap.delete(key);
264
+ itemSignalMap.delete(key);
224
265
  }
225
266
  }
226
267
  const desiredNodes = [];
@@ -228,11 +269,19 @@ function __list(container, items, keyFn, renderFn) {
228
269
  const key = keyFn(item, i);
229
270
  let node = nodeMap.get(key);
230
271
  if (!node) {
272
+ const itemSig = signal(item);
273
+ const proxy = createItemProxy(itemSig);
231
274
  const scope = pushScope();
232
- node = renderFn(item);
275
+ node = renderFn(proxy);
233
276
  popScope();
234
277
  nodeMap.set(key, node);
235
278
  scopeMap.set(key, scope);
279
+ itemSignalMap.set(key, itemSig);
280
+ } else {
281
+ const itemSig = itemSignalMap.get(key);
282
+ if (itemSig) {
283
+ itemSig.value = item;
284
+ }
236
285
  }
237
286
  desiredNodes.push(node);
238
287
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  query,
3
3
  queryMatch
4
- } from "../shared/chunk-hh0dhmb4.js";
4
+ } from "../shared/chunk-rjjjvmcf.js";
5
5
  import"../shared/chunk-jrtrk5z4.js";
6
6
  import"../shared/chunk-g4rch80a.js";
7
7
  import"../shared/chunk-hrd0mft1.js";
@@ -459,7 +459,7 @@ function queryMatch(queryResult, handlers) {
459
459
  } else {
460
460
  branch = "data";
461
461
  }
462
- if (branch === currentBranch && branch !== "data") {
462
+ if (branch === currentBranch) {
463
463
  return;
464
464
  }
465
465
  runCleanups(branchCleanups);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI framework — signals, components, JSX runtime",
@@ -63,11 +63,11 @@
63
63
  "typecheck": "tsc --noEmit"
64
64
  },
65
65
  "dependencies": {
66
- "@vertz/fetch": "workspace:^"
66
+ "@vertz/fetch": "^0.2.11"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@happy-dom/global-registrator": "^20.7.0",
70
- "@vertz/schema": "workspace:^",
70
+ "@vertz/schema": "^0.2.11",
71
71
  "bunup": "^0.16.31",
72
72
  "happy-dom": "^20.7.0",
73
73
  "typescript": "^5.7.0"