Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-monorepo",
"version": "0.10.11",
"version": "0.10.12",
"private": true,
"engines": {
"node": ">=24"
Expand Down
2 changes: 1 addition & 1 deletion packages/data-ai/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adobe-data-ai",
"version": "0.10.11",
"version": "0.10.12",
"description": "Architecture skills for @adobe/data — data-oriented modelling, archetype iteration, hot-path performance, and related conventions.",
"author": {
"name": "Adobe"
Expand Down
2 changes: 1 addition & 1 deletion packages/data-ai/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-ai",
"version": "0.10.11",
"version": "0.10.12",
"description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-gpu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-gpu",
"version": "0.10.11",
"version": "0.10.12",
"description": "Adobe data WebGPU plugins and types for graphics and compute",
"type": "module",
"private": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// © 2026 Adobe. MIT License. See /LICENSE for details.
import { describe, it, expect } from "vitest";
import { Database } from "@adobe/data/ecs";
import type { DragState } from "@adobe/data-lit";
import { ServiceDatabase } from "../../service-database/service-database.js";
import { dragTodo } from "./drag-todo.js";

// `dragTodo` is the "drag UI op" — conformance intentionally skips it (see
// `transactions.md`), so this is its only coverage. It is the one place the
// `useDragGenerator` → action → transaction mapping (including the cancel
// reset) actually lives.

// A stand-in row height; `services/` must not import the real UI constant.
const ROW_HEIGHT = 48;

async function* dragStates(...states: DragState[]): AsyncGenerator<DragState> {
for (const state of states) yield state;
}

describe("dragTodo action", () => {
it("streams a live offset on move, then reorders on the final drop", async () => {
const db = Database.create(ServiceDatabase.plugin);
const entity = db.transactions.createTodo({ name: "a" });
const other = db.transactions.createTodo({ name: "b" });

await dragTodo(db, {
entity,
index: 0,
rowHeight: ROW_HEIGHT,
drag: dragStates(
{ type: "move", delta: [0, 10], position: [0, 10] },
{
type: "end",
delta: [0, ROW_HEIGHT],
position: [0, ROW_HEIGHT],
},
),
});

const after = db.read(entity);
expect(after?.dragPosition).toBeNull();
// dragged from index 0 to index 1 — now orders after "b".
expect(after?.order).toBeGreaterThan(db.read(other)!.order!);
});

it("abandons the drag with no reorder on cancel", async () => {
const db = Database.create(ServiceDatabase.plugin);
const entity = db.transactions.createTodo({ name: "a" });
const before = db.read(entity)!;

await dragTodo(db, {
entity,
index: 0,
rowHeight: ROW_HEIGHT,
drag: dragStates(
{ type: "move", delta: [0, 10], position: [0, 10] },
{ type: "cancel" },
),
});

const after = db.read(entity);
expect(after?.dragPosition).toBeNull();
expect(after?.order).toBe(before.order);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// © 2026 Adobe. MIT License. See /LICENSE for details.
import type { Entity } from "@adobe/data/ecs";
import type { DragState } from "@adobe/data-lit";
import type { ServiceDatabase } from "../../service-database/service-database.js";

// `useDragGenerator` hands us the raw pointer stream; this action maps each
// yielded `DragState` frame to the `dragTodo` transaction's args and drives
// the transaction directly with that mapped async generator, so every frame —
// including the final drop — commits as one coalesced, undoable step. A
// cancelled gesture yields a `dragPosition: null` frame with no `finalIndex`,
// which abandons the drag without reordering.
//
// `rowHeight` is a UI layout measure passed in by the caller (never imported —
// `services/` must not depend on `ui/`), used to convert the final pixel offset
// into a list-index delta.
export const dragTodo = (
db: ServiceDatabase,
args: {
readonly entity: Entity;
readonly index: number;
readonly rowHeight: number;
readonly drag: AsyncGenerator<DragState>;
},
) => {
const { entity, index, rowHeight, drag } = args;
return db.transactions.dragTodo(async function* () {
for await (const state of drag) {
if (state.type === "move") {
yield { entity, dragPosition: state.delta[1] };
} else if (state.type === "end") {
yield {
entity,
dragPosition: state.delta[1],
finalIndex: index + Math.round(state.delta[1] / rowHeight),
};
} else if (state.type === "cancel") {
yield { entity, dragPosition: null };
}
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./delete-todo.js";
export * from "./delete-all-todos.js";
export * from "./toggle-display-completed.js";
export * from "./reorder-todo.js";
export * from "./drag-todo.js";
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { Entity } from "@adobe/data/ecs";
import type { ServiceDatabase } from "../../service-database/service-database.js";

// Move a todo to `toIndex` — the programmatic counterpart of the drag UI (which
// dispatches the richer `dragTodo` transaction directly). Reproduces
// `State.reorderTodo` as a single final-drop `dragTodo` commit, so it is the
// same-named action that conforms the `reorderTodo` transition.
// drives the same `dragTodo` transaction through the richer `dragTodo` action,
// streamed frame-by-frame from `useDragGenerator`). Reproduces `State.reorderTodo`
// as a single final-drop `dragTodo` commit, so it is the same-named action that
// conforms the `reorderTodo` transition.
export const reorderTodo = (
db: ServiceDatabase,
{ id, toIndex }: { id: Entity; toIndex: number },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import { normalizeOrder, selectOrderedTodos } from "./order/index.js";
// `type DragTodoInput = Parameters<typeof dragTodo>[1]`.
type DragTodoInput = {
readonly entity: Entity;
/** Live vertical pixel offset from the todo's resting position. */
readonly dragPosition: number;
/**
* Live vertical pixel offset from the todo's resting position. `null`
* abandons the drag with no reorder — the same shape a cancelled gesture
* yields as its final frame.
*/
readonly dragPosition: number | null;
/**
* Target index within the currently visible list. Present only on the final
* frame of a drag; while omitted the drag is still in progress.
* frame of a drag; while omitted the drag is still in progress (or was
* cancelled).
*/
readonly finalIndex?: number;
};
Expand All @@ -24,8 +29,10 @@ type DragTodoInput = {
* a fractional `order` between its new visible neighbours, clears
* `dragPosition`, then normalizes every todo back to contiguous integers.
*
* Intended to be driven by `useDragTransaction`, which invokes it with an
* `AsyncArgsProvider` so all frames commit as one undoable step.
* Intended to be driven by the `dragTodo` action, which maps a
* `useDragGenerator` stream into this shape and drives this transaction with
* the resulting async generator so every frame — including the final drop —
* commits as one undoable step.
*/
export const dragTodo = (t: CoreDatabase.Store, input: DragTodoInput): void => {
t.undoable = { coalesce: false };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
// © 2026 Adobe. MIT License. See /LICENSE for details.
import { customElement, property } from "lit/decorators.js";
import type { Entity } from "@adobe/data/ecs";
import { useObservableValues, useState, useDragTransaction } from "@adobe/data-lit";
import { useObservableValues, useState, useDragGenerator } from "@adobe/data-lit";
import { TodoElement } from "../todo-element.js";
import { styles } from "./todo-row.css.js";
import { TODO_ROW_HEIGHT } from "./todo-row.constants.js";
import type { dragTodo } from "../../services/main-service/transaction-database/transactions/drag-todo.js";
import * as presentation from "./todo-row-presentation.js";

// The transaction owns this shape and doesn't export it; infer it from the
// function's second parameter rather than importing a type.
type DragTodoInput = Parameters<typeof dragTodo>[1];

const tagName = "todo-row";

declare global {
Expand Down Expand Up @@ -40,29 +35,24 @@ export class TodoRowElement extends TodoElement {
);
const todo = values?.todo;

// Dragging is a lifecycle/pointer concern, so it lives in the element (not
// the pure presentation). A single coalesced transaction spans the whole
// gesture: `move` frames record the live pixel offset, `end` commits the
// reorder. Drag is a continuous manipulation, so it calls the transaction
// directly rather than an analytics-wrapped action.
// Dragging is a lifecycle/pointer concern, so the pointer stream is
// captured here — but mapping it into transaction args is logic, and
// belongs to the `dragTodo` action, not this element (`element.md`: no
// shape-building in a callback). `useDragGenerator` hands the action a
// raw `DragState` generator; the action drives the `dragTodo` transaction
// with it directly, so the whole gesture — every live frame plus the
// final drop — commits as one coalesced, undoable step.
const { entity, index } = this;
useDragTransaction<DragTodoInput>(
{
transaction: this.service.transactions.dragTodo,
update: (value) => {
if (value.type === "move") {
return { entity, dragPosition: value.delta[1] };
}
if (value.type === "end") {
return {
entity,
dragPosition: value.delta[1],
finalIndex: index + Math.round(value.delta[1] / TODO_ROW_HEIGHT),
};
}
},
},
[this.service.transactions.dragTodo, entity, index],
useDragGenerator(
{},
[this.service.actions.dragTodo, entity, index],
(drag) =>
this.service.actions.dragTodo({
entity,
index,
rowHeight: TODO_ROW_HEIGHT,
drag,
}),
);

return presentation.render({
Expand Down
2 changes: 1 addition & 1 deletion packages/data-lit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-lit",
"version": "0.10.11",
"version": "0.10.12",
"description": "Adobe data Lit bindings - hooks, elements, decorators",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-persistence/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-persistence",
"version": "0.10.11",
"version": "0.10.12",
"description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-react",
"version": "0.10.11",
"version": "0.10.12",
"description": "Adobe data React bindings — hooks and context for ECS database",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-rpc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-rpc",
"version": "0.10.11",
"version": "0.10.12",
"description": "Schema-driven, bidirectional projection of @adobe/data async data services across a boundary (iframe / MessagePort / Worker). Only Data crosses the wire.",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-solid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-solid",
"version": "0.10.11",
"version": "0.10.12",
"description": "Adobe data SolidJS bindings — context and provider for ECS database",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-sync/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-sync",
"version": "0.10.11",
"version": "0.10.12",
"description": "Multi-user real-time synchronisation for @adobe/data ECS — server, client, and in-process loopback.",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-testing/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-testing",
"version": "0.10.11",
"version": "0.10.12",
"description": "Conformance-testing utilities (Match + Conformance runners) for @adobe/data ECS features",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data",
"version": "0.10.11",
"version": "0.10.12",
"description": "Adobe data oriented programming library",
"type": "module",
"sideEffects": false,
Expand Down