Docs
Recipes

Record workspace

Read a record, edit it, inspect activity, undo, and save a view.

Copy the shared mutation store and adapter, the product form catalog, and the details and view files below. This example requires the current registry or the v3.1.0 snapshot, not the older v3.0.0 one.

record-config.ts

record-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";

export const recordConfig = defineTableConfig({
  ...productConfig,
  table: {
    ...productConfig.table,
    allowEdit: true,
    enableViews: true,
    allowViewSave: true,
    rowClickMode: "default",
  },
  form: { editFormType: "products" },
});
record-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";

export const recordConfig = defineTableConfig({
  ...productConfig,
  table: {
    ...productConfig.table,
    allowEdit: true,
    enableViews: true,
    allowViewSave: true,
    rowClickMode: "default",
  },
  form: { editFormType: "products" },
});

product-details.ts

product-details.ts
import type {
  DetailRevertHandler,
  RecordDetailsConfig,
} from "@/components/ui/yayaw-table/utils/record-details";
import { productStore } from "../shared/store";

export const productDetails: RecordDetailsConfig = {
  presentation: "drawer",
  title: (row) => String(row.name),
  updatedAt: (row) => String(row.updatedAt),
  sections: [
    {
      id: "product",
      title: "Product",
      fields: [
        { id: "name", label: "Name", type: "text" },
        {
          id: "price",
          label: "Price",
          type: "number",
          numberFormat: { style: "currency", currency: "EUR" },
        },
        { id: "stock", label: "Stock", type: "number" },
        { id: "status", label: "Status", type: "text" },
      ],
    },
  ],
  activity: (row) => productStore.activity(String(row.id)),
};
export const revertActivity: DetailRevertHandler = (row, entry) =>
  productStore.revert(String(row.id), entry.id);
product-details.ts
import type {
  DetailRevertHandler,
  RecordDetailsConfig,
} from "@/components/ui/yayaw-table-vue/record-details";
import { productStore } from "../shared/store";

export const productDetails: RecordDetailsConfig = {
  presentation: "drawer",
  title: (row) => String(row.name),
  updatedAt: (row) => String(row.updatedAt),
  sections: [
    {
      id: "product",
      title: "Product",
      fields: [
        { id: "name", label: "Name", type: "text" },
        {
          id: "price",
          label: "Price",
          type: "number",
          numberFormat: { style: "currency", currency: "EUR" },
        },
        { id: "stock", label: "Stock", type: "number" },
        { id: "status", label: "Status", type: "text" },
      ],
    },
  ],
  activity: (row) => productStore.activity(String(row.id)),
};
export const revertActivity: DetailRevertHandler = (row, entry) =>
  productStore.revert(String(row.id), entry.id);

view-actions.ts

view-actions.ts
import type {
  TableView,
  TableViewActions,
} from "@/components/ui/yayaw-table/types/view-types";

/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
  list: ({ tableId }) =>
    Promise.resolve({
      data: [...views.values()].filter((view) => view.tableId === tableId),
    }),
  create: (input) => {
    const view = {
      ...input,
      id: crypto.randomUUID(),
      createdById: "demo-user",
    };
    views.set(view.id, structuredClone(view));
    return Promise.resolve({ success: true, data: view });
  },
  update: (id, input) => {
    const current = views.get(id);
    if (!current || (input.tableId && input.tableId !== current.tableId)) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    const view = { ...current, ...input };
    views.set(id, structuredClone(view));
    return Promise.resolve({ success: true, data: view });
  },
  delete: (id, { tableId }) => {
    if (views.get(id)?.tableId !== tableId) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    views.delete(id);
    if (favorites.get(tableId) === id) {
      favorites.delete(tableId);
    }
    return Promise.resolve({ success: true, data: { id } });
  },
  getFavorite: ({ tableId }) =>
    Promise.resolve({
      success: true,
      data: { viewId: favorites.get(tableId) ?? null },
    }),
  setFavorite: (viewId, { tableId }) => {
    if (viewId && views.get(viewId)?.tableId !== tableId) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    favorites.set(tableId, viewId);
    return Promise.resolve({ success: true, data: { viewId } });
  },
};
view-actions.ts
import type {
  TableView,
  TableViewActions,
} from "@/components/ui/yayaw-table-vue/types";

/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
  list: ({ tableId }) =>
    Promise.resolve({
      data: [...views.values()].filter((view) => view.tableId === tableId),
    }),
  create: (input) => {
    const view = {
      ...input,
      id: crypto.randomUUID(),
      createdById: "demo-user",
    };
    views.set(view.id, structuredClone(view));
    return Promise.resolve({ success: true, data: view });
  },
  update: (id, input) => {
    const current = views.get(id);
    if (!current || (input.tableId && input.tableId !== current.tableId)) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    const view = { ...current, ...input };
    views.set(id, structuredClone(view));
    return Promise.resolve({ success: true, data: view });
  },
  delete: (id, { tableId }) => {
    if (views.get(id)?.tableId !== tableId) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    views.delete(id);
    if (favorites.get(tableId) === id) {
      favorites.delete(tableId);
    }
    return Promise.resolve({ success: true });
  },
  getFavorite: ({ tableId }) =>
    Promise.resolve({
      success: true,
      data: { viewId: favorites.get(tableId) ?? null },
    }),
  setFavorite: (viewId, { tableId }) => {
    if (viewId && views.get(viewId)?.tableId !== tableId) {
      return Promise.resolve({ success: false, error: "View not found." });
    }
    favorites.set(tableId, viewId);
    return Promise.resolve({ success: true, data: { viewId } });
  },
};

Entry component

Expand ↗

Sample data. Changes stay in this preview.

Expand ↗

Sample data. Changes stay in this preview.

Verify the result

  1. Open Oak desk and read its typed fields.

  2. Edit stock to 15 and save.

  3. Open Activity: the original 12 → 15 change appears.

  4. Undo it: stock returns to 12 and a new undo event references the original event.

  5. Verify a second undo is unavailable.

  6. Change visible columns, save a view, set it as your favorite and remount the table: the view restores. Reloading the browser resets this in-memory store.

Use the existing playground to explore the library’s controls.