✨ feat(vscode): 代码阅读 React 组件卡片按钩子分区:状态 / 引用 / 读取的数据 / 派生值 / 副作用 / 事件处理;副作用、处理函数成子卡片放进卡片那一行下面(库规则新增把子块放进属性表某一行的机制,事件处理表点名称展开)

This commit is contained in:
cruldra committed 2026-10-03 03:11:46 +08:00
1 parent 15f3a6600d
commit ee807ec7a4
19 files changed
+1057 -56

No files matched your search

@@ -1,5 +1,5 @@
import type { ReactElement } from 'react'
import type { Inline, InlinePart, ItemDiff, ItemsDiff, PropertyItem, PropertyRow, Removed, RowsDiff } from './types'
import type { CSSProperties, ReactElement, ReactNode } from 'react'
import type { BlockView, Inline, InlinePart, ItemDiff, ItemsDiff, KindBadge, PropertyItem, PropertyRow, Removed, RowsDiff } from './types'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { Fragment } from 'react'
import { DiffInline, DiffText } from './DiffText'
@@ -17,6 +17,23 @@ function gridTemplate(count: number): string {
return [...fixed, 'minmax(0, 1fr)'].join(' ')
}
/**
* 库规则放进卡片属性表里的子块(BlockSlot):blocks 是卡片的这些子块,render 把一行下面的几块画成一层块(同块树),
* onToggle 是点子行第一格时展开 / 收起讲它的那一块(点子行才展开的,见 BlockSlot.item)。
*/
export interface GridSlots {
blocks: BlockView[]
render: (blocks: BlockView[]) => ReactNode
onToggle: (block: BlockView) => void
}
/** 表格里一个子行讲的子块:展开着没有,它的种类(展开时这一行淡淡垫上它的颜色),点第一格时怎么切换。 */
interface ItemToggle {
open: boolean
badge?: KindBadge
onToggle: () => void
}
/** 眼下折叠着哪些属性行、子行,点折叠箭头时切换哪个。 */
interface Folding {
collapsed: ReadonlySet<string>
@@ -137,21 +154,38 @@ function PropertyItems({ parentId, items, folding, marks }: { parentId: string,
* 列宽按整列的内容定(见 tableTemplate),最后一列分剩下的,长了在格子里换行。一行译文下面摊开实参的表也用它;
* 行带 table(实参的值里跨几行写的调用)时,那张表排在最后一格里、文字下面,往右缩一点,一层层往下套。
* 标差异时(marks)有加行、删行的表每行第一格前多一个记号位(+ / −,没变的空着),加的行淡绿底,删的行放回原来的位置、淡红底划掉。
* 讲某个子块的行(toggles,React 组件的事件处理表)第一格是按钮,点它在表下面展开那一块,展开时这一行淡淡垫上那一块的种类色。
*/
export function PropertyTable({ parentId, columns, items, marks }: { parentId: string, columns: string[], items: PropertyItem[], marks?: ItemsDiff }): ReactElement {
export function PropertyTable({ parentId, columns, items, marks, toggles }: {
parentId: string
columns: string[]
items: PropertyItem[]
marks?: ItemsDiff
/** 按子行下标:讲它的子块(点第一格展开,见 GridSlots) */
toggles?: ReadonlyMap<number, ItemToggle>
}): ReactElement {
// 有加行、删行的表每行第一格前留一个记号位
const signed = marks !== undefined && (marks.removed.length > 0 || Object.values(marks.changed).some(mark => mark.added))
/** 一行:删掉的(change 是 del)不带 id,套着的表不再标 */
const tableRow = (item: PropertyItem, id: string, change: 'add' | 'del' | undefined, mark: ItemDiff | undefined): ReactElement => {
/** 一行:删掉的(change 是 del)不带 id,套着的表不再标;toggle 是这一行讲的子块,第一格成了展开它的按钮 */
const tableRow = (item: PropertyItem, id: string, change: 'add' | 'del' | undefined, mark: ItemDiff | undefined, toggle?: ItemToggle): ReactElement => {
const cells = tableCells(item, columns)
const text = (cell: Inline[], i: number): ReactElement => (
<>
{signed && i === 0 && <span className="cr-diff-sign" data-change={change} aria-hidden>{SIGNS[change ?? 'none']}</span>}
<CellText cell={cell} parts={mark?.cells?.[i]} />
{toggle && i === 0
? (
<button type="button" className="cr-item-toggle" aria-expanded={toggle.open} aria-label={`${toggle.open ? '收起' : '展开'}${inlineText(cell)}`} onClick={toggle.onToggle}>
<CellText cell={cell} parts={mark?.cells?.[i]} />
</button>
)
: <CellText cell={cell} parts={mark?.cells?.[i]} />}
</>
)
const tint = toggle?.open && toggle.badge
? { 'data-badge': '', 'style': { '--kind-dark': toggle.badge.color.dark, '--kind-light': toggle.badge.color.light } as CSSProperties }
: {}
return (
<div className="cr-table-row" role="row" data-id={change === 'del' ? undefined : id} data-diff={change}>
<div className="cr-table-row" role="row" data-id={change === 'del' ? undefined : id} data-diff={change} data-open={toggle?.open ? '' : undefined} {...tint}>
{cells.map((cell, i) => item.table && i === cells.length - 1
? (
<div key={i} className="cr-table-cell flex min-w-0 flex-col gap-[3px]" role="cell">
@@ -176,7 +210,7 @@ export function PropertyTable({ parentId, columns, items, marks }: { parentId: s
const mark = marks?.changed[index]
return (
<Fragment key={index}>
{tableRow(item, propertyItemId(parentId, index), mark?.added && 'add', mark)}
{tableRow(item, propertyItemId(parentId, index), mark?.added && 'add', mark, toggles?.get(index))}
{removed(index)}
</Fragment>
)
@@ -191,13 +225,15 @@ export function PropertyTable({ parentId, columns, items, marks }: { parentId: s
* 给了列名的(输入参数、数据表的字段)排成表格,没给的排成列表。
* 标差异时(marks)新加的行淡绿底、左边 +,删掉的行放回原来的位置、淡红底划掉、左边 −,值改了的按旧值 → 新值或逐词标。
*/
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks }: {
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, slots }: {
blockId: string
rows: PropertyRow[]
collapsed: ReadonlySet<string>
onToggleFold: (id: string) => void
/** 标差异时这张属性表改了哪些行 */
marks?: RowsDiff
/** 放进这张属性表里的子块(库规则的卡片),见 GridSlots */
slots?: GridSlots
}): ReactElement {
const removed = (after: number): ReactElement[] => removedAt(marks?.removed, after)
.map(({ value, index }) => <RemovedRow key={`removed-${index}`} id={`${blockId}/x${index}`} row={value} />)
@@ -210,10 +246,16 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks }:
const folded = collapsed.has(id)
const Chevron = folded ? ChevronRight : ChevronDown
const mark = marks?.changed[index]
const placed = slots?.blocks.filter(block => block.slot?.row === index) ?? []
// 点子行才展开的子块只画展开着的;别的放进这一行的子块总是画
const shownBlocks = placed.filter(block => block.slot?.item === undefined || !collapsed.has(block.id))
const toggles = new Map(placed.flatMap((block): [number, ItemToggle][] => block.slot?.item === undefined
? []
: [[block.slot.item, { open: !collapsed.has(block.id), badge: block.badge, onToggle: () => slots?.onToggle(block) }]]))
return (
<Fragment key={index}>
<div className="cr-prop-row grid grid-cols-subgrid" data-id={id} data-diff={mark?.added && 'add'}>
{layout
{layout || placed.length > 0
? (
<button
type="button"
@@ -229,8 +271,9 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks }:
: <span className="cr-prop-label flex h-5 items-center">{row.label}</span>}
{mark?.value ? <DiffText diff={mark.value} className="min-w-0" /> : <TextPreview text={row.value} className="min-w-0" />}
</div>
{!folded && layout === 'table' && <PropertyTable parentId={id} columns={row.columns ?? []} items={row.items ?? []} marks={mark?.items} />}
{!folded && layout === 'table' && <PropertyTable parentId={id} columns={row.columns ?? []} items={row.items ?? []} marks={mark?.items} toggles={toggles} />}
{!folded && layout === 'list' && <PropertyItems parentId={id} items={row.items ?? []} folding={{ collapsed, onToggleFold }} marks={mark?.items} />}
{!folded && slots && shownBlocks.length > 0 && <div className="cr-slot">{slots.render(shownBlocks)}</div>}
{removed(index)}
</Fragment>
)