@kodaris/krubble-components 1.0.55 → 1.0.57

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.
@@ -3013,6 +3013,7 @@ const KR_OPERATORS = {
3013
3013
  greater_than_equal: { key: 'greater_than_equal', type: 'comparison', dataTypes: ['number', 'date'], label: 'Greater than or equal' },
3014
3014
  between: { key: 'between', type: 'range', dataTypes: ['number', 'date'], label: 'Between' },
3015
3015
  in: { key: 'in', type: 'list', dataTypes: ['string', 'number'], label: 'In' },
3016
+ n_in: { key: 'n_in', type: 'list', dataTypes: ['string', 'number', 'date', 'boolean'], label: 'Not In' },
3016
3017
  empty: { key: 'empty', type: 'nil', dataTypes: ['string', 'number', 'date', 'boolean'], label: 'Empty' },
3017
3018
  n_empty: { key: 'n_empty', type: 'nil', dataTypes: ['string', 'number', 'date', 'boolean'], label: 'Not empty' },
3018
3019
  };
@@ -3345,6 +3346,14 @@ class KRQuery {
3345
3346
  };
3346
3347
  this.specificity = [getDateSpecificity(rawStart), getDateSpecificity(rawEnd)];
3347
3348
  }
3349
+ else if (kql.startsWith('!(') && kql.endsWith(')')) {
3350
+ this.operator = 'n_in';
3351
+ this.text = kql.substring(2, kql.length - 1).trim();
3352
+ this.value = this.text.split(',')
3353
+ .map(v => v.trim())
3354
+ .map(v => this._parse(v));
3355
+ this.specificity = [getDateSpecificity(this.text)];
3356
+ }
3348
3357
  else if (kql.startsWith('(') && kql.endsWith(')')) {
3349
3358
  this.operator = 'in';
3350
3359
  this.text = kql.substring(1, kql.length - 1).trim();
@@ -3391,7 +3400,7 @@ class KRQuery {
3391
3400
  this.value = null;
3392
3401
  this.text = '';
3393
3402
  }
3394
- else if (this.operator === 'in') {
3403
+ else if (this.operator === 'in' || this.operator === 'n_in') {
3395
3404
  this.value = [];
3396
3405
  this.text = '';
3397
3406
  }
@@ -3409,7 +3418,7 @@ class KRQuery {
3409
3418
  this.value = null;
3410
3419
  this.text = '';
3411
3420
  }
3412
- else if (this.operator === 'in') {
3421
+ else if (this.operator === 'in' || this.operator === 'n_in') {
3413
3422
  this.value = [];
3414
3423
  this.text = '';
3415
3424
  }
@@ -3476,14 +3485,14 @@ class KRQuery {
3476
3485
  if (this.operator === 'between') {
3477
3486
  return !this.value?.start && !this.value?.end;
3478
3487
  }
3479
- if (this.operator === 'in') {
3488
+ if (this.operator === 'in' || this.operator === 'n_in') {
3480
3489
  return !this.value?.length;
3481
3490
  }
3482
3491
  return this.value === undefined || this.value === null || this.value === '';
3483
3492
  }
3484
- /** Returns true if the value array contains the given value. Only applies to 'in' operator. */
3493
+ /** Returns true if the value array contains the given value. Only applies to 'in' and 'n_in' operators. */
3485
3494
  has(val) {
3486
- if (this.operator !== 'in' || !Array.isArray(this.value)) {
3495
+ if ((this.operator !== 'in' && this.operator !== 'n_in') || !Array.isArray(this.value)) {
3487
3496
  return false;
3488
3497
  }
3489
3498
  // Bucket values from Solr arrive as strings ("true"/"false") but
@@ -3498,9 +3507,9 @@ class KRQuery {
3498
3507
  }
3499
3508
  return this.value.indexOf(val) >= 0;
3500
3509
  }
3501
- /** Adds or removes a value from the 'in' list and rebuilds text/kql. */
3510
+ /** Adds or removes a value from the 'in' or 'n_in' list and rebuilds text/kql. */
3502
3511
  toggle(val) {
3503
- if (this.operator !== 'in') {
3512
+ if (this.operator !== 'in' && this.operator !== 'n_in') {
3504
3513
  this.setOperator('in');
3505
3514
  }
3506
3515
  // Bucket values from Solr arrive as strings ("true"/"false") —
@@ -3539,8 +3548,8 @@ class KRQuery {
3539
3548
  if (this.operator === 'n_empty') {
3540
3549
  return true;
3541
3550
  }
3542
- // For 'in' operator, valid as long as there's at least one value (including null for empty buckets)
3543
- if (this.operator === 'in') {
3551
+ // For 'in'/'n_in' operator, valid as long as there's at least one value (including null for empty buckets)
3552
+ if (this.operator === 'in' || this.operator === 'n_in') {
3544
3553
  return Array.isArray(this.value) && this.value.length > 0;
3545
3554
  }
3546
3555
  if (this.type === 'date') {
@@ -3684,6 +3693,18 @@ class KRQuery {
3684
3693
  data.value = termify(this.value, true);
3685
3694
  }
3686
3695
  break;
3696
+ case 'n_in':
3697
+ if (Array.isArray(this.value) && this.value.length > 0) {
3698
+ data.operation = 'EXPRESSION';
3699
+ data.not = true;
3700
+ data.value = `(${this.value.map((v) => termify(v, true)).join(' OR ')})`;
3701
+ }
3702
+ else {
3703
+ data.operation = 'EXPRESSION';
3704
+ data.not = true;
3705
+ data.value = termify(this.value, true);
3706
+ }
3707
+ break;
3687
3708
  default:
3688
3709
  data.operation = 'EXPRESSION';
3689
3710
  data.value = termify(this.value, true);
@@ -3778,6 +3799,11 @@ class KRQuery {
3778
3799
  params.operation = 'IN';
3779
3800
  params.values = this.value ?? [];
3780
3801
  break;
3802
+ case 'n_in':
3803
+ params.operation = 'IN';
3804
+ params.not = true;
3805
+ params.values = this.value ?? [];
3806
+ break;
3781
3807
  default:
3782
3808
  throw Error(`${this.operator} operator not supported by db params.`);
3783
3809
  }
@@ -3794,7 +3820,7 @@ class KRQuery {
3794
3820
  if (this.operator === 'between') {
3795
3821
  this.text = `${this._format(this.value?.start)} - ${this._format(this.value?.end)}`;
3796
3822
  }
3797
- else if (this.operator === 'in') {
3823
+ else if (this.operator === 'in' || this.operator === 'n_in') {
3798
3824
  this.text = this.value.map((v) => this._format(v)).join(',');
3799
3825
  }
3800
3826
  else {
@@ -4008,6 +4034,9 @@ class KRQuery {
4008
4034
  case 'in':
4009
4035
  this.kql = `(${this.value.map((v) => this._format(v)).join(',')})`;
4010
4036
  break;
4037
+ case 'n_in':
4038
+ this.kql = `!(${this.value.map((v) => this._format(v)).join(',')})`;
4039
+ break;
4011
4040
  default:
4012
4041
  throw Error(`Unknown operator ${this.operator}`);
4013
4042
  }
@@ -4059,6 +4088,7 @@ let KRTable = class KRTable extends i$2 {
4059
4088
  this._filterPanelTab = 'filter';
4060
4089
  this._buckets = new Map();
4061
4090
  this._filterPanelPos = { top: 0, left: 0 };
4091
+ this._sorts = [];
4062
4092
  this._resizing = null;
4063
4093
  this._resizeObserver = null;
4064
4094
  this._searchPositionLocked = false;
@@ -4248,7 +4278,7 @@ let KRTable = class KRTable extends i$2 {
4248
4278
  const request = {
4249
4279
  page: this._page - 1,
4250
4280
  size: this._pageSize,
4251
- sorts: [],
4281
+ sorts: this._sorts,
4252
4282
  filterFields: [],
4253
4283
  queryFields: [],
4254
4284
  facetFields: []
@@ -4258,7 +4288,7 @@ let KRTable = class KRTable extends i$2 {
4258
4288
  continue;
4259
4289
  }
4260
4290
  const filterData = col.filter.toSolrData();
4261
- if (col.facetable && col.filter.operator === 'in') {
4291
+ if (col.facetable && (col.filter.operator === 'in' || col.filter.operator === 'n_in')) {
4262
4292
  filterData.tagged = true;
4263
4293
  }
4264
4294
  request.filterFields.push(filterData);
@@ -4288,7 +4318,7 @@ let KRTable = class KRTable extends i$2 {
4288
4318
  const request = {
4289
4319
  page: this._page - 1,
4290
4320
  size: this._pageSize,
4291
- sorts: [],
4321
+ sorts: this._sorts,
4292
4322
  filterFields: [],
4293
4323
  queryFields: [],
4294
4324
  facetFields: []
@@ -4410,7 +4440,7 @@ let KRTable = class KRTable extends i$2 {
4410
4440
  }
4411
4441
  }
4412
4442
  // Bucket sync: ensure selected values appear even with 0 results
4413
- if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) {
4443
+ if (col.filter && (col.filter.operator === 'in' || col.filter.operator === 'n_in') && Array.isArray(col.filter.value)) {
4414
4444
  for (const selectedVal of col.filter.value) {
4415
4445
  if (!buckets.some(b => b.val === selectedVal)) {
4416
4446
  buckets.push({
@@ -4587,6 +4617,77 @@ let KRTable = class KRTable extends i$2 {
4587
4617
  document.addEventListener('mouseup', this._handleResizeEnd);
4588
4618
  }
4589
4619
  // ----------------------------------------------------------------------------
4620
+ // Sorting
4621
+ // ----------------------------------------------------------------------------
4622
+ _handleSortClick(e, column) {
4623
+ if (e.shiftKey) {
4624
+ // Multi-sort: add or cycle existing
4625
+ const existingIndex = this._sorts.findIndex(s => s.sortBy === column.id);
4626
+ if (existingIndex === -1) {
4627
+ this._sorts.push({ sortBy: column.id, sortDirection: 'asc' });
4628
+ }
4629
+ else {
4630
+ const existing = this._sorts[existingIndex];
4631
+ if (existing.sortDirection === 'asc') {
4632
+ existing.sortDirection = 'desc';
4633
+ }
4634
+ else {
4635
+ // on third click, remove sorting for the column
4636
+ this._sorts.splice(existingIndex, 1);
4637
+ }
4638
+ }
4639
+ this.requestUpdate();
4640
+ }
4641
+ else {
4642
+ // Single sort: replace all
4643
+ let existing = null;
4644
+ if (this._sorts.length === 1) {
4645
+ existing = this._sorts.find(s => s.sortBy === column.id);
4646
+ }
4647
+ if (!existing) {
4648
+ this._sorts = [{ sortBy: column.id, sortDirection: 'asc' }];
4649
+ }
4650
+ else if (existing.sortDirection === 'asc') {
4651
+ this._sorts = [{ sortBy: column.id, sortDirection: 'desc' }];
4652
+ }
4653
+ else {
4654
+ this._sorts = [];
4655
+ }
4656
+ }
4657
+ this._page = 1;
4658
+ this._fetch();
4659
+ }
4660
+ _renderSortIndicator(column) {
4661
+ if (!column.sortable) {
4662
+ return A;
4663
+ }
4664
+ const sortIndex = this._sorts.findIndex(s => s.sortBy === column.id);
4665
+ if (sortIndex === -1) {
4666
+ // Ghost arrow: visible only on hover via CSS
4667
+ return b `
4668
+ <span class="header-cell__sort" @click=${(e) => this._handleSortClick(e, column)}>
4669
+ <svg class="header-cell__sort-arrow header-cell__sort-arrow--ghost" viewBox="0 0 24 24" fill="currentColor">
4670
+ <path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/>
4671
+ </svg>
4672
+ </span>
4673
+ `;
4674
+ }
4675
+ let arrowStyle = {};
4676
+ if (this._sorts[sortIndex].sortDirection === 'desc') {
4677
+ arrowStyle = { transform: 'rotate(180deg)' };
4678
+ }
4679
+ return b `
4680
+ <span class="header-cell__sort" @click=${(e) => this._handleSortClick(e, column)}>
4681
+ <svg class="header-cell__sort-arrow" viewBox="0 0 24 24" fill="currentColor" style=${o$1(arrowStyle)}>
4682
+ <path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/>
4683
+ </svg>
4684
+ ${this._sorts.length > 1 ? b `
4685
+ <span class="header-cell__sort-priority">${sortIndex + 1}</span>
4686
+ ` : A}
4687
+ </span>
4688
+ `;
4689
+ }
4690
+ // ----------------------------------------------------------------------------
4590
4691
  // Header
4591
4692
  // ----------------------------------------------------------------------------
4592
4693
  _handleAction(action) {
@@ -4780,6 +4881,7 @@ let KRTable = class KRTable extends i$2 {
4780
4881
  _getHeaderCellClasses(column, index) {
4781
4882
  return {
4782
4883
  'header-cell': true,
4884
+ 'header-cell--sortable': !!column.sortable,
4783
4885
  'header-cell--align-center': column.align === 'center',
4784
4886
  'header-cell--align-right': column.align === 'right',
4785
4887
  'header-cell--sticky-left': column.sticky === 'left',
@@ -5012,7 +5114,7 @@ let KRTable = class KRTable extends i$2 {
5012
5114
  />
5013
5115
  `;
5014
5116
  }
5015
- else if (column.filter.operator === 'in') {
5117
+ else if (column.filter.operator === 'in' || column.filter.operator === 'n_in') {
5016
5118
  valueInput = b `
5017
5119
  <textarea
5018
5120
  class="filter-panel__textarea"
@@ -5267,10 +5369,14 @@ let KRTable = class KRTable extends i$2 {
5267
5369
  class=${e$1(this._getHeaderCellClasses(col, i))}
5268
5370
  style=${o$1(this._getCellStyle(col, i))}
5269
5371
  data-column-id=${col.id}
5270
- >${col.label ?? col.id}${col.resizable !== false ? b `<div
5372
+ >
5373
+ <span class="header-cell__label">${col.label ?? col.id}</span>
5374
+ ${this._renderSortIndicator(col)}
5375
+ ${col.resizable !== false ? b `<div
5271
5376
  class="header-cell__resize"
5272
5377
  @mousedown=${(e) => this._handleResizeStart(e, col.id)}
5273
- ></div>` : A}</div>
5378
+ ></div>` : A}
5379
+ </div>
5274
5380
  `)}
5275
5381
  </div>
5276
5382
  ${this._renderFilterRow()}
@@ -5679,9 +5785,7 @@ KRTable.styles = [krBaseCSS, i$5 `
5679
5785
  .header-cell {
5680
5786
  position: sticky;
5681
5787
  top: 0;
5682
- z-index: 2;
5683
5788
  height: 48px;
5684
- line-height: 48px;
5685
5789
  padding: 0 16px;
5686
5790
  white-space: nowrap;
5687
5791
  box-sizing: border-box;
@@ -5691,8 +5795,8 @@ KRTable.styles = [krBaseCSS, i$5 `
5691
5795
  border-right: 1px solid #e5e7ebba;
5692
5796
  font-weight: 600;
5693
5797
  color: #374151;
5694
- overflow: hidden;
5695
- text-overflow: ellipsis;
5798
+ display: flex;
5799
+ align-items: center;
5696
5800
  }
5697
5801
 
5698
5802
  .header-cell__resize {
@@ -5702,21 +5806,52 @@ KRTable.styles = [krBaseCSS, i$5 `
5702
5806
  bottom: 0;
5703
5807
  width: 14px;
5704
5808
  cursor: col-resize;
5809
+ z-index: 10;
5810
+ }
5811
+
5812
+ .header-cell--sortable {
5813
+ user-select: none;
5814
+ }
5815
+
5816
+ .header-cell__label {
5817
+ overflow: hidden;
5818
+ text-overflow: ellipsis;
5819
+ min-width: 0;
5820
+ line-height: 48px;
5821
+ }
5822
+
5823
+ .header-cell__sort {
5824
+ flex-grow: 1;
5705
5825
  display: flex;
5706
5826
  align-items: center;
5707
- justify-content: center;
5708
- z-index: 10;
5827
+ height: 100%;
5828
+ margin-left: 6px;
5829
+ cursor: pointer;
5709
5830
  }
5710
5831
 
5711
- .header-cell__resize::after {
5712
- content: '';
5713
- width: 2px;
5714
- height: 20px;
5715
- background: #c6c6cd;
5832
+ .header-cell__sort-arrow {
5833
+ width: 16px;
5834
+ height: 16px;
5835
+ color: #374151;
5836
+ stroke: currentColor;
5837
+ stroke-width: 1px;
5716
5838
  }
5717
5839
 
5718
- .header-cell:last-child .header-cell__resize::after {
5719
- display: none;
5840
+ .header-cell__sort-arrow--ghost {
5841
+ opacity: 0;
5842
+ color: #374151;
5843
+ transition: opacity 0.15s;
5844
+ }
5845
+
5846
+ .header-cell--sortable:hover .header-cell__sort-arrow--ghost {
5847
+ opacity: 0.4;
5848
+ }
5849
+
5850
+ .header-cell__sort-priority {
5851
+ font-size: 10px;
5852
+ font-weight: 600;
5853
+ color: #374151;
5854
+ line-height: 1;
5720
5855
  }
5721
5856
 
5722
5857
  .cell {
@@ -6182,6 +6317,9 @@ __decorate$9([
6182
6317
  __decorate$9([
6183
6318
  r$1()
6184
6319
  ], KRTable.prototype, "_buckets", void 0);
6320
+ __decorate$9([
6321
+ r$1()
6322
+ ], KRTable.prototype, "_sorts", void 0);
6185
6323
  __decorate$9([
6186
6324
  n$1({ type: Object })
6187
6325
  ], KRTable.prototype, "def", void 0);