flexdesk 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -321,6 +321,39 @@ behaves as it did in 0.4, except for these:
321
321
  to it if it is in a cascade layer, or has zero specificity and loads before
322
322
  `flexdesk.css`. A rule for the cells, the rows or the table does not.
323
323
 
324
+ ### Comparing two tables: `diff` and `createScrollLock`
325
+
326
+ A side-by-side comparison is two `DataTable`s drawn from ONE aligned row list:
327
+ the rows as they are on one side, as they were on the other, and a gap where
328
+ a side has no row. Both options are off until you set them.
329
+
330
+ ```js
331
+ import { DataTable, createScrollLock } from '@flexdesk/widgets';
332
+
333
+ const lock = createScrollLock({ axis: 'y' }); // 'x', 'y' or 'both'
334
+ const side = (host, pick) => DataTable.mount(host, {
335
+ headers, rows: aligned.map((r) => pick(r) ?? blank), pagination: false,
336
+ scrollLock: lock,
337
+ diff: (_row, i) => ({ status: statusOf(aligned[i], pick), cells: changedCols(aligned[i]) }),
338
+ });
339
+ side(left, (r) => r.now);
340
+ side(right, (r) => r.then);
341
+ lock.setEnabled(false); // scroll each side on its own; true brings them level
342
+ ```
343
+
344
+ - `diff: (row, rowIdx) => {status, cells?}` draws the row
345
+ `twm-dt-diff twm-dt-diff--<status>`, for `added`, `removed`, `changed`,
346
+ `same` or `gap`, and each column index in `cells` (an array or a Set)
347
+ `twm-dt-diff__cell--changed`. `null` or any other status draws the row as
348
+ without `diff`. A `gap` row's cells are drawn empty, whatever its values and
349
+ `renderCell`, so the two sides keep their rows level. `updateRow` asks again.
350
+ - `scrollLock` takes a group from `createScrollLock()`. The table's body box
351
+ joins it on every render and leaves on the next render and on `dispose()`.
352
+ Any element can join with `lock.attach(el)`, which returns its detach. A
353
+ box that joins starts at the group's last position, and the echo of a
354
+ position the lock itself wrote moves nobody back. The lock copies pixels, so
355
+ it lines rows up only when both sides draw rows of the same height.
356
+
324
357
  ### Scrollbars
325
358
 
326
359
  `installAutoScrollbars(root)` (from `@flexdesk/widgets`) replaces the browser's
package/css/base.css CHANGED
@@ -686,6 +686,39 @@ body.twm-dt-col-resizing {
686
686
  color: var(--text-dim, #666);
687
687
  font-style: italic;
688
688
  }
689
+ /* 0.6.1 `diff` — one side of a comparison. A row that is NEW on this side is
690
+ * green, one that is GONE on this side red, a changed row carries an amber
691
+ * left edge and its changed cells an amber fill, and a GAP — the place a row
692
+ * holds on the other side — is hatched, so the eye reads it as "nothing here"
693
+ * rather than as an empty row. (0,3,2) and up, for `twm-dt-cell--null`'s
694
+ * reason above; a selected row keeps its own colour (`:not(.selected)`). */
695
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--added:not(.selected) > td {
696
+ background: color-mix(in srgb, var(--color-success, #3fb950) 16%, transparent);
697
+ }
698
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed:not(.selected) > td {
699
+ background: color-mix(in srgb, var(--color-danger, #c74e4e) 18%, transparent);
700
+ }
701
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed > td {
702
+ text-decoration: line-through;
703
+ text-decoration-color: color-mix(in srgb, var(--color-danger, #c74e4e) 70%, transparent);
704
+ }
705
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--added > td:first-child {
706
+ box-shadow: inset 3px 0 0 0 var(--color-success, #3fb950);
707
+ }
708
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed > td:first-child {
709
+ box-shadow: inset 3px 0 0 0 var(--color-danger, #c74e4e);
710
+ }
711
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--changed > td:first-child {
712
+ box-shadow: inset 3px 0 0 0 var(--color-warning, #ff9800);
713
+ }
714
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff:not(.selected) > td.twm-dt-diff__cell--changed {
715
+ background: color-mix(in srgb, var(--color-warning, #ff9800) 22%, transparent);
716
+ }
717
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--gap > td.twm-dt-diff__gap {
718
+ background: repeating-linear-gradient(135deg,
719
+ transparent 0 6px,
720
+ color-mix(in srgb, var(--text-muted, #888) 14%, transparent) 6px 7px);
721
+ }
689
722
  /* `emptyState` and `setError` with no rows: the box's own words, which may
690
723
  * wrap — muted for an empty state, red for a failure, in both densities. Quiet
691
724
  * while loading — an empty table that is still loading is not empty. */
package/css/flexdesk.css CHANGED
@@ -713,6 +713,39 @@ body.twm-dt-col-resizing {
713
713
  color: var(--text-dim, #666);
714
714
  font-style: italic;
715
715
  }
716
+ /* 0.6.1 `diff` — one side of a comparison. A row that is NEW on this side is
717
+ * green, one that is GONE on this side red, a changed row carries an amber
718
+ * left edge and its changed cells an amber fill, and a GAP — the place a row
719
+ * holds on the other side — is hatched, so the eye reads it as "nothing here"
720
+ * rather than as an empty row. (0,3,2) and up, for `twm-dt-cell--null`'s
721
+ * reason above; a selected row keeps its own colour (`:not(.selected)`). */
722
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--added:not(.selected) > td {
723
+ background: color-mix(in srgb, var(--color-success, #3fb950) 16%, transparent);
724
+ }
725
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed:not(.selected) > td {
726
+ background: color-mix(in srgb, var(--color-danger, #c74e4e) 18%, transparent);
727
+ }
728
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed > td {
729
+ text-decoration: line-through;
730
+ text-decoration-color: color-mix(in srgb, var(--color-danger, #c74e4e) 70%, transparent);
731
+ }
732
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--added > td:first-child {
733
+ box-shadow: inset 3px 0 0 0 var(--color-success, #3fb950);
734
+ }
735
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--removed > td:first-child {
736
+ box-shadow: inset 3px 0 0 0 var(--color-danger, #c74e4e);
737
+ }
738
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--changed > td:first-child {
739
+ box-shadow: inset 3px 0 0 0 var(--color-warning, #ff9800);
740
+ }
741
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff:not(.selected) > td.twm-dt-diff__cell--changed {
742
+ background: color-mix(in srgb, var(--color-warning, #ff9800) 22%, transparent);
743
+ }
744
+ .twm-data-table-component .twm-preview-table tbody tr.twm-dt-diff--gap > td.twm-dt-diff__gap {
745
+ background: repeating-linear-gradient(135deg,
746
+ transparent 0 6px,
747
+ color-mix(in srgb, var(--text-muted, #888) 14%, transparent) 6px 7px);
748
+ }
716
749
  /* `emptyState` and `setError` with no rows: the box's own words, which may
717
750
  * wrap — muted for an empty state, red for a failure, in both densities. Quiet
718
751
  * while loading — an empty table that is still loading is not empty. */
package/dist/charts.js CHANGED
@@ -39,10 +39,10 @@ import {
39
39
  setPlotlySource,
40
40
  uid,
41
41
  updateChart
42
- } from "./chunk-CNXP3SIN.js";
42
+ } from "./chunk-ZMCRDOCF.js";
43
43
  import {
44
44
  createRafResizeObserver
45
- } from "./chunk-KH2GJTKR.js";
45
+ } from "./chunk-IYV5JVYB.js";
46
46
  import {
47
47
  ManagedWindow
48
48
  } from "./chunk-6JSOVNID.js";
@@ -360,6 +360,7 @@ function matchDurationFilter(ms, filterText, shown) {
360
360
 
361
361
  // src/ui/components/data_table.js
362
362
  var DEFAULT_PAGE_SIZE = 100;
363
+ var DIFF_STATUSES = /* @__PURE__ */ new Set(["added", "removed", "changed", "same", "gap"]);
363
364
  var TYPED_COLUMNS = /* @__PURE__ */ new Set(["date", "datetime", "duration"]);
364
365
  var DESELECTING_CLICKS = /* @__PURE__ */ new WeakSet();
365
366
  var AUTO_DISPOSE = /* @__PURE__ */ new Map();
@@ -531,6 +532,8 @@ var DataTable = class {
531
532
  rowClass: null,
532
533
  rowAttrs: null,
533
534
  cellClass: null,
535
+ diff: null,
536
+ scrollLock: null,
534
537
  rowIcon: null,
535
538
  emptyState: void 0,
536
539
  nullDisplay: null,
@@ -1009,6 +1012,9 @@ var DataTable = class {
1009
1012
  }
1010
1013
  this._tableWrapEl = tableWrap;
1011
1014
  this.container.appendChild(this._wrapperEl);
1015
+ if (this.config.scrollLock && typeof this.config.scrollLock.attach === "function") {
1016
+ this._disposers.push(this.config.scrollLock.attach(tableWrap));
1017
+ }
1012
1018
  if (this.config.autoDispose) autoDisposeWatch(this);
1013
1019
  if (prevScroll && this._state.offset === this._renderedOffset) {
1014
1020
  tableWrap.scrollTop = prevScroll.top;
@@ -2411,11 +2417,20 @@ var DataTable = class {
2411
2417
  tr.appendChild(td);
2412
2418
  }
2413
2419
  const { cellClass, nullDisplay } = this.config;
2420
+ const diff = this._diffOf(row, globalIdx);
2421
+ const gap = diff?.status === "gap";
2422
+ const changedCells = diff?.cells ? diff.cells instanceof Set ? diff.cells : new Set(diff.cells) : null;
2414
2423
  for (let colIdx = 0; colIdx < row.length; colIdx++) {
2415
2424
  const td = document.createElement("td");
2416
2425
  td.className = this._columnTypes[colIdx] || "text";
2417
2426
  const value = row[colIdx];
2418
2427
  let handled = false;
2428
+ if (gap) {
2429
+ td.classList.add("twm-dt-diff__gap");
2430
+ tr.appendChild(td);
2431
+ continue;
2432
+ }
2433
+ if (changedCells?.has(colIdx)) td.classList.add("twm-dt-diff__cell--changed");
2419
2434
  if (this.config.renderCell) {
2420
2435
  handled = !!this.config.renderCell(td, value, colIdx, globalIdx, row);
2421
2436
  if (!handled) {
@@ -2682,6 +2697,14 @@ var DataTable = class {
2682
2697
  }
2683
2698
  return null;
2684
2699
  }
2700
+ /** 0.6.1. The `diff` answer for one row, or `null` when there is none or
2701
+ * its status is not one this component draws. */
2702
+ _diffOf(row, rowIdx) {
2703
+ if (typeof this.config.diff !== "function") return null;
2704
+ const answer = this.config.diff(row, rowIdx);
2705
+ if (!answer || !DIFF_STATUSES.has(answer.status)) return null;
2706
+ return answer;
2707
+ }
2685
2708
  /** Everything `rowClass`, `rowAttrs` and the open mark put on a `<tr>`,
2686
2709
  * taken off again first — so `updateRow` cannot leave last state's class
2687
2710
  * behind. */
@@ -2696,6 +2719,11 @@ var DataTable = class {
2696
2719
  const added = this._addClasses(tr, rowClass(row, rowIdx));
2697
2720
  tr.__twmClasses = added.filter((c) => c !== "data-preview-row" && c !== "selected");
2698
2721
  }
2722
+ for (const c of [...tr.classList]) {
2723
+ if (c === "twm-dt-diff" || c.startsWith("twm-dt-diff--")) tr.classList.remove(c);
2724
+ }
2725
+ const diff = this._diffOf(row, rowIdx);
2726
+ if (diff) tr.classList.add("twm-dt-diff", `twm-dt-diff--${diff.status}`);
2699
2727
  if (tr.__twmAttrs) {
2700
2728
  for (const name of tr.__twmAttrs) tr.removeAttribute(name);
2701
2729
  tr.__twmAttrs = null;
@@ -3699,4 +3727,4 @@ export {
3699
3727
  matchDurationFilter,
3700
3728
  DataTable
3701
3729
  };
3702
- //# sourceMappingURL=chunk-KH2GJTKR.js.map
3730
+ //# sourceMappingURL=chunk-IYV5JVYB.js.map