@liedekef/ftable 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ftable.esm.js CHANGED
@@ -1413,9 +1413,7 @@ class FTableFormBuilder {
1413
1413
  : [];
1414
1414
 
1415
1415
  // Support data-livesearch attribute on the virtual select as well as field.livesearch
1416
- const livesearch = field.livesearch
1417
- ?? (attributes['data-livesearch'] === 'true' || attributes['data-livesearch'] === true)
1418
- ?? false;
1416
+ const livesearch = field.livesearch ?? false;
1419
1417
 
1420
1418
  return this._buildCustomMultiSelect({
1421
1419
  containerId: fieldName,
@@ -2187,7 +2185,7 @@ class FTable extends FTableEventEmitter {
2187
2185
  initColumnWidths() {
2188
2186
  const visibleFields = this.columnList.filter(fieldName => {
2189
2187
  const field = this.options.fields[fieldName];
2190
- return field.visibility !== 'hidden' && field.visibility !== 'separator';
2188
+ return !field.action && field.visibility !== 'hidden' && field.visibility !== 'separator';
2191
2189
  });
2192
2190
 
2193
2191
  const count = visibleFields.length;
@@ -2206,7 +2204,7 @@ class FTable extends FTableEventEmitter {
2206
2204
  th: this.elements.table.querySelector(`[data-field-name="${fieldName}"]`),
2207
2205
  field: this.options.fields[fieldName]
2208
2206
  }))
2209
- .filter(item => item.th && item.field.visibility !== 'hidden' && item.field.visibility !== 'separator');
2207
+ .filter(item => item.th && !item.field.action && item.field.visibility !== 'hidden' && item.field.visibility !== 'separator');
2210
2208
 
2211
2209
  if (visibleHeaders.length === 0) return;
2212
2210
 
@@ -2341,8 +2339,17 @@ class FTable extends FTableEventEmitter {
2341
2339
  this.fieldList.forEach(fieldName => {
2342
2340
  const field = this.options.fields[fieldName];
2343
2341
  const isKeyField = field.key === true;
2344
-
2345
- if (isKeyField) {
2342
+ const isActionField = !!field.action; // action: 'select' | 'update' | 'clone' | 'delete'
2343
+
2344
+ if (isActionField) {
2345
+ // Action columns are always listed but never part of forms or sorting
2346
+ field.list = true;
2347
+ field.create = false;
2348
+ field.edit = false;
2349
+ field.sorting = false;
2350
+ field.searchable = false;
2351
+ field.visibility = field.visibility ?? 'visible';
2352
+ } else if (isKeyField) {
2346
2353
  if (field.create === undefined || !field.create) {
2347
2354
  field.create = true;
2348
2355
  field.type = 'hidden';
@@ -2367,6 +2374,13 @@ class FTable extends FTableEventEmitter {
2367
2374
  return field.list !== false;
2368
2375
  });
2369
2376
 
2377
+ // Track which actions are user-placed (via action columns in fields)
2378
+ this._userPlacedActions = new Set(
2379
+ this.fieldList
2380
+ .filter(name => this.options.fields[name].action)
2381
+ .map(name => this.options.fields[name].action)
2382
+ );
2383
+
2370
2384
  // Find key field
2371
2385
  this.keyField = this.fieldList.find(name => this.options.fields[name].key === true);
2372
2386
  if (!this.keyField) {
@@ -2377,6 +2391,7 @@ class FTable extends FTableEventEmitter {
2377
2391
  async resolveAsyncFieldOptions() {
2378
2392
  const promises = this.columnList.map(async (fieldName) => {
2379
2393
  const field = this.options.fields[fieldName];
2394
+ if (field.action) return; // Skip action columns
2380
2395
  const originalOptions = this.formBuilder.originalFieldOptions.get(fieldName);
2381
2396
 
2382
2397
  if (this.formBuilder.shouldResolveOptions(originalOptions)) {
@@ -2402,7 +2417,7 @@ class FTable extends FTableEventEmitter {
2402
2417
  for (const row of rows) {
2403
2418
  for (const fieldName of this.columnList) {
2404
2419
  const field = this.options.fields[fieldName];
2405
- if (!field.options) continue;
2420
+ if (field.action || !field.options) continue;
2406
2421
 
2407
2422
  const cell = row.querySelector(`td[data-field-name="${fieldName}"]`);
2408
2423
  if (!cell) continue;
@@ -2473,8 +2488,8 @@ class FTable extends FTableEventEmitter {
2473
2488
  parent: thead
2474
2489
  });
2475
2490
 
2476
- // Add selecting column if enabled
2477
- if (this.options.selecting && this.options.selectingCheckboxes) {
2491
+ // Add selecting column if enabled (only if not user-placed)
2492
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
2478
2493
  const selectHeader = FTableDOMHelper.create('th', {
2479
2494
  className: `ftable-command-column-header ftable-column-header-select`,
2480
2495
  parent: headerRow
@@ -2492,9 +2507,41 @@ class FTable extends FTableEventEmitter {
2492
2507
  }
2493
2508
  }
2494
2509
 
2495
- // Add data columns
2510
+ // Add data columns (including any user-placed action columns)
2496
2511
  this.columnList.forEach(fieldName => {
2497
2512
  const field = this.options.fields[fieldName];
2513
+
2514
+ // If this column is a user-placed action column, render an action header
2515
+ if (field.action) {
2516
+ const actionClassMap = {
2517
+ select: 'ftable-column-header-select',
2518
+ update: 'ftable-column-header-edit',
2519
+ clone: 'ftable-column-header-clone',
2520
+ delete: 'ftable-column-header-delete',
2521
+ };
2522
+ const th = FTableDOMHelper.create('th', {
2523
+ className: `ftable-command-column-header ${actionClassMap[field.action] || ''}`,
2524
+ parent: headerRow
2525
+ });
2526
+ if (field.title) {
2527
+ th.textContent = field.title;
2528
+ }
2529
+ // For select action with multiselect, add the select-all checkbox
2530
+ if (field.action === 'select' && this.options.selecting && this.options.selectingCheckboxes && this.options.multiselect) {
2531
+ const selectAllCheckbox = FTableDOMHelper.create('input', {
2532
+ attributes: { type: 'checkbox' },
2533
+ parent: th
2534
+ });
2535
+ selectAllCheckbox.addEventListener('change', () => {
2536
+ this.toggleSelectAll(selectAllCheckbox.checked);
2537
+ });
2538
+ }
2539
+ if (field.width) {
2540
+ th.style.width = field.width;
2541
+ }
2542
+ return;
2543
+ }
2544
+
2498
2545
  const th = FTableDOMHelper.create('th', {
2499
2546
  className: `ftable-column-header ${field.listClass || ''} ${field.listClassHeader || ''}`,
2500
2547
  attributes: { 'data-field-name': fieldName },
@@ -2543,22 +2590,22 @@ class FTable extends FTableEventEmitter {
2543
2590
  }
2544
2591
  });
2545
2592
 
2546
- // Add action columns
2547
- if (this.options.actions.updateAction) {
2593
+ // Add default action columns only if not user-placed
2594
+ if (this.options.actions.updateAction && !this._userPlacedActions.has('update')) {
2548
2595
  FTableDOMHelper.create('th', {
2549
2596
  className: 'ftable-command-column-header ftable-column-header-edit',
2550
2597
  parent: headerRow
2551
2598
  });
2552
2599
  }
2553
2600
 
2554
- if (this.options.actions.cloneAction) {
2601
+ if (this.options.actions.cloneAction && !this._userPlacedActions.has('clone')) {
2555
2602
  FTableDOMHelper.create('th', {
2556
2603
  className: 'ftable-command-column-header ftable-column-header-clone',
2557
2604
  parent: headerRow
2558
2605
  });
2559
2606
  }
2560
2607
 
2561
- if (this.options.actions.deleteAction) {
2608
+ if (this.options.actions.deleteAction && !this._userPlacedActions.has('delete')) {
2562
2609
  FTableDOMHelper.create('th', {
2563
2610
  className: 'ftable-command-column-header ftable-column-header-delete',
2564
2611
  parent: headerRow
@@ -2579,17 +2626,27 @@ class FTable extends FTableEventEmitter {
2579
2626
  parent: theadParent
2580
2627
  });
2581
2628
 
2582
- // Add empty cell for selecting column if enabled
2583
- if (this.options.selecting && this.options.selectingCheckboxes) {
2629
+ // Add empty cell for selecting column if enabled (only if not user-placed)
2630
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
2584
2631
  FTableDOMHelper.create('th', {
2585
2632
  className: 'ftable-toolbarsearch-column-header',
2586
2633
  parent: searchRow
2587
2634
  });
2588
2635
  }
2589
2636
 
2590
- // Add search input cells for data columns
2637
+ // Add search input cells for data columns (including user-placed action columns)
2591
2638
  for (const fieldName of this.columnList) {
2592
2639
  const field = this.options.fields[fieldName];
2640
+
2641
+ // Action columns get an empty search cell
2642
+ if (field.action) {
2643
+ FTableDOMHelper.create('th', {
2644
+ className: 'ftable-toolbarsearch-column-header ftable-command-column-header',
2645
+ parent: searchRow
2646
+ });
2647
+ continue;
2648
+ }
2649
+
2593
2650
  const isSearchable = field.searchable !== false;
2594
2651
 
2595
2652
  const th = FTableDOMHelper.create('th', {
@@ -2761,9 +2818,9 @@ class FTable extends FTableEventEmitter {
2761
2818
  parent: searchRow
2762
2819
  });
2763
2820
 
2764
- const actionCount = (this.options.actions.updateAction ? 1 : 0) +
2765
- (this.options.actions.deleteAction ? 1 : 0) +
2766
- (this.options.actions.cloneAction ? 1 : 0);
2821
+ const actionCount = (this.options.actions.updateAction && !this._userPlacedActions.has('update') ? 1 : 0) +
2822
+ (this.options.actions.deleteAction && !this._userPlacedActions.has('delete') ? 1 : 0) +
2823
+ (this.options.actions.cloneAction && !this._userPlacedActions.has('clone') ? 1 : 0);
2767
2824
 
2768
2825
  if (actionCount > 0) {
2769
2826
  resetTh.colSpan = actionCount;
@@ -2871,9 +2928,7 @@ class FTable extends FTableEventEmitter {
2871
2928
  }
2872
2929
 
2873
2930
  createCustomMultiSelectForSearch(fieldSearchName, fieldName, field, optionsSource, attributes) {
2874
- const livesearch = field.livesearch
2875
- ?? (attributes['data-livesearch'] === 'true' || attributes['data-livesearch'] === true)
2876
- ?? false;
2931
+ const livesearch = field.livesearch ?? false;
2877
2932
 
2878
2933
  return this.formBuilder._buildCustomMultiSelect({
2879
2934
  hiddenSelectId: fieldSearchName,
@@ -3114,9 +3169,10 @@ class FTable extends FTableEventEmitter {
3114
3169
 
3115
3170
  const settings = {};
3116
3171
  this.columnList.forEach(fieldName => {
3172
+ const field = this.options.fields[fieldName];
3173
+ if (field.action) return; // Action columns have no persistent state
3117
3174
  const th = this.elements.table.querySelector(`[data-field-name="${fieldName}"]`);
3118
3175
  if (th) {
3119
- const field = this.options.fields[fieldName];
3120
3176
  settings[fieldName] = {
3121
3177
  width: th.style.width || field.width || 'auto',
3122
3178
  visibility: field.visibility || 'visible'
@@ -3148,7 +3204,7 @@ class FTable extends FTableEventEmitter {
3148
3204
  const settings = JSON.parse(settingsJson);
3149
3205
  Object.entries(settings).forEach(([fieldName, config]) => {
3150
3206
  const field = this.options.fields[fieldName];
3151
- if (field) {
3207
+ if (field && !field.action) {
3152
3208
  if (config.width) field.width = config.width;
3153
3209
  if (config.visibility) field.visibility = config.visibility;
3154
3210
  }
@@ -3450,6 +3506,7 @@ class FTable extends FTableEventEmitter {
3450
3506
 
3451
3507
  this.columnList.forEach(fieldName => {
3452
3508
  const field = this.options.fields[fieldName];
3509
+ if (field.action) return; // Action columns don't appear in column picker
3453
3510
  const isVisible = field.visibility !== 'hidden';
3454
3511
  const isFixed = field.visibility === 'fixed';
3455
3512
  const isSeparator = field.visibility === 'separator';
@@ -3862,26 +3919,45 @@ class FTable extends FTableEventEmitter {
3862
3919
  // Store record data
3863
3920
  row.recordData = record;
3864
3921
 
3865
- // Add selecting checkbox if enabled
3866
- if (this.options.selecting && this.options.selectingCheckboxes) {
3922
+ // Add selecting checkbox if enabled (only if not user-placed)
3923
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
3867
3924
  this.addSelectingCell(row);
3868
3925
  }
3869
3926
 
3870
- // Add data cells
3927
+ // Add data cells (including user-placed action columns)
3871
3928
  this.columnList.forEach(fieldName => {
3872
- this.addDataCell(row, record, fieldName);
3929
+ const field = this.options.fields[fieldName];
3930
+ if (field.action) {
3931
+ // Render inline action cell
3932
+ switch (field.action) {
3933
+ case 'select':
3934
+ this.addSelectingCell(row);
3935
+ break;
3936
+ case 'update':
3937
+ this.addEditCell(row);
3938
+ break;
3939
+ case 'clone':
3940
+ this.addCloneCell(row);
3941
+ break;
3942
+ case 'delete':
3943
+ this.addDeleteCell(row);
3944
+ break;
3945
+ }
3946
+ } else {
3947
+ this.addDataCell(row, record, fieldName);
3948
+ }
3873
3949
  });
3874
3950
 
3875
- // Add action cells
3876
- if (this.options.actions.updateAction) {
3951
+ // Add default action cells only if not user-placed
3952
+ if (this.options.actions.updateAction && !this._userPlacedActions.has('update')) {
3877
3953
  this.addEditCell(row);
3878
3954
  }
3879
3955
 
3880
- if (this.options.actions.cloneAction) {
3956
+ if (this.options.actions.cloneAction && !this._userPlacedActions.has('clone')) {
3881
3957
  this.addCloneCell(row);
3882
3958
  }
3883
3959
 
3884
- if (this.options.actions.deleteAction) {
3960
+ if (this.options.actions.deleteAction && !this._userPlacedActions.has('delete')) {
3885
3961
  this.addDeleteCell(row);
3886
3962
  }
3887
3963
 
package/ftable.js CHANGED
@@ -1414,9 +1414,7 @@ class FTableFormBuilder {
1414
1414
  : [];
1415
1415
 
1416
1416
  // Support data-livesearch attribute on the virtual select as well as field.livesearch
1417
- const livesearch = field.livesearch
1418
- ?? (attributes['data-livesearch'] === 'true' || attributes['data-livesearch'] === true)
1419
- ?? false;
1417
+ const livesearch = field.livesearch ?? false;
1420
1418
 
1421
1419
  return this._buildCustomMultiSelect({
1422
1420
  containerId: fieldName,
@@ -2188,7 +2186,7 @@ class FTable extends FTableEventEmitter {
2188
2186
  initColumnWidths() {
2189
2187
  const visibleFields = this.columnList.filter(fieldName => {
2190
2188
  const field = this.options.fields[fieldName];
2191
- return field.visibility !== 'hidden' && field.visibility !== 'separator';
2189
+ return !field.action && field.visibility !== 'hidden' && field.visibility !== 'separator';
2192
2190
  });
2193
2191
 
2194
2192
  const count = visibleFields.length;
@@ -2207,7 +2205,7 @@ class FTable extends FTableEventEmitter {
2207
2205
  th: this.elements.table.querySelector(`[data-field-name="${fieldName}"]`),
2208
2206
  field: this.options.fields[fieldName]
2209
2207
  }))
2210
- .filter(item => item.th && item.field.visibility !== 'hidden' && item.field.visibility !== 'separator');
2208
+ .filter(item => item.th && !item.field.action && item.field.visibility !== 'hidden' && item.field.visibility !== 'separator');
2211
2209
 
2212
2210
  if (visibleHeaders.length === 0) return;
2213
2211
 
@@ -2342,8 +2340,17 @@ class FTable extends FTableEventEmitter {
2342
2340
  this.fieldList.forEach(fieldName => {
2343
2341
  const field = this.options.fields[fieldName];
2344
2342
  const isKeyField = field.key === true;
2345
-
2346
- if (isKeyField) {
2343
+ const isActionField = !!field.action; // action: 'select' | 'update' | 'clone' | 'delete'
2344
+
2345
+ if (isActionField) {
2346
+ // Action columns are always listed but never part of forms or sorting
2347
+ field.list = true;
2348
+ field.create = false;
2349
+ field.edit = false;
2350
+ field.sorting = false;
2351
+ field.searchable = false;
2352
+ field.visibility = field.visibility ?? 'visible';
2353
+ } else if (isKeyField) {
2347
2354
  if (field.create === undefined || !field.create) {
2348
2355
  field.create = true;
2349
2356
  field.type = 'hidden';
@@ -2368,6 +2375,13 @@ class FTable extends FTableEventEmitter {
2368
2375
  return field.list !== false;
2369
2376
  });
2370
2377
 
2378
+ // Track which actions are user-placed (via action columns in fields)
2379
+ this._userPlacedActions = new Set(
2380
+ this.fieldList
2381
+ .filter(name => this.options.fields[name].action)
2382
+ .map(name => this.options.fields[name].action)
2383
+ );
2384
+
2371
2385
  // Find key field
2372
2386
  this.keyField = this.fieldList.find(name => this.options.fields[name].key === true);
2373
2387
  if (!this.keyField) {
@@ -2378,6 +2392,7 @@ class FTable extends FTableEventEmitter {
2378
2392
  async resolveAsyncFieldOptions() {
2379
2393
  const promises = this.columnList.map(async (fieldName) => {
2380
2394
  const field = this.options.fields[fieldName];
2395
+ if (field.action) return; // Skip action columns
2381
2396
  const originalOptions = this.formBuilder.originalFieldOptions.get(fieldName);
2382
2397
 
2383
2398
  if (this.formBuilder.shouldResolveOptions(originalOptions)) {
@@ -2403,7 +2418,7 @@ class FTable extends FTableEventEmitter {
2403
2418
  for (const row of rows) {
2404
2419
  for (const fieldName of this.columnList) {
2405
2420
  const field = this.options.fields[fieldName];
2406
- if (!field.options) continue;
2421
+ if (field.action || !field.options) continue;
2407
2422
 
2408
2423
  const cell = row.querySelector(`td[data-field-name="${fieldName}"]`);
2409
2424
  if (!cell) continue;
@@ -2474,8 +2489,8 @@ class FTable extends FTableEventEmitter {
2474
2489
  parent: thead
2475
2490
  });
2476
2491
 
2477
- // Add selecting column if enabled
2478
- if (this.options.selecting && this.options.selectingCheckboxes) {
2492
+ // Add selecting column if enabled (only if not user-placed)
2493
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
2479
2494
  const selectHeader = FTableDOMHelper.create('th', {
2480
2495
  className: `ftable-command-column-header ftable-column-header-select`,
2481
2496
  parent: headerRow
@@ -2493,9 +2508,41 @@ class FTable extends FTableEventEmitter {
2493
2508
  }
2494
2509
  }
2495
2510
 
2496
- // Add data columns
2511
+ // Add data columns (including any user-placed action columns)
2497
2512
  this.columnList.forEach(fieldName => {
2498
2513
  const field = this.options.fields[fieldName];
2514
+
2515
+ // If this column is a user-placed action column, render an action header
2516
+ if (field.action) {
2517
+ const actionClassMap = {
2518
+ select: 'ftable-column-header-select',
2519
+ update: 'ftable-column-header-edit',
2520
+ clone: 'ftable-column-header-clone',
2521
+ delete: 'ftable-column-header-delete',
2522
+ };
2523
+ const th = FTableDOMHelper.create('th', {
2524
+ className: `ftable-command-column-header ${actionClassMap[field.action] || ''}`,
2525
+ parent: headerRow
2526
+ });
2527
+ if (field.title) {
2528
+ th.textContent = field.title;
2529
+ }
2530
+ // For select action with multiselect, add the select-all checkbox
2531
+ if (field.action === 'select' && this.options.selecting && this.options.selectingCheckboxes && this.options.multiselect) {
2532
+ const selectAllCheckbox = FTableDOMHelper.create('input', {
2533
+ attributes: { type: 'checkbox' },
2534
+ parent: th
2535
+ });
2536
+ selectAllCheckbox.addEventListener('change', () => {
2537
+ this.toggleSelectAll(selectAllCheckbox.checked);
2538
+ });
2539
+ }
2540
+ if (field.width) {
2541
+ th.style.width = field.width;
2542
+ }
2543
+ return;
2544
+ }
2545
+
2499
2546
  const th = FTableDOMHelper.create('th', {
2500
2547
  className: `ftable-column-header ${field.listClass || ''} ${field.listClassHeader || ''}`,
2501
2548
  attributes: { 'data-field-name': fieldName },
@@ -2544,22 +2591,22 @@ class FTable extends FTableEventEmitter {
2544
2591
  }
2545
2592
  });
2546
2593
 
2547
- // Add action columns
2548
- if (this.options.actions.updateAction) {
2594
+ // Add default action columns only if not user-placed
2595
+ if (this.options.actions.updateAction && !this._userPlacedActions.has('update')) {
2549
2596
  FTableDOMHelper.create('th', {
2550
2597
  className: 'ftable-command-column-header ftable-column-header-edit',
2551
2598
  parent: headerRow
2552
2599
  });
2553
2600
  }
2554
2601
 
2555
- if (this.options.actions.cloneAction) {
2602
+ if (this.options.actions.cloneAction && !this._userPlacedActions.has('clone')) {
2556
2603
  FTableDOMHelper.create('th', {
2557
2604
  className: 'ftable-command-column-header ftable-column-header-clone',
2558
2605
  parent: headerRow
2559
2606
  });
2560
2607
  }
2561
2608
 
2562
- if (this.options.actions.deleteAction) {
2609
+ if (this.options.actions.deleteAction && !this._userPlacedActions.has('delete')) {
2563
2610
  FTableDOMHelper.create('th', {
2564
2611
  className: 'ftable-command-column-header ftable-column-header-delete',
2565
2612
  parent: headerRow
@@ -2580,17 +2627,27 @@ class FTable extends FTableEventEmitter {
2580
2627
  parent: theadParent
2581
2628
  });
2582
2629
 
2583
- // Add empty cell for selecting column if enabled
2584
- if (this.options.selecting && this.options.selectingCheckboxes) {
2630
+ // Add empty cell for selecting column if enabled (only if not user-placed)
2631
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
2585
2632
  FTableDOMHelper.create('th', {
2586
2633
  className: 'ftable-toolbarsearch-column-header',
2587
2634
  parent: searchRow
2588
2635
  });
2589
2636
  }
2590
2637
 
2591
- // Add search input cells for data columns
2638
+ // Add search input cells for data columns (including user-placed action columns)
2592
2639
  for (const fieldName of this.columnList) {
2593
2640
  const field = this.options.fields[fieldName];
2641
+
2642
+ // Action columns get an empty search cell
2643
+ if (field.action) {
2644
+ FTableDOMHelper.create('th', {
2645
+ className: 'ftable-toolbarsearch-column-header ftable-command-column-header',
2646
+ parent: searchRow
2647
+ });
2648
+ continue;
2649
+ }
2650
+
2594
2651
  const isSearchable = field.searchable !== false;
2595
2652
 
2596
2653
  const th = FTableDOMHelper.create('th', {
@@ -2762,9 +2819,9 @@ class FTable extends FTableEventEmitter {
2762
2819
  parent: searchRow
2763
2820
  });
2764
2821
 
2765
- const actionCount = (this.options.actions.updateAction ? 1 : 0) +
2766
- (this.options.actions.deleteAction ? 1 : 0) +
2767
- (this.options.actions.cloneAction ? 1 : 0);
2822
+ const actionCount = (this.options.actions.updateAction && !this._userPlacedActions.has('update') ? 1 : 0) +
2823
+ (this.options.actions.deleteAction && !this._userPlacedActions.has('delete') ? 1 : 0) +
2824
+ (this.options.actions.cloneAction && !this._userPlacedActions.has('clone') ? 1 : 0);
2768
2825
 
2769
2826
  if (actionCount > 0) {
2770
2827
  resetTh.colSpan = actionCount;
@@ -2872,9 +2929,7 @@ class FTable extends FTableEventEmitter {
2872
2929
  }
2873
2930
 
2874
2931
  createCustomMultiSelectForSearch(fieldSearchName, fieldName, field, optionsSource, attributes) {
2875
- const livesearch = field.livesearch
2876
- ?? (attributes['data-livesearch'] === 'true' || attributes['data-livesearch'] === true)
2877
- ?? false;
2932
+ const livesearch = field.livesearch ?? false;
2878
2933
 
2879
2934
  return this.formBuilder._buildCustomMultiSelect({
2880
2935
  hiddenSelectId: fieldSearchName,
@@ -3115,9 +3170,10 @@ class FTable extends FTableEventEmitter {
3115
3170
 
3116
3171
  const settings = {};
3117
3172
  this.columnList.forEach(fieldName => {
3173
+ const field = this.options.fields[fieldName];
3174
+ if (field.action) return; // Action columns have no persistent state
3118
3175
  const th = this.elements.table.querySelector(`[data-field-name="${fieldName}"]`);
3119
3176
  if (th) {
3120
- const field = this.options.fields[fieldName];
3121
3177
  settings[fieldName] = {
3122
3178
  width: th.style.width || field.width || 'auto',
3123
3179
  visibility: field.visibility || 'visible'
@@ -3149,7 +3205,7 @@ class FTable extends FTableEventEmitter {
3149
3205
  const settings = JSON.parse(settingsJson);
3150
3206
  Object.entries(settings).forEach(([fieldName, config]) => {
3151
3207
  const field = this.options.fields[fieldName];
3152
- if (field) {
3208
+ if (field && !field.action) {
3153
3209
  if (config.width) field.width = config.width;
3154
3210
  if (config.visibility) field.visibility = config.visibility;
3155
3211
  }
@@ -3451,6 +3507,7 @@ class FTable extends FTableEventEmitter {
3451
3507
 
3452
3508
  this.columnList.forEach(fieldName => {
3453
3509
  const field = this.options.fields[fieldName];
3510
+ if (field.action) return; // Action columns don't appear in column picker
3454
3511
  const isVisible = field.visibility !== 'hidden';
3455
3512
  const isFixed = field.visibility === 'fixed';
3456
3513
  const isSeparator = field.visibility === 'separator';
@@ -3863,26 +3920,45 @@ class FTable extends FTableEventEmitter {
3863
3920
  // Store record data
3864
3921
  row.recordData = record;
3865
3922
 
3866
- // Add selecting checkbox if enabled
3867
- if (this.options.selecting && this.options.selectingCheckboxes) {
3923
+ // Add selecting checkbox if enabled (only if not user-placed)
3924
+ if (this.options.selecting && this.options.selectingCheckboxes && !this._userPlacedActions.has('select')) {
3868
3925
  this.addSelectingCell(row);
3869
3926
  }
3870
3927
 
3871
- // Add data cells
3928
+ // Add data cells (including user-placed action columns)
3872
3929
  this.columnList.forEach(fieldName => {
3873
- this.addDataCell(row, record, fieldName);
3930
+ const field = this.options.fields[fieldName];
3931
+ if (field.action) {
3932
+ // Render inline action cell
3933
+ switch (field.action) {
3934
+ case 'select':
3935
+ this.addSelectingCell(row);
3936
+ break;
3937
+ case 'update':
3938
+ this.addEditCell(row);
3939
+ break;
3940
+ case 'clone':
3941
+ this.addCloneCell(row);
3942
+ break;
3943
+ case 'delete':
3944
+ this.addDeleteCell(row);
3945
+ break;
3946
+ }
3947
+ } else {
3948
+ this.addDataCell(row, record, fieldName);
3949
+ }
3874
3950
  });
3875
3951
 
3876
- // Add action cells
3877
- if (this.options.actions.updateAction) {
3952
+ // Add default action cells only if not user-placed
3953
+ if (this.options.actions.updateAction && !this._userPlacedActions.has('update')) {
3878
3954
  this.addEditCell(row);
3879
3955
  }
3880
3956
 
3881
- if (this.options.actions.cloneAction) {
3957
+ if (this.options.actions.cloneAction && !this._userPlacedActions.has('clone')) {
3882
3958
  this.addCloneCell(row);
3883
3959
  }
3884
3960
 
3885
- if (this.options.actions.deleteAction) {
3961
+ if (this.options.actions.deleteAction && !this._userPlacedActions.has('delete')) {
3886
3962
  this.addDeleteCell(row);
3887
3963
  }
3888
3964