@liedekef/ftable 1.3.6 → 1.3.7

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.
Files changed (39) hide show
  1. package/ftable.esm.js +105 -252
  2. package/ftable.js +105 -252
  3. package/ftable.min.js +2 -2
  4. package/ftable.umd.js +105 -252
  5. package/localization/ftable.nl.js +4 -1
  6. package/package.json +1 -1
  7. package/themes/basic/ftable_basic.css +15 -6
  8. package/themes/basic/ftable_basic.min.css +1 -1
  9. package/themes/ftable_theme_base.less +17 -6
  10. package/themes/lightcolor/blue/ftable.css +15 -6
  11. package/themes/lightcolor/blue/ftable.min.css +1 -1
  12. package/themes/lightcolor/gray/ftable.css +15 -6
  13. package/themes/lightcolor/gray/ftable.min.css +1 -1
  14. package/themes/lightcolor/green/ftable.css +15 -6
  15. package/themes/lightcolor/green/ftable.min.css +1 -1
  16. package/themes/lightcolor/orange/ftable.css +15 -6
  17. package/themes/lightcolor/orange/ftable.min.css +1 -1
  18. package/themes/lightcolor/red/ftable.css +15 -6
  19. package/themes/lightcolor/red/ftable.min.css +1 -1
  20. package/themes/metro/blue/ftable.css +15 -6
  21. package/themes/metro/blue/ftable.min.css +1 -1
  22. package/themes/metro/brown/ftable.css +15 -6
  23. package/themes/metro/brown/ftable.min.css +1 -1
  24. package/themes/metro/crimson/ftable.css +15 -6
  25. package/themes/metro/crimson/ftable.min.css +1 -1
  26. package/themes/metro/darkgray/ftable.css +15 -6
  27. package/themes/metro/darkgray/ftable.min.css +1 -1
  28. package/themes/metro/darkorange/ftable.css +15 -6
  29. package/themes/metro/darkorange/ftable.min.css +1 -1
  30. package/themes/metro/green/ftable.css +15 -6
  31. package/themes/metro/green/ftable.min.css +1 -1
  32. package/themes/metro/lightgray/ftable.css +15 -6
  33. package/themes/metro/lightgray/ftable.min.css +1 -1
  34. package/themes/metro/pink/ftable.css +15 -6
  35. package/themes/metro/pink/ftable.min.css +1 -1
  36. package/themes/metro/purple/ftable.css +15 -6
  37. package/themes/metro/purple/ftable.min.css +1 -1
  38. package/themes/metro/red/ftable.css +15 -6
  39. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.umd.js CHANGED
@@ -34,7 +34,8 @@
34
34
  printTable: '🖨️ Print',
35
35
  cloneRecord: 'Clone Record',
36
36
  resetTable: 'Reset table',
37
- resetTableConfirm: 'This will reset all columns, pagesize, sorting to their defaults. Do you want to continue?',
37
+ resetTableConfirm: 'This will reset column visibility, column widths and page size to their defaults. Do you want to continue?',
38
+ resetTableTooltip: 'Resets column visibility, column widths and page size to defaults. Sorting is not affected.',
38
39
  resetSearch: 'Reset'
39
40
  };
40
41
 
@@ -2018,7 +2019,8 @@ class FTable extends FTableEventEmitter {
2018
2019
  saveUserPreferences: true,
2019
2020
  saveUserPreferencesMethod: 'localStorage',
2020
2021
  defaultSorting: '',
2021
- tableReset: false,
2022
+ tableResetButton: false,
2023
+ sortingResetButton: false,
2022
2024
 
2023
2025
  // Paging
2024
2026
  paging: false,
@@ -2228,6 +2230,23 @@ class FTable extends FTableEventEmitter {
2228
2230
  this.createPageSizeSelector();
2229
2231
  }
2230
2232
 
2233
+ // Table reset button — resets column visibility/widths and pageSize (not sorting)
2234
+ if (this.options.tableResetButton) {
2235
+ const resetTableBtn = FTableDOMHelper.create('button', {
2236
+ className: 'ftable-toolbar-item ftable-table-reset-btn',
2237
+ textContent: this.options.messages.resetTable || 'Reset table',
2238
+ title: this.options.messages.resetTableTooltip || 'Resets column visibility, column widths and page size to defaults.',
2239
+ type: 'button',
2240
+ parent: this.elements.rightArea
2241
+ });
2242
+ resetTableBtn.addEventListener('click', (e) => {
2243
+ e.preventDefault();
2244
+ const msg = this.options.messages.resetTableConfirm;
2245
+ this.modals.resetTable.setContent(`<p>${msg}</p>`);
2246
+ this.modals.resetTable.show();
2247
+ });
2248
+ }
2249
+
2231
2250
  }
2232
2251
 
2233
2252
  createPageSizeSelector() {
@@ -3197,6 +3216,10 @@ class FTable extends FTableEventEmitter {
3197
3216
  this.createInfoModal();
3198
3217
  this.createLoadingModal();
3199
3218
 
3219
+ if (this.options.tableResetButton) {
3220
+ this.createResetTableModal();
3221
+ }
3222
+
3200
3223
  // Initialize them (create DOM) once
3201
3224
  Object.values(this.modals).forEach(modal => modal.create());
3202
3225
  }
@@ -3282,6 +3305,34 @@ class FTable extends FTableEventEmitter {
3282
3305
  });
3283
3306
  }
3284
3307
 
3308
+ createResetTableModal() {
3309
+ this.modals.resetTable = new FtableModal({
3310
+ parent: this.elements.mainContainer,
3311
+ title: this.options.messages.resetTable || 'Reset table',
3312
+ className: 'ftable-reset-table-modal',
3313
+ closeOnOverlayClick: this.options.closeOnOverlayClick,
3314
+ buttons: [
3315
+ {
3316
+ text: this.options.messages.cancel,
3317
+ className: 'ftable-dialog-cancelbutton',
3318
+ onClick: () => this.modals.resetTable.close()
3319
+ },
3320
+ {
3321
+ text: this.options.messages.yes,
3322
+ className: 'ftable-dialog-savebutton',
3323
+ onClick: () => {
3324
+ this.userPrefs.remove('column-settings');
3325
+ // Preserve current sorting, only reset column settings and pageSize
3326
+ this.userPrefs.set('table-state', JSON.stringify({
3327
+ sorting: this.state.sorting
3328
+ }));
3329
+ location.reload();
3330
+ }
3331
+ }
3332
+ ]
3333
+ });
3334
+ }
3335
+
3285
3336
  createErrorModal() {
3286
3337
  this.modals.error = new FtableModal({
3287
3338
  parent: this.elements.mainContainer,
@@ -3565,6 +3616,21 @@ class FTable extends FTableEventEmitter {
3565
3616
  });
3566
3617
  }
3567
3618
 
3619
+ // Sorting reset button — visible only when sorting differs from default
3620
+ if (this.options.sorting && this.options.sortingResetButton) {
3621
+ this.elements.sortingResetBtn = this.addToolbarButton({
3622
+ text: this.options.messages.resetSorting || 'Reset sorting',
3623
+ className: 'ftable-toolbar-item-sorting-reset',
3624
+ onClick: () => {
3625
+ this.state.sorting = [];
3626
+ this.updateSortingHeaders();
3627
+ this.load();
3628
+ this.saveState();
3629
+ }
3630
+ });
3631
+ FTableDOMHelper.hide(this.elements.sortingResetBtn); // hidden by default
3632
+ }
3633
+
3568
3634
  if (this.options.actions.createAction) {
3569
3635
  this.addToolbarButton({
3570
3636
  text: this.options.messages.addNewRecord,
@@ -4604,19 +4670,45 @@ class FTable extends FTableEventEmitter {
4604
4670
  }
4605
4671
 
4606
4672
  updateSortingHeaders() {
4607
- // Clear all sorting classes
4673
+ // Clear all sorting classes and remove any existing sort badges
4608
4674
  const headers = this.elements.table.querySelectorAll('.ftable-column-header-sortable');
4609
4675
  headers.forEach(header => {
4610
4676
  FTableDOMHelper.removeClass(header, 'ftable-column-header-sorted-asc ftable-column-header-sorted-desc');
4677
+ const existing = header.querySelector('.ftable-sort-badge');
4678
+ if (existing) existing.remove();
4611
4679
  });
4612
-
4613
- // Apply current sorting classes
4614
- this.state.sorting.forEach(sort => {
4680
+
4681
+ // Apply current sorting classes and sort number badges
4682
+ this.state.sorting.forEach((sort, index) => {
4615
4683
  const header = this.elements.table.querySelector(`[data-field-name="${sort.fieldName}"]`);
4616
- if (header) {
4617
- FTableDOMHelper.addClass(header, `ftable-column-header-sorted-${sort.direction.toLowerCase()}`);
4684
+ if (!header) return;
4685
+
4686
+ FTableDOMHelper.addClass(header, `ftable-column-header-sorted-${sort.direction.toLowerCase()}`);
4687
+
4688
+ // Sort number badge — only show when multisorting with more than 1 active sort
4689
+ if (this.options.multiSorting && this.state.sorting.length > 1) {
4690
+ const container = header.querySelector('.ftable-column-header-container');
4691
+ if (container) {
4692
+ FTableDOMHelper.create('span', {
4693
+ className: 'ftable-sort-badge',
4694
+ textContent: String(index + 1),
4695
+ parent: container
4696
+ });
4697
+ }
4618
4698
  }
4619
4699
  });
4700
+
4701
+ // Update visibility of the sorting reset toolbar button
4702
+ this._updateSortingResetButton();
4703
+ }
4704
+
4705
+ _updateSortingResetButton() {
4706
+ if (!this.elements.sortingResetBtn) return;
4707
+ if (this.state.sorting.length === 0) {
4708
+ FTableDOMHelper.hide(this.elements.sortingResetBtn);
4709
+ } else {
4710
+ FTableDOMHelper.show(this.elements.sortingResetBtn);
4711
+ }
4620
4712
  }
4621
4713
 
4622
4714
  // Paging Methods
@@ -5198,8 +5290,8 @@ class FTable extends FTableEventEmitter {
5198
5290
  // this.emit('columnVisibilityChanged', { field: field });
5199
5291
  }
5200
5292
 
5201
- // Responsive helpers
5202
5293
  /*
5294
+ // Responsive helpers
5203
5295
  makeResponsive() {
5204
5296
  // Add responsive classes and behavior
5205
5297
  FTableDOMHelper.addClass(this.elements.mainContainer, 'ftable-responsive');
@@ -5244,200 +5336,6 @@ class FTable extends FTableEventEmitter {
5244
5336
  });
5245
5337
  }
5246
5338
 
5247
- // Advanced search functionality
5248
- enableSearch(options = {}) {
5249
- const searchOptions = {
5250
- placeholder: 'Search...',
5251
- debounceMs: 300,
5252
- searchFields: this.columnList,
5253
- ...options
5254
- };
5255
-
5256
- const searchContainer = FTableDOMHelper.create('div', {
5257
- className: 'ftable-search-container',
5258
- parent: this.elements.toolbarDiv
5259
- });
5260
-
5261
- const searchInput = FTableDOMHelper.create('input', {
5262
- attributes: {
5263
- type: 'text',
5264
- placeholder: searchOptions.placeholder,
5265
- class: 'ftable-search-input'
5266
- },
5267
- parent: searchContainer
5268
- });
5269
-
5270
- // Debounced search
5271
- let searchTimeout;
5272
- searchInput.addEventListener('input', (e) => {
5273
- clearTimeout(searchTimeout);
5274
- searchTimeout = setTimeout(() => {
5275
- this.performSearch(e.target.value, searchOptions.searchFields);
5276
- }, searchOptions.debounceMs);
5277
- });
5278
-
5279
- return searchInput;
5280
- }
5281
-
5282
- async performSearch(query, searchFields) {
5283
- if (!query.trim()) {
5284
- return this.load(); // Clear search
5285
- }
5286
-
5287
- const searchParams = {
5288
- search: query,
5289
- searchFields: searchFields.join(',')
5290
- };
5291
-
5292
- return this.load(searchParams);
5293
- }
5294
-
5295
- // Keyboard shortcuts
5296
- enableKeyboardShortcuts() {
5297
- document.addEventListener('keydown', (e) => {
5298
- // Only handle shortcuts when table has focus or is active
5299
- if (!this.elements.mainContainer.contains(document.activeElement)) return;
5300
-
5301
- switch (e.key) {
5302
- case 'n':
5303
- if (e.ctrlKey && this.options.actions.createAction) {
5304
- e.preventDefault();
5305
- this.showAddRecordForm();
5306
- }
5307
- break;
5308
- case 'r':
5309
- if (e.ctrlKey) {
5310
- e.preventDefault();
5311
- this.reload();
5312
- }
5313
- break;
5314
- case 'Delete':
5315
- if (this.options.actions.deleteAction) {
5316
- const selectedRows = this.getSelectedRows();
5317
- if (selectedRows.length > 0) {
5318
- e.preventDefault();
5319
- this.bulkDelete();
5320
- }
5321
- }
5322
- break;
5323
- case 'a':
5324
- if (e.ctrlKey && this.options.selecting && this.options.multiselect) {
5325
- e.preventDefault();
5326
- this.toggleSelectAll(true);
5327
- }
5328
- break;
5329
- case 'Escape':
5330
- // Close any open modals
5331
- Object.values(this.modals).forEach(modal => {
5332
- if (modal.isOpen) modal.close();
5333
- });
5334
- break;
5335
- }
5336
- });
5337
- }
5338
-
5339
- // Real-time updates via WebSocket
5340
- enableRealTimeUpdates(websocketUrl) {
5341
- if (!websocketUrl) return;
5342
-
5343
- this.websocket = new WebSocket(websocketUrl);
5344
-
5345
- this.websocket.onmessage = (event) => {
5346
- try {
5347
- const data = JSON.parse(event.data);
5348
- this.handleRealTimeUpdate(data);
5349
- } catch (error) {
5350
- this.logger.error('Failed to parse WebSocket message', error);
5351
- }
5352
- };
5353
-
5354
- this.websocket.onerror = (error) => {
5355
- this.logger.error('WebSocket error', error);
5356
- };
5357
-
5358
- this.websocket.onclose = () => {
5359
- this.logger.info('WebSocket connection closed');
5360
- // Attempt to reconnect after delay
5361
- setTimeout(() => {
5362
- if (this.websocket.readyState === WebSocket.CLOSED) {
5363
- this.enableRealTimeUpdates(websocketUrl);
5364
- }
5365
- }, 5000);
5366
- };
5367
- }
5368
-
5369
- handleRealTimeUpdate(data) {
5370
- switch (data.type) {
5371
- case 'record_added':
5372
- this.addRecordToTable(data.record);
5373
- break;
5374
- case 'record_updated':
5375
- this.updateRecordInTable(data.record);
5376
- break;
5377
- case 'record_deleted':
5378
- this.removeRecordFromTable(data.recordKey);
5379
- break;
5380
- case 'refresh':
5381
- this.reload();
5382
- break;
5383
- }
5384
- }
5385
-
5386
- addRecordToTable(record) {
5387
- const row = this.createTableRow(record);
5388
-
5389
- // Add to beginning or end based on sorting
5390
- if (this.state.sorting.length > 0) {
5391
- // Would need to calculate correct position based on sort
5392
- this.elements.tableBody.appendChild(row);
5393
- } else {
5394
- this.elements.tableBody.appendChild(row);
5395
- }
5396
-
5397
- this.state.records.push(record);
5398
- this.removeNoDataRow();
5399
- this.refreshRowStyles();
5400
-
5401
- // Show animation
5402
- if (this.options.animationsEnabled) {
5403
- this.showRowAnimation(row, 'added');
5404
- }
5405
- }
5406
-
5407
- updateRecordInTable(record) {
5408
- const keyValue = this.getKeyValue(record);
5409
- const existingRow = this.getRowByKey(keyValue);
5410
-
5411
- if (existingRow) {
5412
- this.updateRowData(existingRow, record);
5413
-
5414
- if (this.options.animationsEnabled) {
5415
- this.showRowAnimation(existingRow, 'updated');
5416
- }
5417
- }
5418
- }
5419
-
5420
- removeRecordFromTable(keyValue) {
5421
- const row = this.getRowByKey(keyValue);
5422
- if (row) {
5423
- this.removeRowFromTable(row);
5424
-
5425
- // Remove from state
5426
- this.state.records = this.state.records.filter(r =>
5427
- this.getKeyValue(r) !== keyValue
5428
- );
5429
- }
5430
- }
5431
-
5432
- showRowAnimation(row, type) {
5433
- const animationClass = `ftable-row-${type}`;
5434
- FTableDOMHelper.addClass(row, animationClass);
5435
-
5436
- setTimeout(() => {
5437
- FTableDOMHelper.removeClass(row, animationClass);
5438
- }, 2000);
5439
- }
5440
-
5441
5339
  // Plugin system for extensions
5442
5340
  use(plugin, options = {}) {
5443
5341
  if (typeof plugin === 'function') {
@@ -5618,65 +5516,20 @@ class FTable extends FTableEventEmitter {
5618
5516
 
5619
5517
  const messages = this.options.messages || {};
5620
5518
 
5621
- // Get prefix/suffix if defined
5622
- const prefix = messages.sortingInfoPrefix ? `<span class="ftable-sorting-prefix">${messages.sortingInfoPrefix}</span> ` : '';
5623
- const suffix = messages.sortingInfoSuffix ? ` <span class="ftable-sorting-suffix">${messages.sortingInfoSuffix}</span>` : '';
5624
-
5625
5519
  if (this.state.sorting.length === 0) {
5626
5520
  container.innerHTML = messages.sortingInfoNone || '';
5627
5521
  return;
5628
5522
  }
5629
5523
 
5524
+ // Get prefix/suffix if defined
5525
+ const prefix = messages.sortingInfoPrefix ? `<span class="ftable-sorting-prefix">${messages.sortingInfoPrefix}</span> ` : '';
5526
+ const suffix = messages.sortingInfoSuffix ? ` <span class="ftable-sorting-suffix">${messages.sortingInfoSuffix}</span>` : '';
5527
+
5630
5528
  // Build sorted fields list with translated directions
5631
5529
  const sortingInfo = this.getSortingInfo();
5632
5530
 
5633
5531
  // Combine with prefix and suffix
5634
5532
  container.innerHTML = `${prefix}${sortingInfo}${suffix}`;
5635
-
5636
- // Add reset sorting button
5637
- if (this.state.sorting.length > 0) {
5638
- const resetSortBtn = document.createElement('button');
5639
- resetSortBtn.textContent = messages.resetSorting || 'Reset Sorting';
5640
- resetSortBtn.style.marginLeft = '10px';
5641
- resetSortBtn.classList.add('ftable-sorting-reset-btn');
5642
- resetSortBtn.addEventListener('click', (e) => {
5643
- e.preventDefault();
5644
- this.state.sorting = [];
5645
- this.updateSortingHeaders();
5646
- this.load();
5647
- this.saveState();
5648
- });
5649
- container.appendChild(resetSortBtn);
5650
- }
5651
-
5652
- // Add reset table button if enabled
5653
- if (this.options.tableReset) {
5654
- const resetTableBtn = document.createElement('button');
5655
- resetTableBtn.textContent = messages.resetTable || 'Reset Table';
5656
- resetTableBtn.style.marginLeft = '10px';
5657
- resetTableBtn.classList.add('ftable-table-reset-btn');
5658
- resetTableBtn.addEventListener('click', (e) => {
5659
- e.preventDefault();
5660
- const confirmMsg = messages.resetTableConfirm;
5661
- if (confirm(confirmMsg)) {
5662
- this.userPrefs.remove('column-settings');
5663
- this.userPrefs.remove('table-state');
5664
-
5665
- // Clear any in-memory state that might affect rendering
5666
- this.state.sorting = [];
5667
- this.state.pageSize = this.options.pageSize;
5668
-
5669
- // Reset field visibility to default
5670
- this.columnList.forEach(fieldName => {
5671
- const field = this.options.fields[fieldName];
5672
- // Reset to default: hidden only if explicitly set
5673
- field.visibility = field.visibility === 'fixed' ? 'fixed' : 'visible';
5674
- });
5675
- location.reload();
5676
- }
5677
- });
5678
- container.appendChild(resetTableBtn);
5679
- }
5680
5533
  }
5681
5534
 
5682
5535
  /**
@@ -28,8 +28,11 @@ FTable.setMessages({
28
28
  sortingInfoPrefix: 'Sortering toegepast: ',
29
29
  ascending: 'Oplopend',
30
30
  descending: 'Aflopend',
31
+ resetTableConfirm: 'Hiermee worden de kolomzichtbaarheid, kolombreedtes en paginagrootte teruggezet naar de standaardinstellingen. Wilt u doorgaan?',
31
32
  sortingInfoNone: 'Geen sortering toegepast',
32
33
  csvExport: 'CSV',
33
34
  printTable: '🖨️ Print',
34
- cloneRecord: 'Clone Record'
35
+ cloneRecord: 'Clone Record',
36
+ resetSorting: 'Reset sorteren',
37
+ resetTable: 'Reset tabel'
35
38
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liedekef/ftable",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "Modern, lightweight, jQuery-free CRUD table for dynamic AJAX-powered tables.",
5
5
  "main": "ftable.js",
6
6
  "module": "ftable.esm.js",
@@ -112,22 +112,21 @@ div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .f
112
112
  }
113
113
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before,
114
114
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after {
115
- content: '';
116
115
  position: absolute;
117
- top: 50%;
118
- transform: translateY(-50%);
119
- font-size: 1.1em;
116
+ font-size: 1em;
120
117
  color: #999;
121
118
  opacity: 0.7;
122
119
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
123
120
  }
124
121
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before {
125
122
  content: '▲';
126
- right: 0.55em;
123
+ right: 0;
124
+ top: 0;
127
125
  }
128
126
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after {
129
127
  content: '▼';
130
- right: 0px;
128
+ right: 0;
129
+ top: 0.8em;
131
130
  }
132
131
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::before {
133
132
  color: #222;
@@ -567,6 +566,16 @@ div.ftable-column-selection-container ul.ftable-column-select-list li input[type
567
566
  .ftable-multiselect-dropdown::-webkit-scrollbar-thumb:hover {
568
567
  background: #555;
569
568
  }
569
+ .ftable-sort-badge {
570
+ float: right;
571
+ align-items: center;
572
+ font-size: 0.68em;
573
+ font-weight: bold;
574
+ border-radius: 50%;
575
+ width: 2.5em;
576
+ margin-top: 5px;
577
+ line-height: 1;
578
+ }
570
579
  div.ftable-main-container div.ftable-title div.ftable-title-text {
571
580
  font-size: 16px;
572
581
  font-weight: bold;
@@ -1 +1 @@
1
- div.ftable-main-container{position:relative}div.ftable-main-container div.ftable-title{position:relative;text-align:left}div.ftable-main-container div.ftable-title div.ftable-toolbar{bottom:0;right:0;position:absolute;display:inline-block;margin-right:5px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item{position:relative;display:inline-block;margin:0 0 0 5px;cursor:pointer;font-size:.9em;padding:2px;border:none}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon{display:inline-block;margin:2px;vertical-align:middle;width:16px;height:16px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon.ftable-toolbar-item-add-record{display:inline-flex;align-items:center;background-color:transparent;justify-content:center;width:16px;height:16px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon.ftable-toolbar-item-add-record::before{content:"➕";font-size:14px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-text{display:inline-block;margin:2px;vertical-align:middle}div.ftable-main-container table.ftable{width:100%}div.ftable-main-container table.ftable thead th{padding:0 3px 0 6px;vertical-align:middle;text-align:left}div.ftable-main-container table.ftable thead th.ftable-column-header{height:1px}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container{position:relative;display:table;width:100%;height:100%!important}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container span.ftable-column-header-text{display:table-cell;vertical-align:middle;padding-top:4px;padding-bottom:3px}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container div.ftable-column-resize-handler{position:absolute;display:table-cell;vertical-align:middle;height:100%;width:8px;right:-8px;z-index:2;cursor:col-resize}div.ftable-main-container table.ftable thead th.ftable-command-column-header{text-align:center;width:1%}div.ftable-main-container table.ftable thead th.ftable-column-header-select{text-align:center;width:1%}div.ftable-main-container table.ftable thead th.ftable-column-header-select input{cursor:pointer}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable{cursor:pointer}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-sortable-text{padding-right:20px}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container{position:relative}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after,div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before{content:'';position:absolute;top:50%;transform:translateY(-50%);font-size:1.1em;color:#999;opacity:.7;text-shadow:0 1px 0 rgba(255,255,255,.5)}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before{content:'▲';right:.55em}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after{content:'▼';right:0}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::before{color:#222;opacity:1;font-weight:700}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::after{opacity:.7}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-desc .ftable-column-header-container::after{color:#222;opacity:1;font-weight:700}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-desc .ftable-column-header-container::before{opacity:.7}div.ftable-main-container table.ftable tbody tr>td .ftable-command-button{margin:5px;padding:0;cursor:pointer;border:none;display:inline}div.ftable-main-container table.ftable tbody tr>td .ftable-command-button span{display:none}div.ftable-main-container table.ftable tbody tr>td.ftable-command-column{text-align:center;vertical-align:middle}div.ftable-main-container table.ftable tbody tr>td.ftable-selecting-column{text-align:center;vertical-align:middle}div.ftable-main-container table.ftable tbody tr>td.ftable-selecting-column input{cursor:pointer}div.ftable-main-container table.ftable tbody tr>td .ftable-edit-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-edit-command-button::before{content:"📝";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr>td .ftable-clone-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-clone-command-button::before{content:"📋";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr>td .ftable-delete-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-delete-command-button::before{content:"🗑️ ";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr.ftable-no-data-row{text-align:center}div.ftable-main-container>div.ftable-bottom-panel{position:relative;min-height:24px;text-align:left}div.ftable-main-container>div.ftable-bottom-panel div.ftable-right-area{right:0;top:0;bottom:0;position:absolute}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list{display:inline-block}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-active,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-first,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-last,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-next,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-previous,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-space{padding:2px 5px;display:inline-block;cursor:pointer}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-active,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-disabled,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-space{cursor:default}div.ftable-main-container>div.ftable-bottom-panel span.ftable-page-size-change{margin-left:5px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-goto-page{margin-left:5px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-goto-page input[type=text]{width:22px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-page-info{vertical-align:middle}div.ftable-main-container div.ftable-column-resize-bar{opacity:.5;position:absolute;width:1px;background-color:#000}form.ftable-dialog-form div.ftable-input-field-container{padding:2px 0 3px 0;border-bottom:1px solid #ddd}form.ftable-dialog-form div.ftable-input-field-container:last-child{border:none}form.ftable-dialog-form div.ftable-input-label{padding:2px 3px;font-size:1.1em;color:#666}form.ftable-dialog-form div.ftable-input{padding:2px}form.ftable-dialog-form span.ftable-option-text-clickable{position:relative;top:-2px}form.ftable-dialog-form div.ftable-textarea-input textarea{width:300px;min-height:60px}form.ftable-dialog-form div.ftable-checkbox-input span,form.ftable-dialog-form div.ftable-radio-input span{padding-left:4px}form.ftable-dialog-form div.ftable-checkbox-input input,form.ftable-dialog-form div.ftable-radio-input input,form.ftable-dialog-form span.ftable-option-text-clickable{cursor:pointer}.ftable-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:1000;display:none}.ftable-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#fff;padding:20px;border-radius:5px;box-shadow:0 2px 10px rgba(0,0,0,.3);z-index:1001;max-width:90%;max-height:90vh;overflow:auto}.ftable-modal-header{margin-bottom:15px;margin-top:0;padding-bottom:10px;border-bottom:1px solid #eee}.ftable-modal-footer{margin-top:15px;padding-top:10px;border-top:1px solid #eee;text-align:right}.ftable-modal-close{position:absolute;top:10px;right:10px;cursor:pointer;font-size:28px;font-weight:700;color:#aaa}.ftable-busy-modal{padding:0}.ftable-dialog-button{opacity:.8;border:1px solid #ccc;padding:5px;margin:5px}.ftable-dialog-button:hover{background-color:#dedede}div.ftable-busy-message{cursor:wait;margin:0}div.ftable-contextmenu-overlay{position:fixed;left:0;top:0;width:100%;height:100%;z-index:100}.ftable-table-div{display:block;overflow-x:auto}.ftable-table-div>table{overflow:hidden}.ftable-toolbarsearch{width:90%;min-width:fit-content}th.ftable-toolbarsearch-reset{text-align:center!important}div.ftable-column-selection-container{position:absolute;border:1px solid #c8c8c8;background:#fff;color:#000;z-index:101;padding:5px}div.ftable-column-selection-container ul.ftable-column-select-list{margin:0;padding:0;list-style:none}div.ftable-column-selection-container ul.ftable-column-select-list li{margin:0;padding:2px 0}div.ftable-column-selection-container ul.ftable-column-select-list li label span{position:relative;top:-1px;margin-left:4px}div.ftable-column-selection-container ul.ftable-column-select-list li input[type=checkbox]{cursor:pointer}.ftable-yesno-check-wrapper{display:flex;align-items:center}.ftable-yesno-check-fixedlabel,.ftable-yesno-check-text{margin-left:4px}.ftable-yesno-check-text:before{content:attr(data-no)}.ftable-yesno-check-input:checked~.ftable-yesno-check-text:before{content:attr(data-yes)}.ftable-multiselect-container{position:relative;display:inline-block;width:100%;min-width:100px}.ftable-multiselect-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:0 0;z-index:9999}.ftable-multiselect-display{display:flex;align-items:center;background:#fff;border:1px solid #ccc;border-radius:4px;cursor:pointer;padding:4px 30px 4px 8px;position:relative}.ftable-multiselect-display:hover{border-color:#999}.ftable-multiselect-selected{display:flex;flex-wrap:wrap;gap:4px;flex:1;align-items:center}.ftable-multiselect-placeholder{color:#555;font-weight:400}.ftable-multiselect-tag{display:inline-flex;align-items:center;background:#e3f2fd;border:1px solid #90caf9;border-radius:3px;padding:2px 6px;font-size:13px;gap:6px;max-width:100%}.ftable-multiselect-tag-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ftable-multiselect-tag-remove{cursor:pointer;font-weight:700;color:#1976d2;font-size:18px;line-height:1;padding:0 2px;user-select:none}.ftable-multiselect-tag-remove:hover{color:#d32f2f}.ftable-multiselect-count{color:#666;font-size:12px;margin-left:4px;white-space:nowrap}.ftable-multiselect-toggle{position:absolute;right:0;background:0 0;border:none;cursor:pointer;padding:4px;color:#666;font-size:10px;line-height:1}.ftable-multiselect-toggle:hover{color:#333}.ftable-multiselect-dropdown{background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 4px 6px rgba(0,0,0,.1);max-height:200px;overflow-y:auto;z-index:10000}.ftable-multiselect-dropdown:focus{outline:0}.ftable-multiselect-option{display:flex;align-items:center;padding:4px 8px;cursor:pointer;user-select:none;gap:4px}.ftable-multiselect-option:hover{background:#f5f5f5}.ftable-multiselect-checkbox{cursor:pointer;margin:0;flex-shrink:0}.ftable-multiselect-label{cursor:pointer;flex:1;margin:0}@media (max-width:768px){.ftable-multiselect-dropdown{max-height:200px}.ftable-multiselect-tag{font-size:12px}}.ftable-multiselect-dropdown::-webkit-scrollbar{width:8px}.ftable-multiselect-dropdown::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}.ftable-multiselect-dropdown::-webkit-scrollbar-thumb{background:#888;border-radius:4px}.ftable-multiselect-dropdown::-webkit-scrollbar-thumb:hover{background:#555}div.ftable-main-container div.ftable-title div.ftable-title-text{font-size:16px;font-weight:700}div.ftable-busy-message{color:#000;background-color:#ddd;font-size:1.25em}
1
+ div.ftable-main-container{position:relative}div.ftable-main-container div.ftable-title{position:relative;text-align:left}div.ftable-main-container div.ftable-title div.ftable-toolbar{bottom:0;right:0;position:absolute;display:inline-block;margin-right:5px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item{position:relative;display:inline-block;margin:0 0 0 5px;cursor:pointer;font-size:.9em;padding:2px;border:none}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon{display:inline-block;margin:2px;vertical-align:middle;width:16px;height:16px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon.ftable-toolbar-item-add-record{display:inline-flex;align-items:center;background-color:transparent;justify-content:center;width:16px;height:16px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-icon.ftable-toolbar-item-add-record::before{content:"➕";font-size:14px}div.ftable-main-container div.ftable-title div.ftable-toolbar .ftable-toolbar-item span.ftable-toolbar-item-text{display:inline-block;margin:2px;vertical-align:middle}div.ftable-main-container table.ftable{width:100%}div.ftable-main-container table.ftable thead th{padding:0 3px 0 6px;vertical-align:middle;text-align:left}div.ftable-main-container table.ftable thead th.ftable-column-header{height:1px}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container{position:relative;display:table;width:100%;height:100%!important}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container span.ftable-column-header-text{display:table-cell;vertical-align:middle;padding-top:4px;padding-bottom:3px}div.ftable-main-container table.ftable thead th.ftable-column-header div.ftable-column-header-container div.ftable-column-resize-handler{position:absolute;display:table-cell;vertical-align:middle;height:100%;width:8px;right:-8px;z-index:2;cursor:col-resize}div.ftable-main-container table.ftable thead th.ftable-command-column-header{text-align:center;width:1%}div.ftable-main-container table.ftable thead th.ftable-column-header-select{text-align:center;width:1%}div.ftable-main-container table.ftable thead th.ftable-column-header-select input{cursor:pointer}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable{cursor:pointer}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-sortable-text{padding-right:20px}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container{position:relative}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after,div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before{position:absolute;font-size:1em;color:#999;opacity:.7;text-shadow:0 1px 0 rgba(255,255,255,.5)}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before{content:'▲';right:0;top:0}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after{content:'▼';right:0;top:.8em}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::before{color:#222;opacity:1;font-weight:700}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::after{opacity:.7}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-desc .ftable-column-header-container::after{color:#222;opacity:1;font-weight:700}div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-desc .ftable-column-header-container::before{opacity:.7}div.ftable-main-container table.ftable tbody tr>td .ftable-command-button{margin:5px;padding:0;cursor:pointer;border:none;display:inline}div.ftable-main-container table.ftable tbody tr>td .ftable-command-button span{display:none}div.ftable-main-container table.ftable tbody tr>td.ftable-command-column{text-align:center;vertical-align:middle}div.ftable-main-container table.ftable tbody tr>td.ftable-selecting-column{text-align:center;vertical-align:middle}div.ftable-main-container table.ftable tbody tr>td.ftable-selecting-column input{cursor:pointer}div.ftable-main-container table.ftable tbody tr>td .ftable-edit-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-edit-command-button::before{content:"📝";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr>td .ftable-clone-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-clone-command-button::before{content:"📋";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr>td .ftable-delete-command-button{width:16px;height:16px;background-color:transparent}div.ftable-main-container table.ftable tbody tr>td .ftable-delete-command-button::before{content:"🗑️ ";font-size:14px;display:flex;align-items:center;justify-content:center;width:100%;height:100%}div.ftable-main-container table.ftable tbody tr.ftable-no-data-row{text-align:center}div.ftable-main-container>div.ftable-bottom-panel{position:relative;min-height:24px;text-align:left}div.ftable-main-container>div.ftable-bottom-panel div.ftable-right-area{right:0;top:0;bottom:0;position:absolute}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list{display:inline-block}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-active,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-first,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-last,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-next,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-previous,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-space{padding:2px 5px;display:inline-block;cursor:pointer}div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-active,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-disabled,div.ftable-main-container>div.ftable-bottom-panel .ftable-page-list .ftable-page-number-space{cursor:default}div.ftable-main-container>div.ftable-bottom-panel span.ftable-page-size-change{margin-left:5px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-goto-page{margin-left:5px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-goto-page input[type=text]{width:22px}div.ftable-main-container>div.ftable-bottom-panel span.ftable-page-info{vertical-align:middle}div.ftable-main-container div.ftable-column-resize-bar{opacity:.5;position:absolute;width:1px;background-color:#000}form.ftable-dialog-form div.ftable-input-field-container{padding:2px 0 3px 0;border-bottom:1px solid #ddd}form.ftable-dialog-form div.ftable-input-field-container:last-child{border:none}form.ftable-dialog-form div.ftable-input-label{padding:2px 3px;font-size:1.1em;color:#666}form.ftable-dialog-form div.ftable-input{padding:2px}form.ftable-dialog-form span.ftable-option-text-clickable{position:relative;top:-2px}form.ftable-dialog-form div.ftable-textarea-input textarea{width:300px;min-height:60px}form.ftable-dialog-form div.ftable-checkbox-input span,form.ftable-dialog-form div.ftable-radio-input span{padding-left:4px}form.ftable-dialog-form div.ftable-checkbox-input input,form.ftable-dialog-form div.ftable-radio-input input,form.ftable-dialog-form span.ftable-option-text-clickable{cursor:pointer}.ftable-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:1000;display:none}.ftable-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#fff;padding:20px;border-radius:5px;box-shadow:0 2px 10px rgba(0,0,0,.3);z-index:1001;max-width:90%;max-height:90vh;overflow:auto}.ftable-modal-header{margin-bottom:15px;margin-top:0;padding-bottom:10px;border-bottom:1px solid #eee}.ftable-modal-footer{margin-top:15px;padding-top:10px;border-top:1px solid #eee;text-align:right}.ftable-modal-close{position:absolute;top:10px;right:10px;cursor:pointer;font-size:28px;font-weight:700;color:#aaa}.ftable-busy-modal{padding:0}.ftable-dialog-button{opacity:.8;border:1px solid #ccc;padding:5px;margin:5px}.ftable-dialog-button:hover{background-color:#dedede}div.ftable-busy-message{cursor:wait;margin:0}div.ftable-contextmenu-overlay{position:fixed;left:0;top:0;width:100%;height:100%;z-index:100}.ftable-table-div{display:block;overflow-x:auto}.ftable-table-div>table{overflow:hidden}.ftable-toolbarsearch{width:90%;min-width:fit-content}th.ftable-toolbarsearch-reset{text-align:center!important}div.ftable-column-selection-container{position:absolute;border:1px solid #c8c8c8;background:#fff;color:#000;z-index:101;padding:5px}div.ftable-column-selection-container ul.ftable-column-select-list{margin:0;padding:0;list-style:none}div.ftable-column-selection-container ul.ftable-column-select-list li{margin:0;padding:2px 0}div.ftable-column-selection-container ul.ftable-column-select-list li label span{position:relative;top:-1px;margin-left:4px}div.ftable-column-selection-container ul.ftable-column-select-list li input[type=checkbox]{cursor:pointer}.ftable-yesno-check-wrapper{display:flex;align-items:center}.ftable-yesno-check-fixedlabel,.ftable-yesno-check-text{margin-left:4px}.ftable-yesno-check-text:before{content:attr(data-no)}.ftable-yesno-check-input:checked~.ftable-yesno-check-text:before{content:attr(data-yes)}.ftable-multiselect-container{position:relative;display:inline-block;width:100%;min-width:100px}.ftable-multiselect-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:0 0;z-index:9999}.ftable-multiselect-display{display:flex;align-items:center;background:#fff;border:1px solid #ccc;border-radius:4px;cursor:pointer;padding:4px 30px 4px 8px;position:relative}.ftable-multiselect-display:hover{border-color:#999}.ftable-multiselect-selected{display:flex;flex-wrap:wrap;gap:4px;flex:1;align-items:center}.ftable-multiselect-placeholder{color:#555;font-weight:400}.ftable-multiselect-tag{display:inline-flex;align-items:center;background:#e3f2fd;border:1px solid #90caf9;border-radius:3px;padding:2px 6px;font-size:13px;gap:6px;max-width:100%}.ftable-multiselect-tag-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ftable-multiselect-tag-remove{cursor:pointer;font-weight:700;color:#1976d2;font-size:18px;line-height:1;padding:0 2px;user-select:none}.ftable-multiselect-tag-remove:hover{color:#d32f2f}.ftable-multiselect-count{color:#666;font-size:12px;margin-left:4px;white-space:nowrap}.ftable-multiselect-toggle{position:absolute;right:0;background:0 0;border:none;cursor:pointer;padding:4px;color:#666;font-size:10px;line-height:1}.ftable-multiselect-toggle:hover{color:#333}.ftable-multiselect-dropdown{background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 4px 6px rgba(0,0,0,.1);max-height:200px;overflow-y:auto;z-index:10000}.ftable-multiselect-dropdown:focus{outline:0}.ftable-multiselect-option{display:flex;align-items:center;padding:4px 8px;cursor:pointer;user-select:none;gap:4px}.ftable-multiselect-option:hover{background:#f5f5f5}.ftable-multiselect-checkbox{cursor:pointer;margin:0;flex-shrink:0}.ftable-multiselect-label{cursor:pointer;flex:1;margin:0}@media (max-width:768px){.ftable-multiselect-dropdown{max-height:200px}.ftable-multiselect-tag{font-size:12px}}.ftable-multiselect-dropdown::-webkit-scrollbar{width:8px}.ftable-multiselect-dropdown::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}.ftable-multiselect-dropdown::-webkit-scrollbar-thumb{background:#888;border-radius:4px}.ftable-multiselect-dropdown::-webkit-scrollbar-thumb:hover{background:#555}.ftable-sort-badge{float:right;align-items:center;font-size:.68em;font-weight:700;border-radius:50%;width:2.5em;margin-top:5px;line-height:1}div.ftable-main-container div.ftable-title div.ftable-title-text{font-size:16px;font-weight:700}div.ftable-busy-message{color:#000;background-color:#ddd;font-size:1.25em}
@@ -258,11 +258,8 @@
258
258
 
259
259
  &::before,
260
260
  &::after {
261
- content: '';
262
261
  position: absolute;
263
- top: 50%;
264
- transform: translateY(-50%);
265
- font-size: 1.1em;
262
+ font-size: 1em;
266
263
  color: #999;
267
264
  opacity: 0.7;
268
265
  text-shadow: 0 1px 0 rgba(255,255,255,0.5); // Optional: subtle highlight
@@ -270,12 +267,14 @@
270
267
 
271
268
  &::before {
272
269
  content: '▲';
273
- right: .55em;
270
+ right: 0;
271
+ top: 0;
274
272
  }
275
273
 
276
274
  &::after {
277
275
  content: '▼';
278
- right: 0px;
276
+ right: 0;
277
+ top: 0.8em;
279
278
  }
280
279
 
281
280
  }
@@ -891,4 +890,16 @@
891
890
  .ftable-multiselect-dropdown::-webkit-scrollbar-thumb:hover {
892
891
  background: #555;
893
892
  }
893
+
894
+ .ftable-sort-badge {
895
+ float: right;
896
+ align-items: center;
897
+ font-size: 0.68em;
898
+ font-weight: bold;
899
+ border-radius: 50%;
900
+ width: 2.5em;
901
+ margin-top: 5px;
902
+ line-height: 1;
903
+ }
904
+
894
905
  }
@@ -109,22 +109,21 @@ div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .f
109
109
  }
110
110
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before,
111
111
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after {
112
- content: '';
113
112
  position: absolute;
114
- top: 50%;
115
- transform: translateY(-50%);
116
- font-size: 1.1em;
113
+ font-size: 1em;
117
114
  color: #999;
118
115
  opacity: 0.7;
119
116
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
120
117
  }
121
118
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::before {
122
119
  content: '▲';
123
- right: 0.55em;
120
+ right: 0;
121
+ top: 0;
124
122
  }
125
123
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable .ftable-column-header-container::after {
126
124
  content: '▼';
127
- right: 0px;
125
+ right: 0;
126
+ top: 0.8em;
128
127
  }
129
128
  div.ftable-main-container table.ftable thead th.ftable-column-header-sortable.ftable-column-header-sorted-asc .ftable-column-header-container::before {
130
129
  color: #222;
@@ -564,6 +563,16 @@ div.ftable-column-selection-container ul.ftable-column-select-list li input[type
564
563
  .ftable-multiselect-dropdown::-webkit-scrollbar-thumb:hover {
565
564
  background: #555;
566
565
  }
566
+ .ftable-sort-badge {
567
+ float: right;
568
+ align-items: center;
569
+ font-size: 0.68em;
570
+ font-weight: bold;
571
+ border-radius: 50%;
572
+ width: 2.5em;
573
+ margin-top: 5px;
574
+ line-height: 1;
575
+ }
567
576
  div.ftable-main-container {
568
577
  font-family: Verdana, Arial, Helvetica, sans-serif;
569
578
  font-size: 1em;