@design.estate/dees-catalog 3.61.2 → 3.62.0

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.
@@ -163645,6 +163645,44 @@ var demoFunc29 = /* @__PURE__ */ __name(() => b2`
163645
163645
  ></dees-table>
163646
163646
  </div>
163647
163647
 
163648
+ <div class="demo-section">
163649
+ <h2 class="demo-title">Multi-Column Sort</h2>
163650
+ <p class="demo-description">
163651
+ Click any column header for a single-column sort. Hold Shift while clicking to add the
163652
+ column to a multi-sort cascade (or cycle its direction). Right-click any sortable header
163653
+ to open a menu where you can pin a column to a specific priority slot, remove it, or
163654
+ clear the cascade.
163655
+ </p>
163656
+ <dees-table
163657
+ heading1="People Directory"
163658
+ heading2="Pre-seeded with department ▲ 1, name ▲ 2"
163659
+ .sortBy=${[
163660
+ { key: "department", dir: "asc" },
163661
+ { key: "name", dir: "asc" }
163662
+ ]}
163663
+ .columns=${[
163664
+ { key: "department", header: "Department", sortable: true },
163665
+ { key: "name", header: "Name", sortable: true },
163666
+ { key: "role", header: "Role", sortable: true },
163667
+ { key: "createdAt", header: "Created", sortable: true },
163668
+ { key: "location", header: "Location", sortable: true },
163669
+ { key: "status", header: "Status", sortable: true }
163670
+ ]}
163671
+ .data=${[
163672
+ { department: "R&D", name: "Alice Johnson", role: "Engineer", createdAt: "2023-01-12", location: "Berlin", status: "Active" },
163673
+ { department: "R&D", name: "Diana Martinez", role: "Engineer", createdAt: "2020-06-30", location: "Madrid", status: "Active" },
163674
+ { department: "R&D", name: "Mark Lee", role: "Engineer", createdAt: "2024-03-04", location: "Berlin", status: "Active" },
163675
+ { department: "Design", name: "Bob Smith", role: "Designer", createdAt: "2022-11-05", location: "Paris", status: "Active" },
163676
+ { department: "Design", name: "Sara Kim", role: "Designer", createdAt: "2021-08-19", location: "Paris", status: "On Leave" },
163677
+ { department: "Ops", name: "Charlie Davis", role: "Manager", createdAt: "2021-04-21", location: "London", status: "On Leave" },
163678
+ { department: "Ops", name: "Helena Voss", role: "SRE", createdAt: "2023-07-22", location: "London", status: "Active" },
163679
+ { department: "QA", name: "Fiona Clark", role: "QA", createdAt: "2022-03-14", location: "Vienna", status: "Active" },
163680
+ { department: "QA", name: "Tom\xE1s Rivera", role: "QA", createdAt: "2024-01-09", location: "Madrid", status: "Active" },
163681
+ { department: "CS", name: "Ethan Brown", role: "Support", createdAt: "2019-09-18", location: "Rome", status: "Inactive" }
163682
+ ]}
163683
+ ></dees-table>
163684
+ </div>
163685
+
163648
163686
  <div class="demo-section">
163649
163687
  <h2 class="demo-title">Wide Properties + Many Actions</h2>
163650
163688
  <p class="demo-description">A table with many columns and rich actions to stress test layout and sticky Actions.</p>
@@ -164038,6 +164076,32 @@ var tableStyles = [
164038
164076
  color: ${cssManager.bdTheme("hsl(215.4 16.3% 46.9%)", "hsl(215 20.2% 65.1%)")};
164039
164077
  letter-spacing: -0.01em;
164040
164078
  }
164079
+
164080
+ th[role='columnheader']:hover {
164081
+ color: var(--dees-color-text-primary);
164082
+ }
164083
+
164084
+ th .sortArrow {
164085
+ display: inline-block;
164086
+ margin-left: 6px;
164087
+ font-size: 10px;
164088
+ line-height: 1;
164089
+ opacity: 0.7;
164090
+ vertical-align: middle;
164091
+ }
164092
+
164093
+ th .sortBadge {
164094
+ display: inline-block;
164095
+ margin-left: 3px;
164096
+ padding: 1px 5px;
164097
+ font-size: 10px;
164098
+ font-weight: 600;
164099
+ line-height: 1;
164100
+ color: ${cssManager.bdTheme("hsl(222.2 47.4% 30%)", "hsl(217.2 91.2% 75%)")};
164101
+ background: ${cssManager.bdTheme("hsl(222.2 47.4% 51.2% / 0.12)", "hsl(217.2 91.2% 59.8% / 0.18)")};
164102
+ border-radius: 999px;
164103
+ vertical-align: middle;
164104
+ }
164041
164105
 
164042
164106
  :host([show-vertical-lines]) th {
164043
164107
  border-right: 1px solid var(--dees-color-border-default);
@@ -164229,7 +164293,23 @@ function getCellValue(row, col, displayFunction) {
164229
164293
  return col.value ? col.value(row) : row[col.key];
164230
164294
  }
164231
164295
  __name(getCellValue, "getCellValue");
164232
- function getViewData(data, effectiveColumns, sortKey, sortDir, filterText, columnFilters, filterMode = "table", lucenePredicate) {
164296
+ function compareCellValues(va, vb) {
164297
+ if (va == null && vb == null) return 0;
164298
+ if (va == null) return -1;
164299
+ if (vb == null) return 1;
164300
+ if (typeof va === "number" && typeof vb === "number") {
164301
+ if (va < vb) return -1;
164302
+ if (va > vb) return 1;
164303
+ return 0;
164304
+ }
164305
+ const sa = String(va).toLowerCase();
164306
+ const sb = String(vb).toLowerCase();
164307
+ if (sa < sb) return -1;
164308
+ if (sa > sb) return 1;
164309
+ return 0;
164310
+ }
164311
+ __name(compareCellValues, "compareCellValues");
164312
+ function getViewData(data, effectiveColumns, sortBy, filterText, columnFilters, filterMode = "table", lucenePredicate) {
164233
164313
  let arr = data.slice();
164234
164314
  const ft = (filterText || "").trim().toLowerCase();
164235
164315
  const cf = columnFilters || {};
@@ -164243,9 +164323,9 @@ function getViewData(data, effectiveColumns, sortKey, sortDir, filterText, colum
164243
164323
  const needle = String(cf[k4]).toLowerCase();
164244
164324
  if (!s10.includes(needle)) return false;
164245
164325
  } else {
164246
- const col2 = effectiveColumns.find((c11) => String(c11.key) === k4);
164247
- if (!col2 || col2.hidden || col2.filterable === false) continue;
164248
- const val = getCellValue(row, col2);
164326
+ const col = effectiveColumns.find((c11) => String(c11.key) === k4);
164327
+ if (!col || col.hidden || col.filterable === false) continue;
164328
+ const val = getCellValue(row, col);
164249
164329
  const s10 = String(val ?? "").toLowerCase();
164250
164330
  const needle = String(cf[k4]).toLowerCase();
164251
164331
  if (!s10.includes(needle)) return false;
@@ -164266,9 +164346,9 @@ function getViewData(data, effectiveColumns, sortKey, sortDir, filterText, colum
164266
164346
  }
164267
164347
  }
164268
164348
  } else {
164269
- for (const col2 of effectiveColumns) {
164270
- if (col2.hidden) continue;
164271
- const val = getCellValue(row, col2);
164349
+ for (const col of effectiveColumns) {
164350
+ if (col.hidden) continue;
164351
+ const val = getCellValue(row, col);
164272
164352
  const s10 = String(val ?? "").toLowerCase();
164273
164353
  if (s10.includes(ft)) {
164274
164354
  any = true;
@@ -164281,21 +164361,14 @@ function getViewData(data, effectiveColumns, sortKey, sortDir, filterText, colum
164281
164361
  return true;
164282
164362
  });
164283
164363
  }
164284
- if (!sortKey || !sortDir) return arr;
164285
- const col = effectiveColumns.find((c11) => String(c11.key) === sortKey);
164286
- if (!col) return arr;
164287
- const dir = sortDir === "asc" ? 1 : -1;
164364
+ if (!sortBy || sortBy.length === 0) return arr;
164365
+ const resolved2 = sortBy.map((desc) => ({ desc, col: effectiveColumns.find((c11) => String(c11.key) === desc.key) })).filter((entry) => !!entry.col);
164366
+ if (resolved2.length === 0) return arr;
164288
164367
  arr.sort((a5, b5) => {
164289
- const va = getCellValue(a5, col);
164290
- const vb = getCellValue(b5, col);
164291
- if (va == null && vb == null) return 0;
164292
- if (va == null) return -1 * dir;
164293
- if (vb == null) return 1 * dir;
164294
- if (typeof va === "number" && typeof vb === "number") return (va - vb) * dir;
164295
- const sa = String(va).toLowerCase();
164296
- const sb = String(vb).toLowerCase();
164297
- if (sa < sb) return -1 * dir;
164298
- if (sa > sb) return 1 * dir;
164368
+ for (const { desc, col } of resolved2) {
164369
+ const cmp2 = compareCellValues(getCellValue(a5, col), getCellValue(b5, col));
164370
+ if (cmp2 !== 0) return desc.dir === "asc" ? cmp2 : -cmp2;
164371
+ }
164299
164372
  return 0;
164300
164373
  });
164301
164374
  return arr;
@@ -164448,7 +164521,13 @@ __name(compileLucenePredicate, "compileLucenePredicate");
164448
164521
  init_dist_ts30();
164449
164522
  init_dist_ts29();
164450
164523
  init_theme();
164451
- var _selectedIds_dec, _selectionMode_dec, _searchMode_dec, _stickyHeader_dec, _showColumnFilters_dec, _columnFilters_dec, _filterText_dec, _sortDir_dec, _sortKey_dec, _showGrid_dec, _showHorizontalLines_dec, _showVerticalLines_dec, _editableFields_dec, _selectedDataRow_dec, _reverseDisplayFunction_dec, _displayFunction_dec, _augmentFromDisplayFunction_dec, _rowKey_dec, _columns_dec, _dataActions_dec, _searchable_dec, _dataName_dec, _required_dec3, _disabled_dec3, _label_dec3, _key_dec2, _data_dec, _heading2_dec, _heading1_dec, _a42, _DeesTable_decorators, _init39, _heading1, _heading22, _data, _key2, _label3, _disabled3, _required3, _dataName, _searchable, _dataActions, _columns, _rowKey, _augmentFromDisplayFunction, _displayFunction, _reverseDisplayFunction, _selectedDataRow, _editableFields, _showVerticalLines, _showHorizontalLines, _showGrid, _sortKey, _sortDir, _filterText, _columnFilters, _showColumnFilters, _stickyHeader, _searchMode, _selectionMode, _selectedIds;
164524
+ var _selectedIds_dec, _selectionMode_dec, _searchMode_dec, _stickyHeader_dec, _showColumnFilters_dec, _columnFilters_dec, _filterText_dec, _sortBy_dec, _showGrid_dec, _showHorizontalLines_dec, _showVerticalLines_dec, _editableFields_dec, _selectedDataRow_dec, _reverseDisplayFunction_dec, _displayFunction_dec, _augmentFromDisplayFunction_dec, _rowKey_dec, _columns_dec, _dataActions_dec, _searchable_dec, _dataName_dec, _required_dec3, _disabled_dec3, _label_dec3, _key_dec2, _data_dec, _heading2_dec, _heading1_dec, _a42, _DeesTable_decorators, _init39, _heading1, _heading22, _data, _key2, _label3, _disabled3, _required3, _dataName, _searchable, _dataActions, _columns, _rowKey, _augmentFromDisplayFunction, _displayFunction, _reverseDisplayFunction, _selectedDataRow, _editableFields, _showVerticalLines, _showHorizontalLines, _showGrid, _sortBy, _filterText, _columnFilters, _showColumnFilters, _stickyHeader, _searchMode, _selectionMode, _selectedIds;
164525
+ function ordinalLabel(n12) {
164526
+ const s10 = ["th", "st", "nd", "rd"];
164527
+ const v5 = n12 % 100;
164528
+ return n12 + (s10[(v5 - 20) % 10] || s10[v5] || s10[0]);
164529
+ }
164530
+ __name(ordinalLabel, "ordinalLabel");
164452
164531
  _DeesTable_decorators = [customElement("dees-table")];
164453
164532
  var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [n5({
164454
164533
  type: String
@@ -164491,7 +164570,7 @@ var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [
164491
164570
  type: Boolean,
164492
164571
  reflect: true,
164493
164572
  attribute: "show-grid"
164494
- })], _sortKey_dec = [n5({ attribute: false })], _sortDir_dec = [n5({ attribute: false })], _filterText_dec = [n5({ type: String })], _columnFilters_dec = [n5({ attribute: false })], _showColumnFilters_dec = [n5({ type: Boolean, attribute: "show-column-filters" })], _stickyHeader_dec = [n5({ type: Boolean, reflect: true, attribute: "sticky-header" })], _searchMode_dec = [n5({ type: String })], _selectionMode_dec = [n5({ type: String })], _selectedIds_dec = [n5({ attribute: false })], _a42) {
164573
+ })], _sortBy_dec = [n5({ attribute: false })], _filterText_dec = [n5({ type: String })], _columnFilters_dec = [n5({ attribute: false })], _showColumnFilters_dec = [n5({ type: Boolean, attribute: "show-column-filters" })], _stickyHeader_dec = [n5({ type: Boolean, reflect: true, attribute: "sticky-header" })], _searchMode_dec = [n5({ type: String })], _selectionMode_dec = [n5({ type: String })], _selectedIds_dec = [n5({ attribute: false })], _a42) {
164495
164574
  constructor() {
164496
164575
  super();
164497
164576
  __privateAdd(this, _heading1, __runInitializers(_init39, 8, this, "heading 1")), __runInitializers(_init39, 11, this);
@@ -164518,17 +164597,16 @@ var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [
164518
164597
  __publicField(this, "files", []);
164519
164598
  __publicField(this, "fileWeakMap", /* @__PURE__ */ new WeakMap());
164520
164599
  __publicField(this, "dataChangeSubject", new domtools_pluginexports_exports.smartrx.rxjs.Subject());
164521
- __privateAdd(this, _sortKey, __runInitializers(_init39, 88, this)), __runInitializers(_init39, 91, this);
164522
- __privateAdd(this, _sortDir, __runInitializers(_init39, 92, this, null)), __runInitializers(_init39, 95, this);
164523
- __privateAdd(this, _filterText, __runInitializers(_init39, 96, this, "")), __runInitializers(_init39, 99, this);
164524
- __privateAdd(this, _columnFilters, __runInitializers(_init39, 100, this, {})), __runInitializers(_init39, 103, this);
164525
- __privateAdd(this, _showColumnFilters, __runInitializers(_init39, 104, this, false)), __runInitializers(_init39, 107, this);
164526
- __privateAdd(this, _stickyHeader, __runInitializers(_init39, 108, this, false)), __runInitializers(_init39, 111, this);
164527
- __privateAdd(this, _searchMode, __runInitializers(_init39, 112, this, "table")), __runInitializers(_init39, 115, this);
164600
+ __privateAdd(this, _sortBy, __runInitializers(_init39, 88, this, [])), __runInitializers(_init39, 91, this);
164601
+ __privateAdd(this, _filterText, __runInitializers(_init39, 92, this, "")), __runInitializers(_init39, 95, this);
164602
+ __privateAdd(this, _columnFilters, __runInitializers(_init39, 96, this, {})), __runInitializers(_init39, 99, this);
164603
+ __privateAdd(this, _showColumnFilters, __runInitializers(_init39, 100, this, false)), __runInitializers(_init39, 103, this);
164604
+ __privateAdd(this, _stickyHeader, __runInitializers(_init39, 104, this, false)), __runInitializers(_init39, 107, this);
164605
+ __privateAdd(this, _searchMode, __runInitializers(_init39, 108, this, "table")), __runInitializers(_init39, 111, this);
164528
164606
  __publicField(this, "__searchTextSub");
164529
164607
  __publicField(this, "__searchModeSub");
164530
- __privateAdd(this, _selectionMode, __runInitializers(_init39, 116, this, "none")), __runInitializers(_init39, 119, this);
164531
- __privateAdd(this, _selectedIds, __runInitializers(_init39, 120, this, /* @__PURE__ */ new Set())), __runInitializers(_init39, 123, this);
164608
+ __privateAdd(this, _selectionMode, __runInitializers(_init39, 112, this, "none")), __runInitializers(_init39, 115, this);
164609
+ __privateAdd(this, _selectedIds, __runInitializers(_init39, 116, this, /* @__PURE__ */ new Set())), __runInitializers(_init39, 119, this);
164532
164610
  __publicField(this, "_rowIdMap", /* @__PURE__ */ new WeakMap());
164533
164611
  __publicField(this, "_rowIdCounter", 0);
164534
164612
  __publicField(this, "__debounceTimer");
@@ -164549,8 +164627,7 @@ var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [
164549
164627
  const viewData = getViewData(
164550
164628
  this.data,
164551
164629
  effectiveColumns,
164552
- this.sortKey,
164553
- this.sortDir,
164630
+ this.sortBy,
164554
164631
  this.filterText,
164555
164632
  this.columnFilters,
164556
164633
  this.searchMode === "data" ? "data" : "table",
@@ -164645,7 +164722,8 @@ var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [
164645
164722
  role="columnheader"
164646
164723
  aria-sort=${ariaSort}
164647
164724
  style="${isSortable ? "cursor: pointer;" : ""}"
164648
- @click=${() => isSortable ? this.toggleSort(col) : null}
164725
+ @click=${(eventArg) => isSortable ? this.handleHeaderClick(eventArg, col, effectiveColumns) : null}
164726
+ @contextmenu=${(eventArg) => isSortable ? this.openHeaderContextMenu(eventArg, col, effectiveColumns) : null}
164649
164727
  >
164650
164728
  ${col.header ?? col.key}
164651
164729
  ${this.renderSortIndicator(col)}
@@ -164941,33 +165019,302 @@ var _DeesTable = class _DeesTable extends (_a42 = DeesElement, _heading1_dec = [
164941
165019
  table2.style.tableLayout = "fixed";
164942
165020
  }
164943
165021
  // compute helpers moved to ./data.ts
164944
- toggleSort(col) {
164945
- const key2 = String(col.key);
164946
- if (this.sortKey !== key2) {
164947
- this.sortKey = key2;
164948
- this.sortDir = "asc";
164949
- } else {
164950
- if (this.sortDir === "asc") this.sortDir = "desc";
164951
- else if (this.sortDir === "desc") {
164952
- this.sortDir = null;
164953
- this.sortKey = void 0;
164954
- } else this.sortDir = "asc";
164955
- }
165022
+ // ─── sort: public API ────────────────────────────────────────────────
165023
+ /** Returns the descriptor for `key` if the column is currently in the cascade. */
165024
+ getSortDescriptor(key2) {
165025
+ return this.sortBy.find((d6) => d6.key === key2);
165026
+ }
165027
+ /** Returns the 0-based priority of `key` in the cascade, or -1 if not present. */
165028
+ getSortPriority(key2) {
165029
+ return this.sortBy.findIndex((d6) => d6.key === key2);
165030
+ }
165031
+ /** Replaces the cascade with a single sort entry. */
165032
+ setSort(key2, dir) {
165033
+ this.sortBy = [{ key: key2, dir }];
165034
+ this.emitSortChange();
165035
+ this.requestUpdate();
165036
+ }
165037
+ /**
165038
+ * Inserts (or moves) `key` to a 0-based position in the cascade. If the key is
165039
+ * already present elsewhere, its previous entry is removed before insertion so
165040
+ * a column appears at most once.
165041
+ */
165042
+ addSortAt(key2, position3, dir) {
165043
+ const next2 = this.sortBy.filter((d6) => d6.key !== key2);
165044
+ const clamped = Math.max(0, Math.min(position3, next2.length));
165045
+ next2.splice(clamped, 0, { key: key2, dir });
165046
+ this.sortBy = next2;
165047
+ this.emitSortChange();
165048
+ this.requestUpdate();
165049
+ }
165050
+ /** Appends `key` to the end of the cascade (or moves it there if already present). */
165051
+ appendSort(key2, dir) {
165052
+ const next2 = this.sortBy.filter((d6) => d6.key !== key2);
165053
+ next2.push({ key: key2, dir });
165054
+ this.sortBy = next2;
165055
+ this.emitSortChange();
165056
+ this.requestUpdate();
165057
+ }
165058
+ /** Removes `key` from the cascade. No-op if not present. */
165059
+ removeSort(key2) {
165060
+ if (!this.sortBy.some((d6) => d6.key === key2)) return;
165061
+ this.sortBy = this.sortBy.filter((d6) => d6.key !== key2);
165062
+ this.emitSortChange();
165063
+ this.requestUpdate();
165064
+ }
165065
+ /** Empties the cascade. */
165066
+ clearSorts() {
165067
+ if (this.sortBy.length === 0) return;
165068
+ this.sortBy = [];
165069
+ this.emitSortChange();
165070
+ this.requestUpdate();
165071
+ }
165072
+ emitSortChange() {
164956
165073
  this.dispatchEvent(
164957
165074
  new CustomEvent("sortChange", {
164958
- detail: { key: this.sortKey, dir: this.sortDir },
165075
+ detail: { sortBy: this.sortBy.map((d6) => ({ ...d6 })) },
164959
165076
  bubbles: true
164960
165077
  })
164961
165078
  );
164962
- this.requestUpdate();
164963
165079
  }
165080
+ // ─── sort: header interaction handlers ───────────────────────────────
165081
+ /**
165082
+ * Plain left-click on a sortable header. Cycles `none → asc → desc → none`
165083
+ * collapsing the cascade to a single column. If a multi-column cascade is
165084
+ * active, asks the user to confirm the destructive replacement first. A
165085
+ * Shift+click bypasses the modal and routes through the multi-sort cycle.
165086
+ */
165087
+ async handleHeaderClick(eventArg, col, _effectiveColumns) {
165088
+ if (eventArg.shiftKey) {
165089
+ this.handleHeaderShiftClick(col);
165090
+ return;
165091
+ }
165092
+ const proceed = await this.confirmReplaceCascade(col);
165093
+ if (!proceed) return;
165094
+ this.cycleSingleSort(col);
165095
+ }
165096
+ /**
165097
+ * Cycles a single column through `none → asc → desc → none`, collapsing the
165098
+ * cascade. Used by both plain click and the menu's "Sort Ascending/Descending"
165099
+ * shortcuts (after confirmation).
165100
+ */
165101
+ cycleSingleSort(col) {
165102
+ const key2 = String(col.key);
165103
+ const current = this.sortBy.length === 1 && this.sortBy[0].key === key2 ? this.sortBy[0].dir : null;
165104
+ if (current === "asc") this.setSort(key2, "desc");
165105
+ else if (current === "desc") this.clearSorts();
165106
+ else this.setSort(key2, "asc");
165107
+ }
165108
+ /**
165109
+ * Shift+click cycle on a sortable header. Edits the cascade in place without
165110
+ * destroying other sort keys: append → flip dir → remove.
165111
+ */
165112
+ handleHeaderShiftClick(col) {
165113
+ const key2 = String(col.key);
165114
+ const existing = this.getSortDescriptor(key2);
165115
+ if (!existing) {
165116
+ this.appendSort(key2, "asc");
165117
+ } else if (existing.dir === "asc") {
165118
+ this.sortBy = this.sortBy.map((d6) => d6.key === key2 ? { key: key2, dir: "desc" } : d6);
165119
+ this.emitSortChange();
165120
+ this.requestUpdate();
165121
+ } else {
165122
+ this.removeSort(key2);
165123
+ }
165124
+ }
165125
+ /**
165126
+ * Opens a confirmation modal when the cascade has more than one entry and the
165127
+ * user attempts a destructive single-sort replacement. Resolves to `true` if
165128
+ * the user accepts, `false` if they cancel. If the cascade has 0 or 1 entries
165129
+ * the modal is skipped and we resolve to `true` immediately.
165130
+ */
165131
+ confirmReplaceCascade(targetCol) {
165132
+ if (this.sortBy.length <= 1) return Promise.resolve(true);
165133
+ return new Promise((resolve2) => {
165134
+ let settled = false;
165135
+ const settle = /* @__PURE__ */ __name((result) => {
165136
+ if (settled) return;
165137
+ settled = true;
165138
+ resolve2(result);
165139
+ }, "settle");
165140
+ const summary = this.sortBy.map((d6, i11) => {
165141
+ const c11 = this._lookupColumnByKey?.(d6.key);
165142
+ const label = c11?.header ?? d6.key;
165143
+ return b2`<li>${i11 + 1}. ${label} ${d6.dir === "asc" ? "\u25B2" : "\u25BC"}</li>`;
165144
+ });
165145
+ DeesModal.createAndShow({
165146
+ heading: "Replace multi-column sort?",
165147
+ width: "small",
165148
+ showCloseButton: true,
165149
+ content: b2`
165150
+ <div style="font-size:13px; line-height:1.55;">
165151
+ <p style="margin:0 0 8px;">
165152
+ You currently have a ${this.sortBy.length}-column sort active:
165153
+ </p>
165154
+ <ul style="margin:0 0 12px; padding-left:18px;">${summary}</ul>
165155
+ <p style="margin:0;">
165156
+ Continuing will discard the cascade and replace it with a single sort on
165157
+ <strong>${targetCol.header ?? String(targetCol.key)}</strong>.
165158
+ </p>
165159
+ </div>
165160
+ `,
165161
+ menuOptions: [
165162
+ {
165163
+ name: "Cancel",
165164
+ iconName: "lucide:x",
165165
+ action: /* @__PURE__ */ __name(async (modal) => {
165166
+ settle(false);
165167
+ await modal.destroy();
165168
+ return null;
165169
+ }, "action")
165170
+ },
165171
+ {
165172
+ name: "Replace",
165173
+ iconName: "lucide:check",
165174
+ action: /* @__PURE__ */ __name(async (modal) => {
165175
+ settle(true);
165176
+ await modal.destroy();
165177
+ return null;
165178
+ }, "action")
165179
+ }
165180
+ ]
165181
+ });
165182
+ });
165183
+ }
165184
+ /**
165185
+ * Looks up a column by its string key in the currently effective column set.
165186
+ * Used by the modal helper to render human-friendly labels.
165187
+ */
165188
+ _lookupColumnByKey(key2) {
165189
+ const usingColumns = Array.isArray(this.columns) && this.columns.length > 0;
165190
+ const effective = usingColumns ? computeEffectiveColumns(this.columns, this.augmentFromDisplayFunction, this.displayFunction, this.data) : computeColumnsFromDisplayFunction(this.displayFunction, this.data);
165191
+ return effective.find((c11) => String(c11.key) === key2);
165192
+ }
165193
+ /**
165194
+ * Opens the header context menu for explicit multi-sort priority control.
165195
+ */
165196
+ openHeaderContextMenu(eventArg, col, effectiveColumns) {
165197
+ const items = this.buildHeaderMenuItems(col, effectiveColumns);
165198
+ DeesContextmenu.openContextMenuWithOptions(eventArg, items);
165199
+ }
165200
+ /**
165201
+ * Builds the dynamic context-menu structure for a single column header.
165202
+ */
165203
+ buildHeaderMenuItems(col, effectiveColumns) {
165204
+ const key2 = String(col.key);
165205
+ const existing = this.getSortDescriptor(key2);
165206
+ const cascadeLen = this.sortBy.length;
165207
+ const sortableColumnCount = effectiveColumns.filter((c11) => !!c11.sortable).length;
165208
+ const maxSlot = Math.min(
165209
+ Math.max(cascadeLen + (existing ? 0 : 1), 1),
165210
+ Math.max(sortableColumnCount, 1)
165211
+ );
165212
+ const items = [];
165213
+ items.push({
165214
+ name: "Sort Ascending",
165215
+ iconName: cascadeLen === 1 && existing?.dir === "asc" ? "lucide:check" : "lucide:arrowUp",
165216
+ action: /* @__PURE__ */ __name(async () => {
165217
+ if (await this.confirmReplaceCascade(col)) this.setSort(key2, "asc");
165218
+ return null;
165219
+ }, "action")
165220
+ });
165221
+ items.push({
165222
+ name: "Sort Descending",
165223
+ iconName: cascadeLen === 1 && existing?.dir === "desc" ? "lucide:check" : "lucide:arrowDown",
165224
+ action: /* @__PURE__ */ __name(async () => {
165225
+ if (await this.confirmReplaceCascade(col)) this.setSort(key2, "desc");
165226
+ return null;
165227
+ }, "action")
165228
+ });
165229
+ items.push({ divider: true });
165230
+ for (let slot = 1; slot <= maxSlot; slot++) {
165231
+ const ordinal = ordinalLabel(slot);
165232
+ const isCurrentSlot = existing && this.getSortPriority(key2) === slot - 1;
165233
+ items.push({
165234
+ name: `Set as ${ordinal} sort`,
165235
+ iconName: isCurrentSlot ? "lucide:check" : "lucide:listOrdered",
165236
+ submenu: [
165237
+ {
165238
+ name: "Ascending",
165239
+ iconName: "lucide:arrowUp",
165240
+ action: /* @__PURE__ */ __name(async () => {
165241
+ this.addSortAt(key2, slot - 1, "asc");
165242
+ return null;
165243
+ }, "action")
165244
+ },
165245
+ {
165246
+ name: "Descending",
165247
+ iconName: "lucide:arrowDown",
165248
+ action: /* @__PURE__ */ __name(async () => {
165249
+ this.addSortAt(key2, slot - 1, "desc");
165250
+ return null;
165251
+ }, "action")
165252
+ }
165253
+ ]
165254
+ });
165255
+ }
165256
+ items.push({ divider: true });
165257
+ items.push({
165258
+ name: "Append to sort",
165259
+ iconName: "lucide:plus",
165260
+ submenu: [
165261
+ {
165262
+ name: "Ascending",
165263
+ iconName: "lucide:arrowUp",
165264
+ action: /* @__PURE__ */ __name(async () => {
165265
+ this.appendSort(key2, "asc");
165266
+ return null;
165267
+ }, "action")
165268
+ },
165269
+ {
165270
+ name: "Descending",
165271
+ iconName: "lucide:arrowDown",
165272
+ action: /* @__PURE__ */ __name(async () => {
165273
+ this.appendSort(key2, "desc");
165274
+ return null;
165275
+ }, "action")
165276
+ }
165277
+ ]
165278
+ });
165279
+ if (existing) {
165280
+ items.push({ divider: true });
165281
+ items.push({
165282
+ name: "Remove from sort",
165283
+ iconName: "lucide:minus",
165284
+ action: /* @__PURE__ */ __name(async () => {
165285
+ this.removeSort(key2);
165286
+ return null;
165287
+ }, "action")
165288
+ });
165289
+ }
165290
+ if (cascadeLen > 0) {
165291
+ if (!existing) items.push({ divider: true });
165292
+ items.push({
165293
+ name: "Clear all sorts",
165294
+ iconName: "lucide:trash",
165295
+ action: /* @__PURE__ */ __name(async () => {
165296
+ this.clearSorts();
165297
+ return null;
165298
+ }, "action")
165299
+ });
165300
+ }
165301
+ return items;
165302
+ }
165303
+ // ─── sort: indicator + ARIA ──────────────────────────────────────────
164964
165304
  getAriaSort(col) {
164965
- if (String(col.key) !== this.sortKey || !this.sortDir) return "none";
164966
- return this.sortDir === "asc" ? "ascending" : "descending";
165305
+ const primary = this.sortBy[0];
165306
+ if (!primary || primary.key !== String(col.key)) return "none";
165307
+ return primary.dir === "asc" ? "ascending" : "descending";
164967
165308
  }
164968
165309
  renderSortIndicator(col) {
164969
- if (String(col.key) !== this.sortKey || !this.sortDir) return b2``;
164970
- return b2`<span style="margin-left:6px; opacity:0.7;">${this.sortDir === "asc" ? "\u25B2" : "\u25BC"}</span>`;
165310
+ const idx = this.getSortPriority(String(col.key));
165311
+ if (idx < 0) return b2``;
165312
+ const desc = this.sortBy[idx];
165313
+ const arrow2 = desc.dir === "asc" ? "\u25B2" : "\u25BC";
165314
+ if (this.sortBy.length === 1) {
165315
+ return b2`<span class="sortArrow">${arrow2}</span>`;
165316
+ }
165317
+ return b2`<span class="sortArrow">${arrow2}</span><span class="sortBadge">${idx + 1}</span>`;
164971
165318
  }
164972
165319
  // filtering helpers
164973
165320
  setFilterText(value2) {
@@ -165132,8 +165479,7 @@ _editableFields = new WeakMap();
165132
165479
  _showVerticalLines = new WeakMap();
165133
165480
  _showHorizontalLines = new WeakMap();
165134
165481
  _showGrid = new WeakMap();
165135
- _sortKey = new WeakMap();
165136
- _sortDir = new WeakMap();
165482
+ _sortBy = new WeakMap();
165137
165483
  _filterText = new WeakMap();
165138
165484
  _columnFilters = new WeakMap();
165139
165485
  _showColumnFilters = new WeakMap();
@@ -165161,8 +165507,7 @@ __decorateElement(_init39, 4, "editableFields", _editableFields_dec, _DeesTable,
165161
165507
  __decorateElement(_init39, 4, "showVerticalLines", _showVerticalLines_dec, _DeesTable, _showVerticalLines);
165162
165508
  __decorateElement(_init39, 4, "showHorizontalLines", _showHorizontalLines_dec, _DeesTable, _showHorizontalLines);
165163
165509
  __decorateElement(_init39, 4, "showGrid", _showGrid_dec, _DeesTable, _showGrid);
165164
- __decorateElement(_init39, 4, "sortKey", _sortKey_dec, _DeesTable, _sortKey);
165165
- __decorateElement(_init39, 4, "sortDir", _sortDir_dec, _DeesTable, _sortDir);
165510
+ __decorateElement(_init39, 4, "sortBy", _sortBy_dec, _DeesTable, _sortBy);
165166
165511
  __decorateElement(_init39, 4, "filterText", _filterText_dec, _DeesTable, _filterText);
165167
165512
  __decorateElement(_init39, 4, "columnFilters", _columnFilters_dec, _DeesTable, _columnFilters);
165168
165513
  __decorateElement(_init39, 4, "showColumnFilters", _showColumnFilters_dec, _DeesTable, _showColumnFilters);
@@ -199016,7 +199361,7 @@ init_group_runtime();
199016
199361
  // ts_web/00_commitinfo_data.ts
199017
199362
  var commitinfo = {
199018
199363
  name: "@design.estate/dees-catalog",
199019
- version: "3.61.2",
199364
+ version: "3.62.0",
199020
199365
  description: "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript."
199021
199366
  };
199022
199367
  export {
@@ -200989,4 +201334,4 @@ ibantools/jsnext/ibantools.js:
200989
201334
  * @preferred
200990
201335
  *)
200991
201336
  */
200992
- //# sourceMappingURL=bundle-1775472094927.js.map
201337
+ //# sourceMappingURL=bundle-1775564414196.js.map
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '3.61.2',
6
+ version: '3.62.0',
7
7
  description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsNkJBQTZCO0lBQ25DLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSxzSkFBc0o7Q0FDcEssQ0FBQSJ9
@@ -1,5 +1,11 @@
1
- import type { Column, TDisplayFunction } from './types.js';
1
+ import type { Column, ISortDescriptor, TDisplayFunction } from './types.js';
2
2
  export declare function computeColumnsFromDisplayFunction<T>(displayFunction: TDisplayFunction<T>, data: T[]): Column<T>[];
3
3
  export declare function computeEffectiveColumns<T>(columns: Column<T>[] | undefined, augmentFromDisplayFunction: boolean, displayFunction: TDisplayFunction<T>, data: T[]): Column<T>[];
4
4
  export declare function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDisplayFunction<T>): any;
5
- export declare function getViewData<T>(data: T[], effectiveColumns: Column<T>[], sortKey?: string, sortDir?: 'asc' | 'desc' | null, filterText?: string, columnFilters?: Record<string, string>, filterMode?: 'table' | 'data', lucenePredicate?: (row: T) => boolean): T[];
5
+ /**
6
+ * Compares two cell values in ascending order. Returns -1, 0, or 1.
7
+ * Null/undefined values sort before defined values. Numbers compare numerically;
8
+ * everything else compares as case-insensitive strings.
9
+ */
10
+ export declare function compareCellValues(va: any, vb: any): number;
11
+ export declare function getViewData<T>(data: T[], effectiveColumns: Column<T>[], sortBy: ISortDescriptor[], filterText?: string, columnFilters?: Record<string, string>, filterMode?: 'table' | 'data', lucenePredicate?: (row: T) => boolean): T[];