From 9b3aa14e9d82173236539559262cddc4be6690b6 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Tue, 30 Jun 2026 00:02:31 +0800 Subject: [PATCH 1/2] feat(list): make inline editing a configurable view property with a runtime toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inline cell editing was effectively hardcoded per render path. Make it a first-class, per-view property with UI to flip it: - ViewSettingsPopover: a 'Record editing' toggle (mobile/compact settings). - ListView: a desktop toolbar toggle button (Pencil) for grid views, plus an onInlineEditChange callback and the popover wiring; reads schema.inlineEdit -> editable (already existed). - app-shell ObjectView (object saved views): persist the toggle to viewDef.inlineEdit via persistViewPatch, alongside sort/filter/hiddenFields. - InterfaceListPage (page lists): read inlineEdit from the page block's userActions.editInline instead of hardcoding false — inline editing becomes a page-authored property. When on, cells edit with the dedicated @object-ui/fields widgets (#2100). type-check clean; the one ListView rules-of-hooks lint warning is pre-existing (useObjectLabel try/catch). Co-Authored-By: Claude Opus 4.8 --- .../app-shell/src/views/InterfaceListPage.tsx | 5 ++- packages/app-shell/src/views/ObjectView.tsx | 3 ++ packages/plugin-list/src/ListView.tsx | 26 +++++++++++++++- .../src/components/ViewSettingsPopover.tsx | 31 +++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/app-shell/src/views/InterfaceListPage.tsx b/packages/app-shell/src/views/InterfaceListPage.tsx index c0c832836..22b5d4825 100644 --- a/packages/app-shell/src/views/InterfaceListPage.tsx +++ b/packages/app-shell/src/views/InterfaceListPage.tsx @@ -383,7 +383,10 @@ export function InterfaceListPage({ page, className, onConfigChange, reserveEdit showGroup: false, showColor: false, allowExport: false, - inlineEdit: false, + // Inline record editing is a page-authored property: a list block opts in + // via `userActions.editInline` (default off). When on, clicking a cell + // edits it with the dedicated field widgets, same as the object views. + inlineEdit: userActions.editInline === true, }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [objectDefName, viewDefJson, cfg]); diff --git a/packages/app-shell/src/views/ObjectView.tsx b/packages/app-shell/src/views/ObjectView.tsx index 759f035c5..efa3ad0ca 100644 --- a/packages/app-shell/src/views/ObjectView.tsx +++ b/packages/app-shell/src/views/ObjectView.tsx @@ -1416,6 +1416,9 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: an onHiddenFieldsChange={(hidden: string[]) => { persistViewPatch(viewDef.id, viewDef, { hiddenFields: hidden }); }} + onInlineEditChange={(next: boolean) => { + persistViewPatch(viewDef.id, viewDef, { inlineEdit: next }); + }} onColumnStateChange={(state: { order?: string[]; widths?: Record }) => { persistViewPatch(viewDef.id, viewDef, { columnState: state }); }} diff --git a/packages/plugin-list/src/ListView.tsx b/packages/plugin-list/src/ListView.tsx index 2cb579359..8cab0b1ba 100644 --- a/packages/plugin-list/src/ListView.tsx +++ b/packages/plugin-list/src/ListView.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder, SortBuilder, NavigationOverlay, GroupingEditor, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, RefreshIndicator, DataEmptyState } from '@object-ui/components'; import type { SortItem } from '@object-ui/components'; -import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Group, Paintbrush, Ruler, Inbox, Download, AlignJustify, Rows4, Rows3, Rows2, Share2, Printer, Plus, Trash2, CheckSquare, AlertTriangle, RotateCw, icons, type LucideIcon } from 'lucide-react'; +import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Pencil, Group, Paintbrush, Ruler, Inbox, Download, AlignJustify, Rows4, Rows3, Rows2, Share2, Printer, Plus, Trash2, CheckSquare, AlertTriangle, RotateCw, icons, type LucideIcon } from 'lucide-react'; import type { FilterGroup } from '@object-ui/components'; import { ViewSwitcherDropdown, ViewType } from './ViewSwitcher'; import { ViewSettingsPopover } from './components/ViewSettingsPopover'; @@ -31,6 +31,8 @@ export interface ListViewProps { onSearchChange?: (search: string) => void; /** Called when the user toggles fields via the Hide Fields popover. */ onHiddenFieldsChange?: (hidden: string[]) => void; + /** Called when the user toggles inline record editing in View settings. */ + onInlineEditChange?: (next: boolean) => void; /** Called when the user resizes/reorders columns in the underlying grid. */ onColumnStateChange?: (state: { order?: string[]; widths?: Record }) => void; /** Callback when a row/item is clicked (overrides NavigationConfig) */ @@ -322,6 +324,7 @@ export const ListView = React.forwardRef(({ onSortChange, onSearchChange, onHiddenFieldsChange, + onInlineEditChange, onColumnStateChange, onRowClick, showViewSwitcher: showViewSwitcherProp, @@ -1639,6 +1642,24 @@ export const ListView = React.forwardRef(({
)} + {/* Inline edit — toggle record editing for this (grid) view. Persists + `inlineEdit` on the view via onInlineEditChange. */} + {currentView === 'grid' && onInlineEditChange && !toolbarFlags.compactToolbar && ( + + )} {/* Hide Fields — hidden on mobile (collapsed into ViewSettingsPopover) */} {toolbarFlags.showHideFields && !toolbarFlags.compactToolbar && ( @@ -1982,6 +2003,9 @@ export const ListView = React.forwardRef(({ showHideFields={toolbarFlags.showHideFields} hiddenFields={hiddenFields} updateHiddenFields={updateHiddenFields} + showInlineEdit={currentView === 'grid'} + inlineEdit={!!schema.inlineEdit} + setInlineEdit={(v) => onInlineEditChange?.(v)} /> )} diff --git a/packages/plugin-list/src/components/ViewSettingsPopover.tsx b/packages/plugin-list/src/components/ViewSettingsPopover.tsx index 4a07d7203..a73a20994 100644 --- a/packages/plugin-list/src/components/ViewSettingsPopover.tsx +++ b/packages/plugin-list/src/components/ViewSettingsPopover.tsx @@ -70,6 +70,11 @@ export interface ViewSettingsPopoverProps { showHideFields?: boolean; hiddenFields?: Set; updateHiddenFields?: (next: Set) => void; + + /** Record editing — toggle inline cell editing (persists `inlineEdit` on the view). */ + showInlineEdit?: boolean; + inlineEdit?: boolean; + setInlineEdit?: (next: boolean) => void; } interface SectionProps { @@ -131,6 +136,9 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) { showHideFields, hiddenFields, updateHiddenFields, + showInlineEdit, + inlineEdit, + setInlineEdit, } = props; const [open, setOpen] = React.useState(false); @@ -141,6 +149,7 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) { !!rowColorConfig?.field, density && density.mode !== 'compact', (hiddenFields?.size ?? 0) > 0, + !!inlineEdit, ].filter(Boolean).length; const DensityIcon = @@ -266,6 +275,28 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) { )} + {showInlineEdit && setInlineEdit && ( +
+ +
+ )} + {showHideFields && hiddenFields && updateHiddenFields && (
Date: Tue, 30 Jun 2026 00:43:16 +0800 Subject: [PATCH 2/2] feat(types): declare userActions.editInline on ListViewSchema Mirror the framework spec change (UserActionsConfigSchema.editInline): the list toolbar/interface userActions gains an `editInline?: boolean`. This types the runtime read at InterfaceListPage (`userActions.editInline === true` -> inlineEdit) and documents inline editing as an author-controlled, default-off list property. Co-Authored-By: Claude Opus 4.8 --- packages/types/src/objectql.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/types/src/objectql.ts b/packages/types/src/objectql.ts index 8a61483cc..d813bd1f9 100644 --- a/packages/types/src/objectql.ts +++ b/packages/types/src/objectql.ts @@ -1936,6 +1936,12 @@ export interface ListViewSchema extends BaseSchema { filter?: boolean; rowHeight?: boolean; addRecordForm?: boolean; + /** + * Allow end users to edit records inline (click a cell → type-aware editor, + * the same widgets the form uses). Default off: a list is read-only unless + * the author opts in. Read by InterfaceListPage → `inlineEdit`. + */ + editInline?: boolean; buttons?: string[]; };