or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

content-display.mdcore-components.mdediting.mdindex.mdinteractive-features.mdlayout-scrolling.mdutilities.md

editing.mddocs/

0

# Editing Capabilities

1

2

Inline editing components for text and select inputs with validation support and row-level editing controls.

3

4

## Capabilities

5

6

### EditableTextCell

7

8

Inline editable text cell component with validation support.

9

10

```typescript { .api }

11

/**

12

* Inline editable text cell component

13

* @param props - EditableTextCell configuration props

14

* @returns EditableTextCell component

15

*/

16

function EditableTextCell(props: IEditableTextCell): React.FunctionComponent<IEditableTextCell>;

17

18

interface IEditableTextCell extends React.HTMLProps<HTMLDivElement> {

19

/** The current value of the text input */

20

value: string;

21

/** Row index of this text cell */

22

rowIndex: number;

23

/** Cell index of this text cell */

24

cellIndex: number;

25

/** Props to build the input */

26

props: EditableTextCellProps;

27

/** Event handler which fires when user changes the text in this cell */

28

handleTextInputChange: (

29

newValue: string,

30

event: React.FormEvent<HTMLInputElement>,

31

rowIndex: number,

32

cellIndex: number

33

) => void;

34

/** Accessible label of the text input */

35

inputAriaLabel: string;

36

/** Flag indicating if the text input is disabled */

37

isDisabled?: boolean;

38

}

39

40

interface EditableTextCellProps {

41

/** Name of the input */

42

name: string;

43

/** Value to display in the cell */

44

value: string;

45

/** Editable value (can differ from display value) */

46

editableValue?: string;

47

/** Whether the cell is valid */

48

isValid?: boolean;

49

/** Error text to display */

50

errorText?: string;

51

/** Arbitrary data to pass to the internal text input */

52

[key: string]: any;

53

}

54

```

55

56

**Usage Examples:**

57

58

```typescript

59

import { EditableTextCell } from "@patternfly/react-table";

60

61

// Basic editable text cell

62

<Td>

63

<EditableTextCell

64

value={rowData.name}

65

rowIndex={rowIndex}

66

cellIndex={0}

67

props={{

68

name: 'name',

69

value: rowData.name,

70

editableValue: editValues[`${rowIndex}-0`],

71

isValid: validationResults[`${rowIndex}-0`]?.isValid !== false

72

}}

73

handleTextInputChange={(newValue, event, rowIndex, cellIndex) => {

74

setEditValues(prev => ({

75

...prev,

76

[`${rowIndex}-${cellIndex}`]: newValue

77

}));

78

validateCell(rowIndex, cellIndex, newValue);

79

}}

80

inputAriaLabel={`Edit name for row ${rowIndex}`}

81

/>

82

</Td>

83

84

// Editable text cell with validation error

85

<Td>

86

<EditableTextCell

87

value={rowData.email}

88

rowIndex={rowIndex}

89

cellIndex={1}

90

props={{

91

name: 'email',

92

value: rowData.email,

93

editableValue: editValues[`${rowIndex}-1`],

94

isValid: false,

95

errorText: 'Please enter a valid email address',

96

}}

97

handleTextInputChange={handleEmailChange}

98

inputAriaLabel={`Edit email for row ${rowIndex}`}

99

isDisabled={!isEditing}

100

/>

101

</Td>

102

```

103

104

### EditableSelectInputCell

105

106

Inline editable select cell component with support for single and multi-select.

107

108

```typescript { .api }

109

/**

110

* Inline editable select cell component

111

* @param props - EditableSelectInputCell configuration props

112

* @returns EditableSelectInputCell component

113

*/

114

function EditableSelectInputCell(props: IEditableSelectInputCell): React.FunctionComponent<IEditableSelectInputCell>;

115

116

interface IEditableSelectInputCell extends Omit<React.HTMLProps<HTMLElement | HTMLDivElement>, 'onSelect' | 'onToggle'> {

117

/** Row index of this select input cell */

118

rowIndex: number;

119

/** Cell index of this select input cell */

120

cellIndex: number;

121

/** Props to build the select component */

122

props: EditableSelectInputProps;

123

/** Event handler which fires when user selects an option in this cell */

124

onSelect: (

125

event: React.MouseEvent | React.ChangeEvent,

126

newValue: any | any[],

127

rowIndex: number,

128

cellIndex: number,

129

isPlaceholder?: boolean

130

) => void;

131

/** Options to display in the expandable select menu */

132

options?: React.ReactElement<any>[];

133

/** Flag indicating the select input is disabled */

134

isDisabled?: boolean;

135

/** Flag indicating the toggle gets placeholder styles */

136

isPlaceholder?: boolean;

137

/** Current selected options to display as the read only value of the table cell */

138

selections?: any | any[];

139

/** Flag indicating the select menu is open */

140

isOpen?: boolean;

141

/** Event handler which fires when the select toggle is toggled */

142

onToggle?: (event: React.MouseEvent | undefined) => void;

143

/** Event handler which fires when the user clears the selections */

144

clearSelection?: (event: React.MouseEvent, rowIndex: number, cellIndex: number) => void;

145

}

146

147

interface EditableSelectInputProps {

148

/** Name of the select input */

149

name: string;

150

/** Value to display in the cell */

151

value: string | string[];

152

/** Flag controlling isOpen state of select */

153

isSelectOpen: boolean;

154

/** Single select option value for single select menus, or array for multi select */

155

selected: any | any[];

156

/** Array of react elements to display in the select menu */

157

options: React.ReactElement<any>[];

158

/** Props to be passed down to the select component */

159

editableSelectProps?: SelectProps;

160

/** Error text to display */

161

errorText?: string;

162

/** Arbitrary data to pass to the internal select component */

163

[key: string]: any;

164

}

165

```

166

167

**Usage Examples:**

168

169

```typescript

170

import { EditableSelectInputCell, SelectOption } from "@patternfly/react-table";

171

172

// Basic editable select cell

173

const statusOptions = [

174

<SelectOption key="active" value="active">Active</SelectOption>,

175

<SelectOption key="inactive" value="inactive">Inactive</SelectOption>,

176

<SelectOption key="pending" value="pending">Pending</SelectOption>

177

];

178

179

<Td>

180

<EditableSelectInputCell

181

rowIndex={rowIndex}

182

cellIndex={2}

183

props={{

184

name: 'status',

185

value: rowData.status,

186

isSelectOpen: openSelects[`${rowIndex}-2`] || false,

187

selected: editValues[`${rowIndex}-2`] || rowData.status,

188

options: statusOptions

189

}}

190

options={statusOptions}

191

selections={editValues[`${rowIndex}-2`] || rowData.status}

192

isOpen={openSelects[`${rowIndex}-2`] || false}

193

onSelect={(event, value, rowIndex, cellIndex) => {

194

setEditValues(prev => ({

195

...prev,

196

[`${rowIndex}-${cellIndex}`]: value

197

}));

198

setOpenSelects(prev => ({

199

...prev,

200

[`${rowIndex}-${cellIndex}`]: false

201

}));

202

}}

203

onToggle={(event) => {

204

setOpenSelects(prev => ({

205

...prev,

206

[`${rowIndex}-2`]: !prev[`${rowIndex}-2`]

207

}));

208

}}

209

/>

210

</Td>

211

212

// Multi-select editable cell

213

<Td>

214

<EditableSelectInputCell

215

rowIndex={rowIndex}

216

cellIndex={3}

217

props={{

218

name: 'tags',

219

value: rowData.tags,

220

isSelectOpen: openSelects[`${rowIndex}-3`] || false,

221

selected: editValues[`${rowIndex}-3`] || rowData.tags,

222

options: tagOptions,

223

editableSelectProps: { variant: 'checkbox' }

224

}}

225

options={tagOptions}

226

selections={editValues[`${rowIndex}-3`] || rowData.tags}

227

isOpen={openSelects[`${rowIndex}-3`] || false}

228

onSelect={handleMultiSelect}

229

onToggle={toggleMultiSelect}

230

clearSelection={(event, rowIndex, cellIndex) => {

231

setEditValues(prev => ({

232

...prev,

233

[`${rowIndex}-${cellIndex}`]: []

234

}));

235

}}

236

/>

237

</Td>

238

```

239

240

## Row-Level Editing

241

242

### Row Edit Configuration

243

244

```typescript { .api }

245

// Row editing event handler

246

type OnRowEdit = (

247

event: React.MouseEvent<HTMLButtonElement>,

248

type: RowEditType,

249

isEditable?: boolean,

250

rowIndex?: number,

251

validationErrors?: RowErrors

252

) => void;

253

254

// Row edit action types

255

type RowEditType = 'save' | 'cancel' | 'edit';

256

257

// Row validation errors

258

interface RowErrors {

259

[name: string]: string[];

260

}

261

262

// Row validation definition

263

interface IValidatorDef {

264

validator: (value: string) => boolean;

265

errorText: string;

266

name: string;

267

}

268

```

269

270

### Row Edit Properties

271

272

```typescript { .api }

273

// IRow interface includes editing properties

274

interface IRow extends RowType {

275

/** Whether the row is editable */

276

isEditable?: boolean;

277

/** Whether the row is valid */

278

isValid?: boolean;

279

/** Array of validation functions to run against every cell for a given row */

280

rowEditValidationRules?: IValidatorDef[];

281

/** Aria label for edit button in inline edit */

282

rowEditBtnAriaLabel?: (idx: number) => string;

283

/** Aria label for save button in inline edit */

284

rowSaveBtnAriaLabel?: (idx: number) => string;

285

/** Aria label for cancel button in inline edit */

286

rowCancelBtnAriaLabel?: (idx: number) => string;

287

}

288

```

289

290

**Usage Examples:**

291

292

```typescript

293

// Row with editing configuration

294

const editableRows = [

295

{

296

cells: ['John Doe', 'john@example.com', 'Active'],

297

isEditable: editingRows.includes(0),

298

isValid: rowValidation[0]?.isValid,

299

rowEditValidationRules: [

300

{

301

name: 'email',

302

validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),

303

errorText: 'Please enter a valid email address'

304

},

305

{

306

name: 'name',

307

validator: (value) => value.trim().length > 0,

308

errorText: 'Name is required'

309

}

310

],

311

rowEditBtnAriaLabel: (idx) => `Edit row ${idx}`,

312

rowSaveBtnAriaLabel: (idx) => `Save changes for row ${idx}`,

313

rowCancelBtnAriaLabel: (idx) => `Cancel editing row ${idx}`

314

}

315

];

316

317

// Row edit handlers

318

const handleRowEdit = (event, type, isEditable, rowIndex, validationErrors) => {

319

switch (type) {

320

case 'edit':

321

setEditingRows(prev => [...prev, rowIndex]);

322

break;

323

case 'save':

324

if (!validationErrors || Object.keys(validationErrors).length === 0) {

325

// Save the changes

326

saveRowChanges(rowIndex);

327

setEditingRows(prev => prev.filter(idx => idx !== rowIndex));

328

}

329

break;

330

case 'cancel':

331

// Discard changes

332

discardRowChanges(rowIndex);

333

setEditingRows(prev => prev.filter(idx => idx !== rowIndex));

334

break;

335

}

336

};

337

```

338

339

## Validation Support

340

341

### Cell-Level Validation

342

343

```typescript { .api }

344

// Validation function for individual cells

345

const validateCell = (rowIndex: number, cellIndex: number, value: string): boolean => {

346

const row = rows[rowIndex];

347

if (row.rowEditValidationRules) {

348

const rule = row.rowEditValidationRules[cellIndex];

349

if (rule && !rule.validator(value)) {

350

setValidationErrors(prev => ({

351

...prev,

352

[`${rowIndex}-${cellIndex}`]: rule.errorText

353

}));

354

return false;

355

}

356

}

357

358

// Clear validation error if valid

359

setValidationErrors(prev => {

360

const newErrors = { ...prev };

361

delete newErrors[`${rowIndex}-${cellIndex}`];

362

return newErrors;

363

});

364

return true;

365

};

366

```

367

368

### Row-Level Validation

369

370

```typescript { .api }

371

// Validation function for entire rows

372

const validateRow = (rowIndex: number): RowErrors | null => {

373

const row = rows[rowIndex];

374

const errors: RowErrors = {};

375

376

if (row.rowEditValidationRules) {

377

row.rowEditValidationRules.forEach((rule, cellIndex) => {

378

const cellValue = getCellValue(rowIndex, cellIndex);

379

if (!rule.validator(cellValue)) {

380

if (!errors[rule.name]) {

381

errors[rule.name] = [];

382

}

383

errors[rule.name].push(rule.errorText);

384

}

385

});

386

}

387

388

return Object.keys(errors).length > 0 ? errors : null;

389

};

390

```

391

392

## Edit State Management

393

394

### Managing Edit State

395

396

```typescript

397

// Example state management for table editing

398

const [editingRows, setEditingRows] = useState<number[]>([]);

399

const [editValues, setEditValues] = useState<Record<string, any>>({});

400

const [openSelects, setOpenSelects] = useState<Record<string, boolean>>({});

401

const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});

402

403

// Helper functions

404

const startEditing = (rowIndex: number) => {

405

setEditingRows(prev => [...prev, rowIndex]);

406

// Initialize edit values with current row values

407

const row = rows[rowIndex];

408

row.cells.forEach((cell, cellIndex) => {

409

setEditValues(prev => ({

410

...prev,

411

[`${rowIndex}-${cellIndex}`]: cell

412

}));

413

});

414

};

415

416

const saveChanges = (rowIndex: number) => {

417

const validationErrors = validateRow(rowIndex);

418

if (!validationErrors) {

419

// Apply changes to the row data

420

const updatedRows = [...rows];

421

updatedRows[rowIndex].cells = updatedRows[rowIndex].cells.map((_, cellIndex) =>

422

editValues[`${rowIndex}-${cellIndex}`] || _

423

);

424

setRows(updatedRows);

425

setEditingRows(prev => prev.filter(idx => idx !== rowIndex));

426

clearEditState(rowIndex);

427

}

428

};

429

430

const cancelEditing = (rowIndex: number) => {

431

setEditingRows(prev => prev.filter(idx => idx !== rowIndex));

432

clearEditState(rowIndex);

433

};

434

435

const clearEditState = (rowIndex: number) => {

436

// Clear edit values for this row

437

const keysToRemove = Object.keys(editValues).filter(key =>

438

key.startsWith(`${rowIndex}-`)

439

);

440

setEditValues(prev => {

441

const newState = { ...prev };

442

keysToRemove.forEach(key => delete newState[key]);

443

return newState;

444

});

445

446

// Clear validation errors for this row

447

const errorKeysToRemove = Object.keys(validationErrors).filter(key =>

448

key.startsWith(`${rowIndex}-`)

449

);

450

setValidationErrors(prev => {

451

const newState = { ...prev };

452

errorKeysToRemove.forEach(key => delete newState[key]);

453

return newState;

454

});

455

};

456

```