@chefuicore/core 1.0.1 → 1.0.2

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/dist/index.js CHANGED
@@ -11895,6 +11895,11 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
11895
11895
  // UI state
11896
11896
  this._activeFilterColumn = null;
11897
11897
  this._filterPopoverField = null;
11898
+ this._filterPopoverX = 0;
11899
+ this._filterPopoverY = 0;
11900
+ this._columnMenuField = null;
11901
+ this._columnMenuX = 0;
11902
+ this._columnMenuY = 0;
11898
11903
  this._densityMenuOpen = false;
11899
11904
  this._columnsMenuOpen = false;
11900
11905
  this._exportMenuOpen = false;
@@ -12164,9 +12169,94 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12164
12169
  this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
12165
12170
  }
12166
12171
  // ── Filtering ──
12167
- _openFilterPopover(field) {
12168
- this._filterPopoverField = this._filterPopoverField === field ? null : field;
12169
- this._closeAllMenus('filter');
12172
+ _openFilterPopover(field, e) {
12173
+ // Close other menus first
12174
+ this._densityMenuOpen = false;
12175
+ this._columnsMenuOpen = false;
12176
+ this._exportMenuOpen = false;
12177
+ this._contextMenuField = null;
12178
+ if (this._filterPopoverField === field) {
12179
+ this._filterPopoverField = null;
12180
+ return;
12181
+ }
12182
+ // Calculate position relative to grid-container
12183
+ const icon = e.currentTarget;
12184
+ const gridContainer = this.shadowRoot.querySelector('.grid-container');
12185
+ if (icon && gridContainer) {
12186
+ const iconRect = icon.getBoundingClientRect();
12187
+ const gridRect = gridContainer.getBoundingClientRect();
12188
+ this._filterPopoverX = iconRect.left - gridRect.left;
12189
+ this._filterPopoverY = iconRect.bottom - gridRect.top + 4;
12190
+ }
12191
+ this._filterPopoverField = field;
12192
+ }
12193
+ // ── Column Header Menu ──
12194
+ _openColumnMenu(field, e) {
12195
+ e.stopPropagation();
12196
+ // Close other menus
12197
+ this._filterPopoverField = null;
12198
+ this._densityMenuOpen = false;
12199
+ this._columnsMenuOpen = false;
12200
+ this._exportMenuOpen = false;
12201
+ if (this._columnMenuField === field) {
12202
+ this._columnMenuField = null;
12203
+ return;
12204
+ }
12205
+ const btn = e.currentTarget;
12206
+ const gridContainer = this.shadowRoot.querySelector('.grid-container');
12207
+ if (btn && gridContainer) {
12208
+ const btnRect = btn.getBoundingClientRect();
12209
+ const gridRect = gridContainer.getBoundingClientRect();
12210
+ this._columnMenuX = btnRect.left - gridRect.left;
12211
+ this._columnMenuY = btnRect.bottom - gridRect.top + 4;
12212
+ }
12213
+ this._columnMenuField = field;
12214
+ }
12215
+ _closeColumnMenu() {
12216
+ this._columnMenuField = null;
12217
+ }
12218
+ _columnMenuSort(field, dir) {
12219
+ // Set sort to specific direction
12220
+ this._sortModel = [{ field, sort: dir }];
12221
+ this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
12222
+ this._closeColumnMenu();
12223
+ }
12224
+ _columnMenuUnsort(field) {
12225
+ this._sortModel = this._sortModel.filter(s => s.field !== field);
12226
+ this.dispatchEvent(new CustomEvent('sort-change', { detail: { sortModel: this._sortModel } }));
12227
+ this._closeColumnMenu();
12228
+ }
12229
+ _columnMenuFilter(field) {
12230
+ this._closeColumnMenu();
12231
+ // Open filter popover — we need to calculate position from the header cell
12232
+ const headerCells = this.shadowRoot.querySelectorAll('.header-cell');
12233
+ const gridContainer = this.shadowRoot.querySelector('.grid-container');
12234
+ const col = this._visibleColumns.find(c => c.field === field);
12235
+ if (!col || !gridContainer)
12236
+ return;
12237
+ const colIdx = this._visibleColumns.indexOf(col);
12238
+ const adjustedIdx = this.checkboxSelection ? colIdx + 1 : colIdx;
12239
+ const headerCell = headerCells[adjustedIdx];
12240
+ if (headerCell) {
12241
+ const cellRect = headerCell.getBoundingClientRect();
12242
+ const gridRect = gridContainer.getBoundingClientRect();
12243
+ this._filterPopoverX = cellRect.left - gridRect.left;
12244
+ this._filterPopoverY = cellRect.bottom - gridRect.top + 4;
12245
+ }
12246
+ this._filterPopoverField = field;
12247
+ }
12248
+ _columnMenuHide(field) {
12249
+ const newHidden = new Set(this._hiddenColumns);
12250
+ newHidden.add(field);
12251
+ this._hiddenColumns = newHidden;
12252
+ this.dispatchEvent(new CustomEvent('column-visibility-change', {
12253
+ detail: { hiddenColumns: Array.from(newHidden) },
12254
+ }));
12255
+ this._closeColumnMenu();
12256
+ }
12257
+ _columnMenuManageColumns() {
12258
+ this._closeColumnMenu();
12259
+ this._columnsMenuOpen = true;
12170
12260
  }
12171
12261
  _updateFilter(field, operator, value) {
12172
12262
  const items = this._filterModel.items.filter(i => i.field !== field);
@@ -12445,6 +12535,20 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12445
12535
  this.density = d;
12446
12536
  this._densityMenuOpen = false;
12447
12537
  }
12538
+ // ── Grid Click ──
12539
+ _handleGridClick(e) {
12540
+ const path = e.composedPath();
12541
+ // Don't close menus if user clicked inside an overlay/popover/dropdown
12542
+ const insideOverlay = path.some(el => el instanceof HTMLElement && (el.classList?.contains('filter-popover') ||
12543
+ el.classList?.contains('column-menu') ||
12544
+ el.classList?.contains('dropdown') ||
12545
+ el.classList?.contains('filter-icon') ||
12546
+ el.classList?.contains('toolbar-btn') ||
12547
+ el.classList?.contains('column-menu-btn')));
12548
+ if (!insideOverlay) {
12549
+ this._closeAllMenus();
12550
+ }
12551
+ }
12448
12552
  // ── Helpers ──
12449
12553
  _getColumnWidth(field) {
12450
12554
  if (this._columnWidths.has(field))
@@ -12478,6 +12582,8 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12478
12582
  this._exportMenuOpen = false;
12479
12583
  if (except !== 'context')
12480
12584
  this._contextMenuField = null;
12585
+ if (except !== 'columnMenu')
12586
+ this._columnMenuField = null;
12481
12587
  }
12482
12588
  _getSortDirection(field) {
12483
12589
  const s = this._sortModel.find(s => s.field === field);
@@ -12503,7 +12609,7 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12503
12609
  aria-rowcount="${this._totalRowCount}"
12504
12610
  aria-colcount="${this._visibleColumns.length}"
12505
12611
  @keydown="${this._handleGridKeydown}"
12506
- @click="${() => this._closeAllMenus()}"
12612
+ @click="${(e) => this._handleGridClick(e)}"
12507
12613
  >
12508
12614
  ${this.showToolbar ? this._renderToolbar() : A}
12509
12615
  ${this._renderHeader()}
@@ -12522,6 +12628,86 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12522
12628
  ` : this._renderBody()}
12523
12629
  </div>
12524
12630
  ${this._renderFooter()}
12631
+ ${this._filterPopoverField ? this._renderFilterPopoverOverlay() : A}
12632
+ ${this._columnMenuField ? this._renderColumnMenu() : A}
12633
+ ${this._columnsMenuOpen && !this.showToolbar ? this._renderColumnsPanel() : A}
12634
+ </div>
12635
+ `;
12636
+ }
12637
+ // ── Filter Popover Overlay ──
12638
+ _renderFilterPopoverOverlay() {
12639
+ const col = this.columns.find(c => c.field === this._filterPopoverField);
12640
+ if (!col)
12641
+ return A;
12642
+ return this._renderFilterPopover(col);
12643
+ }
12644
+ // ── Column Header Menu ──
12645
+ _renderColumnMenu() {
12646
+ const field = this._columnMenuField;
12647
+ const col = this.columns.find(c => c.field === field);
12648
+ if (!col)
12649
+ return A;
12650
+ const sortDir = this._getSortDirection(field);
12651
+ const isSortable = this.sortable && col.sortable !== false;
12652
+ const isFilterable = this.filterable && col.filterable !== false;
12653
+ const isPinnable = col.pinnable !== false;
12654
+ const isHideable = col.hideable !== false;
12655
+ const isPinnedLeft = (this.pinnedColumns.left || []).includes(field);
12656
+ const isPinnedRight = (this.pinnedColumns.right || []).includes(field);
12657
+ return b `
12658
+ <div class="column-menu"
12659
+ style="left:${this._columnMenuX}px; top:${this._columnMenuY}px"
12660
+ @click="${(e) => e.stopPropagation()}">
12661
+
12662
+ ${isSortable ? b `
12663
+ <button class="column-menu-item" @click="${() => this._columnMenuSort(field, 'asc')}">
12664
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
12665
+ Sort by ASC
12666
+ </button>
12667
+ <button class="column-menu-item" @click="${() => this._columnMenuSort(field, 'desc')}">
12668
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
12669
+ Sort by DESC
12670
+ </button>
12671
+ ${sortDir ? b `
12672
+ <button class="column-menu-item" @click="${() => this._columnMenuUnsort(field)}">
12673
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
12674
+ Unsort
12675
+ </button>
12676
+ ` : A}
12677
+ <div class="column-menu-divider"></div>
12678
+ ` : A}
12679
+
12680
+ ${isPinnable ? b `
12681
+ <button class="column-menu-item" @click="${() => { this._pinColumn(field, isPinnedLeft ? 'none' : 'left'); this._closeColumnMenu(); }}">
12682
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 4.5L7.5 12l3 3L18 7.5"/><path d="M2 22l5.5-5.5"/></svg>
12683
+ ${isPinnedLeft ? 'Unpin from left' : 'Pin to left'}
12684
+ </button>
12685
+ <button class="column-menu-item" @click="${() => { this._pinColumn(field, isPinnedRight ? 'none' : 'right'); this._closeColumnMenu(); }}">
12686
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 4.5L16.5 12l-3 3L6 7.5"/><path d="M22 22l-5.5-5.5"/></svg>
12687
+ ${isPinnedRight ? 'Unpin from right' : 'Pin to right'}
12688
+ </button>
12689
+ <div class="column-menu-divider"></div>
12690
+ ` : A}
12691
+
12692
+ ${isFilterable ? b `
12693
+ <button class="column-menu-item" @click="${() => this._columnMenuFilter(field)}">
12694
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
12695
+ Filter
12696
+ </button>
12697
+ <div class="column-menu-divider"></div>
12698
+ ` : A}
12699
+
12700
+ ${isHideable ? b `
12701
+ <button class="column-menu-item" @click="${() => this._columnMenuHide(field)}">
12702
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
12703
+ Hide column
12704
+ </button>
12705
+ ` : A}
12706
+
12707
+ <button class="column-menu-item" @click="${() => this._columnMenuManageColumns()}">
12708
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
12709
+ Manage columns
12710
+ </button>
12525
12711
  </div>
12526
12712
  `;
12527
12713
  }
@@ -12622,11 +12808,10 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12622
12808
  const sortDir = this._getSortDirection(col.field);
12623
12809
  const sortIdx = this._getSortIndex(col.field);
12624
12810
  const isSortable = this.sortable && col.sortable !== false;
12625
- const isFilterable = this.filterable && col.filterable !== false;
12626
12811
  const hasFilter = this._getFilterForField(col.field) !== undefined;
12627
12812
  const isReorderable = this.reorderableColumns;
12628
12813
  const isResizable = this.resizableColumns && col.resizable !== false;
12629
- const isFilterOpen = this._filterPopoverField === col.field;
12814
+ const isMenuOpen = this._columnMenuField === col.field;
12630
12815
  return b `
12631
12816
  <div class="header-cell ${isSortable ? 'sortable' : ''}"
12632
12817
  style="width:${width}px; height:${this._effectiveHeaderHeight}px; text-align:${col.headerAlign || col.align || 'left'}"
@@ -12644,20 +12829,31 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12644
12829
  <span class="sort-icon">${sortDir === 'asc' ? '↑' : '↓'}</span>
12645
12830
  ${this._sortModel.length > 1 ? b `<span class="sort-index">${sortIdx + 1}</span>` : A}
12646
12831
  ` : A}
12832
+ ${hasFilter ? b `<span class="sort-icon" style="color:var(--dg-primary);font-size:10px">⧫</span>` : A}
12647
12833
 
12648
- ${isFilterable ? b `
12649
- <svg class="filter-icon ${hasFilter || isFilterOpen ? 'active' : ''}"
12650
- viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
12651
- @click="${(e) => { e.stopPropagation(); this._openFilterPopover(col.field); }}">
12652
- <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
12653
- </svg>
12654
- ` : A}
12834
+ <button class="column-menu-btn ${isMenuOpen ? 'active' : ''}"
12835
+ @click="${(e) => this._openColumnMenu(col.field, e)}">⋮</button>
12655
12836
 
12656
12837
  ${isResizable ? b `
12657
12838
  <div class="resize-handle" @mousedown="${(e) => this._startResize(col.field, e)}"></div>
12658
12839
  ` : A}
12659
-
12660
- ${isFilterOpen ? this._renderFilterPopover(col) : A}
12840
+ </div>
12841
+ `;
12842
+ }
12843
+ // ── Standalone Columns Panel ──
12844
+ _renderColumnsPanel() {
12845
+ return b `
12846
+ <div class="column-menu"
12847
+ style="right:8px; top:${this._effectiveHeaderHeight + 8}px; left:auto; max-height:300px; overflow-y:auto"
12848
+ @click="${(e) => e.stopPropagation()}">
12849
+ ${this.columns.filter(c => c.hideable !== false).map(col => b `
12850
+ <label class="column-menu-item" style="cursor:pointer">
12851
+ <input type="checkbox" style="cursor:pointer"
12852
+ ?checked="${!this._hiddenColumns.has(col.field)}"
12853
+ @change="${() => this._toggleColumnVisibility(col.field)}" />
12854
+ ${col.headerName || col.field}
12855
+ </label>
12856
+ `)}
12661
12857
  </div>
12662
12858
  `;
12663
12859
  }
@@ -12669,7 +12865,9 @@ let ChefUiDataGrid = class ChefUiDataGrid extends i {
12669
12865
  const currentOp = existing?.operator || getDefaultOperator(type);
12670
12866
  const currentVal = existing?.value ?? '';
12671
12867
  return b `
12672
- <div class="filter-popover" @click="${(e) => e.stopPropagation()}">
12868
+ <div class="filter-popover"
12869
+ style="left:${this._filterPopoverX}px; top:${this._filterPopoverY}px"
12870
+ @click="${(e) => e.stopPropagation()}">
12673
12871
  <div class="filter-popover-title">Filter: ${col.headerName || col.field}</div>
12674
12872
  <select
12675
12873
  .value="${currentOp}"
@@ -12944,7 +13142,7 @@ ChefUiDataGrid.styles = i$3 `
12944
13142
  .grid-container {
12945
13143
  border: 1px solid var(--dg-border);
12946
13144
  border-radius: 4px;
12947
- overflow: hidden;
13145
+ overflow: visible;
12948
13146
  position: relative;
12949
13147
  background: #fff;
12950
13148
  }
@@ -13047,6 +13245,7 @@ ChefUiDataGrid.styles = i$3 `
13047
13245
  position: sticky;
13048
13246
  top: 0;
13049
13247
  z-index: 10;
13248
+ overflow: visible;
13050
13249
  }
13051
13250
 
13052
13251
  .header-cell {
@@ -13059,7 +13258,7 @@ ChefUiDataGrid.styles = i$3 `
13059
13258
  border-right: 1px solid var(--dg-border);
13060
13259
  position: relative;
13061
13260
  user-select: none;
13062
- overflow: hidden;
13261
+ overflow: visible;
13063
13262
  flex-shrink: 0;
13064
13263
  }
13065
13264
 
@@ -13253,16 +13452,14 @@ ChefUiDataGrid.styles = i$3 `
13253
13452
  /* ── Filter Popover ── */
13254
13453
  .filter-popover {
13255
13454
  position: absolute;
13256
- top: 100%;
13257
- left: 0;
13258
- margin-top: 4px;
13259
13455
  background: #fff;
13260
13456
  border: 1px solid var(--dg-border);
13261
13457
  border-radius: 8px;
13262
- box-shadow: 0 4px 20px rgba(0,0,0,0.15);
13263
- z-index: 200;
13458
+ box-shadow: 0 8px 32px rgba(0,0,0,0.18);
13459
+ z-index: 1000;
13264
13460
  padding: 16px;
13265
13461
  min-width: 280px;
13462
+ max-width: 340px;
13266
13463
  }
13267
13464
 
13268
13465
  .filter-popover-title {
@@ -13347,24 +13544,48 @@ ChefUiDataGrid.styles = i$3 `
13347
13544
  font-size: 14px;
13348
13545
  }
13349
13546
 
13350
- /* ── Context Menu ── */
13351
- .header-context-menu {
13547
+ /* ── Column Menu Button ── */
13548
+ .column-menu-btn {
13549
+ display: flex;
13550
+ align-items: center;
13551
+ justify-content: center;
13552
+ width: 24px;
13553
+ height: 24px;
13554
+ border: none;
13555
+ background: none;
13556
+ cursor: pointer;
13557
+ border-radius: 4px;
13558
+ color: #999;
13559
+ font-size: 16px;
13560
+ flex-shrink: 0;
13561
+ margin-left: 2px;
13562
+ padding: 0;
13563
+ transition: background 0.15s, color 0.15s;
13564
+ font-family: var(--dg-font);
13565
+ line-height: 1;
13566
+ }
13567
+
13568
+ .column-menu-btn:hover { background: rgba(0,0,0,0.08); color: var(--dg-text); }
13569
+ .column-menu-btn.active { background: rgba(0,0,0,0.08); color: var(--dg-primary); }
13570
+
13571
+ /* ── Column Menu ── */
13572
+ .column-menu {
13352
13573
  position: absolute;
13353
- top: 100%;
13354
- right: 4px;
13355
13574
  background: #fff;
13356
13575
  border: 1px solid var(--dg-border);
13357
- border-radius: 6px;
13358
- box-shadow: 0 4px 16px rgba(0,0,0,0.12);
13359
- z-index: 200;
13360
- padding: 4px 0;
13361
- min-width: 140px;
13576
+ border-radius: 8px;
13577
+ box-shadow: 0 8px 32px rgba(0,0,0,0.18);
13578
+ z-index: 1000;
13579
+ padding: 6px 0;
13580
+ min-width: 200px;
13362
13581
  }
13363
13582
 
13364
- .context-item {
13365
- display: block;
13583
+ .column-menu-item {
13584
+ display: flex;
13585
+ align-items: center;
13586
+ gap: 10px;
13366
13587
  width: 100%;
13367
- padding: 8px 16px;
13588
+ padding: 9px 16px;
13368
13589
  border: none;
13369
13590
  background: none;
13370
13591
  cursor: pointer;
@@ -13373,11 +13594,21 @@ ChefUiDataGrid.styles = i$3 `
13373
13594
  transition: background 0.15s;
13374
13595
  font-family: var(--dg-font);
13375
13596
  color: var(--dg-text);
13597
+ white-space: nowrap;
13598
+ }
13599
+
13600
+ .column-menu-item:hover { background: #f5f5f5; }
13601
+
13602
+ .column-menu-item svg {
13603
+ width: 18px;
13604
+ height: 18px;
13605
+ flex-shrink: 0;
13606
+ color: #666;
13376
13607
  }
13377
13608
 
13378
- .context-item:hover { background: #f5f5f5; }
13609
+ .column-menu-item:hover svg { color: var(--dg-text); }
13379
13610
 
13380
- .context-divider {
13611
+ .column-menu-divider {
13381
13612
  height: 1px;
13382
13613
  background: var(--dg-border);
13383
13614
  margin: 4px 0;
@@ -13513,6 +13744,21 @@ __decorate([
13513
13744
  __decorate([
13514
13745
  r()
13515
13746
  ], ChefUiDataGrid.prototype, "_filterPopoverField", void 0);
13747
+ __decorate([
13748
+ r()
13749
+ ], ChefUiDataGrid.prototype, "_filterPopoverX", void 0);
13750
+ __decorate([
13751
+ r()
13752
+ ], ChefUiDataGrid.prototype, "_filterPopoverY", void 0);
13753
+ __decorate([
13754
+ r()
13755
+ ], ChefUiDataGrid.prototype, "_columnMenuField", void 0);
13756
+ __decorate([
13757
+ r()
13758
+ ], ChefUiDataGrid.prototype, "_columnMenuX", void 0);
13759
+ __decorate([
13760
+ r()
13761
+ ], ChefUiDataGrid.prototype, "_columnMenuY", void 0);
13516
13762
  __decorate([
13517
13763
  r()
13518
13764
  ], ChefUiDataGrid.prototype, "_densityMenuOpen", void 0);