@ixfx/components 0.9.6 → 0.9.8

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 (84) hide show
  1. package/bundle/index.d.ts +100 -64
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +382 -253
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +2 -2
  6. package/dist/ac-text.js +2 -2
  7. package/dist/ac-token.js +2 -2
  8. package/dist/checkbox.js +1 -1
  9. package/dist/{colour-picker-CSxwUmtE.js → colour-picker-R73i0QTP.js} +2 -2
  10. package/dist/{colour-picker-CSxwUmtE.js.map → colour-picker-R73i0QTP.js.map} +1 -1
  11. package/dist/colour-picker.js +1 -1
  12. package/dist/crumbs.js +2 -2
  13. package/dist/data-display.js +1 -1
  14. package/dist/{data-grid-DIHhfsqS.d.ts → data-grid-BUeZjnvj.d.ts} +38 -1
  15. package/dist/data-grid-BUeZjnvj.d.ts.map +1 -0
  16. package/dist/data-grid.d.ts +1 -1
  17. package/dist/data-grid.js +156 -26
  18. package/dist/data-grid.js.map +1 -1
  19. package/dist/detail-list.js +2 -2
  20. package/dist/editable-label.js +1 -1
  21. package/dist/{fallbacks-BcKGq-2F.js → fallbacks-DQ_5OsmR.js} +2 -2
  22. package/dist/{fallbacks-BcKGq-2F.js.map → fallbacks-DQ_5OsmR.js.map} +1 -1
  23. package/dist/{grid-list-mL0GJTQR.js → grid-list-40blkeza.js} +5 -232
  24. package/dist/grid-list-40blkeza.js.map +1 -0
  25. package/dist/{grid-list-CSw1VMXT.d.ts → grid-list-lTwg4lp7.d.ts} +3 -66
  26. package/dist/grid-list-lTwg4lp7.d.ts.map +1 -0
  27. package/dist/grid-list.d.ts +1 -1
  28. package/dist/grid-list.js +1 -1
  29. package/dist/{icon-Dy33gu4Z.js → icon-BCJJYEpm.js} +2 -2
  30. package/dist/{icon-Dy33gu4Z.js.map → icon-BCJJYEpm.js.map} +1 -1
  31. package/dist/icons.js +1 -1
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +13 -12
  35. package/dist/index.js.map +1 -1
  36. package/dist/labelled-radial-input.js +2 -2
  37. package/dist/{labelled-slider-input-aEYDvW2S.js → labelled-slider-input-CF2RtzgG.js} +3 -3
  38. package/dist/{labelled-slider-input-aEYDvW2S.js.map → labelled-slider-input-CF2RtzgG.js.map} +1 -1
  39. package/dist/labelled-slider-input.js +1 -1
  40. package/dist/{menu-CMXhIaNZ.js → menu-C8a-84S5.js} +4 -4
  41. package/dist/{menu-CMXhIaNZ.js.map → menu-C8a-84S5.js.map} +1 -1
  42. package/dist/{menu-item-BYGQzyCe.js → menu-item-DzizkMH-.js} +3 -3
  43. package/dist/{menu-item-BYGQzyCe.js.map → menu-item-DzizkMH-.js.map} +1 -1
  44. package/dist/menu.js +2 -2
  45. package/dist/miller.js +1 -1
  46. package/dist/narrowed-text.js +1 -1
  47. package/dist/notifier.js +1 -1
  48. package/dist/pan-text-CoYmt2qd.d.ts +67 -0
  49. package/dist/pan-text-CoYmt2qd.d.ts.map +1 -0
  50. package/dist/pan-text-CqtvjPyK.js +235 -0
  51. package/dist/pan-text-CqtvjPyK.js.map +1 -0
  52. package/dist/panel.js +1 -1
  53. package/dist/polar-pad.js +1 -1
  54. package/dist/{radial-input-DyxfbNzD.js → radial-input-KMVltQfe.js} +2 -2
  55. package/dist/{radial-input-DyxfbNzD.js.map → radial-input-KMVltQfe.js.map} +1 -1
  56. package/dist/radial-input.js +1 -1
  57. package/dist/range-input.js +1 -1
  58. package/dist/range.js +1 -1
  59. package/dist/select-horiz.js +1 -1
  60. package/dist/{slider-input-Cb5uSV42.js → slider-input-ByHdotzp.js} +2 -2
  61. package/dist/{slider-input-Cb5uSV42.js.map → slider-input-ByHdotzp.js.map} +1 -1
  62. package/dist/slider-input.js +1 -1
  63. package/dist/snackbar.js +1 -1
  64. package/dist/{split-layout-Bx0g8k8-.js → split-layout-3yBOEVZW.js} +2 -2
  65. package/dist/{split-layout-Bx0g8k8-.js.map → split-layout-3yBOEVZW.js.map} +1 -1
  66. package/dist/split-layout.js +1 -1
  67. package/dist/style.css +2 -2
  68. package/dist/swipe.js +1 -1
  69. package/dist/tabs.js +1 -1
  70. package/dist/{timeline-THyvH8xy.js → timeline-CFlBFFGZ.js} +2 -2
  71. package/dist/{timeline-THyvH8xy.js.map → timeline-CFlBFFGZ.js.map} +1 -1
  72. package/dist/timeline.js +1 -1
  73. package/dist/titlebar.js +2 -2
  74. package/dist/{tree-DAFBO_MF.js → tree-CVj7TIk2.js} +2 -2
  75. package/dist/{tree-DAFBO_MF.js.map → tree-CVj7TIk2.js.map} +1 -1
  76. package/dist/tree.js +1 -1
  77. package/dist/vertical-list.js +1 -1
  78. package/dist/xy-pad.js +1 -1
  79. package/docs-user/data-grid.md +59 -0
  80. package/docs-user/index.json +2 -2
  81. package/package.json +1 -1
  82. package/dist/data-grid-DIHhfsqS.d.ts.map +0 -1
  83. package/dist/grid-list-CSw1VMXT.d.ts.map +0 -1
  84. package/dist/grid-list-mL0GJTQR.js.map +0 -1
package/bundle/index.js CHANGED
@@ -8,8 +8,8 @@ import { Colour } from "@ixfx/visual";
8
8
  import { Trees } from "@ixfx/collections";
9
9
  import { clamp, clamper, rangeStream, round, scaler } from "@ixfx/numbers";
10
10
  import { repeat } from "lit/directives/repeat.js";
11
- import { createRef, ref } from "lit/directives/ref.js";
12
11
  import { classMap } from "lit/directives/class-map.js";
12
+ import { createRef, ref } from "lit/directives/ref.js";
13
13
  import { Easings } from "@ixfx/modulation";
14
14
  import "@clinth/vcanvas";
15
15
  import { keyed } from "lit/directives/keyed.js";
@@ -1344,7 +1344,7 @@ const _themeFallbacksCss = i$3`
1344
1344
  @property --item-border-tickled {
1345
1345
  syntax: '*';
1346
1346
  inherits: true;
1347
- initial-value: 1px solid color-mix(in oklch, oklch(60% 0.15 140) 40%, transparent);
1347
+ initial-value: 1px solid oklch(19% 0.02 240);
1348
1348
  }
1349
1349
  @property --item-border-selected {
1350
1350
  syntax: '*';
@@ -13900,6 +13900,233 @@ var menu_exports = /* @__PURE__ */ __exportAll({
13900
13900
  menuFromTreeToElements: () => menuFromTreeToElements
13901
13901
  });
13902
13902
  //#endregion
13903
+ //#region src/pan-text/pan-text.ts
13904
+ const SPEED = 45;
13905
+ const END_PADDING = 4;
13906
+ const START_DELAY = 300;
13907
+ const END_DWELL = 2e3;
13908
+ const RESET_DURATION = 400;
13909
+ const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
13910
+ let PanTextElement = class PanTextElement extends i {
13911
+ constructor(..._args) {
13912
+ super(..._args);
13913
+ this.direction = `end`;
13914
+ this.disabled = false;
13915
+ this.mode = `hover`;
13916
+ this._isOverflowing = false;
13917
+ this._isHovering = false;
13918
+ this._loopActive = false;
13919
+ this._loopAtEnd = false;
13920
+ this._overflow = 0;
13921
+ this._resizeObserver = null;
13922
+ this._loopTimer = 0;
13923
+ this._onMouseEnter = () => {
13924
+ this._isHovering = true;
13925
+ };
13926
+ this._onMouseLeave = () => {
13927
+ this._isHovering = false;
13928
+ };
13929
+ this._measure = () => {
13930
+ if (this._isHovering || this._loopAtEnd) return;
13931
+ const el = this._textEl;
13932
+ if (!el) return;
13933
+ this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
13934
+ const overflowing = this._overflow > 0;
13935
+ this.classList.toggle(`is-overflowing`, overflowing);
13936
+ if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
13937
+ if (this._loopActive && this.mode === `loop` && !this.disabled && overflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
13938
+ if (!overflowing && this._loopAtEnd) {
13939
+ this._loopAtEnd = false;
13940
+ this.requestUpdate();
13941
+ }
13942
+ };
13943
+ }
13944
+ connectedCallback() {
13945
+ super.connectedCallback();
13946
+ this._resizeObserver = new ResizeObserver(() => this._measure());
13947
+ this.addEventListener(`mouseenter`, this._onMouseEnter);
13948
+ this.addEventListener(`mouseleave`, this._onMouseLeave);
13949
+ }
13950
+ disconnectedCallback() {
13951
+ super.disconnectedCallback();
13952
+ this._clearLoopTimer();
13953
+ this._loopActive = false;
13954
+ this._loopAtEnd = false;
13955
+ this._resizeObserver?.disconnect();
13956
+ this._resizeObserver = null;
13957
+ this.removeEventListener(`mouseenter`, this._onMouseEnter);
13958
+ this.removeEventListener(`mouseleave`, this._onMouseLeave);
13959
+ }
13960
+ firstUpdated() {
13961
+ this._measure();
13962
+ this._resizeObserver?.observe(this);
13963
+ document.fonts?.ready.then(() => this._measure());
13964
+ if (this.mode === `loop`) this.startLoop();
13965
+ }
13966
+ updated(changed) {
13967
+ if (changed.has(`mode`)) {
13968
+ if (this.mode === `loop`) this.startLoop();
13969
+ else this.stopLoop();
13970
+ }
13971
+ if (changed.has(`disabled`) && this.disabled) this.stopLoop();
13972
+ this._measure();
13973
+ }
13974
+ /**
13975
+ * Whether a loop pan is currently active (between `startLoop()` and `stopLoop()`).
13976
+ */
13977
+ get looping() {
13978
+ return this._loopActive;
13979
+ }
13980
+ /**
13981
+ * Begin looping: pan to the end, dwell there for 2 seconds, reset to the
13982
+ * start and pan again, repeating until `stopLoop()` is called. A no-op when
13983
+ * `disabled`, when there is nothing to reveal, or under reduced-motion.
13984
+ */
13985
+ startLoop() {
13986
+ if (this.disabled || this._prefersReducedMotion()) return;
13987
+ this._loopActive = true;
13988
+ this._loopAtEnd = false;
13989
+ this.requestUpdate();
13990
+ if (this._isOverflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
13991
+ }
13992
+ /**
13993
+ * Stop a loop started with `startLoop()` and snap back to the rest position.
13994
+ */
13995
+ stopLoop() {
13996
+ if (!this._loopActive && !this._loopAtEnd && this._loopTimer === 0) return;
13997
+ this._clearLoopTimer();
13998
+ this._loopActive = false;
13999
+ this._loopAtEnd = false;
14000
+ this.requestUpdate();
14001
+ }
14002
+ _prefersReducedMotion() {
14003
+ return globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches ?? false;
14004
+ }
14005
+ _panDuration() {
14006
+ return Math.max(1.5, (this._overflow + END_PADDING) / SPEED);
14007
+ }
14008
+ _clearLoopTimer() {
14009
+ if (this._loopTimer !== 0) {
14010
+ clearTimeout(this._loopTimer);
14011
+ this._loopTimer = 0;
14012
+ }
14013
+ }
14014
+ _scheduleLoopPan(delay) {
14015
+ this._clearLoopTimer();
14016
+ this._loopTimer = window.setTimeout(() => {
14017
+ this._loopTimer = 0;
14018
+ if (!this._loopActive || this.disabled || !this._isOverflowing) return;
14019
+ this._loopAtEnd = true;
14020
+ this.requestUpdate();
14021
+ const dwell = this._panDuration() * 1e3 + END_DWELL;
14022
+ this._loopTimer = window.setTimeout(() => {
14023
+ this._loopTimer = 0;
14024
+ if (!this._loopActive) return;
14025
+ this._loopAtEnd = false;
14026
+ this.requestUpdate();
14027
+ this._scheduleLoopPan(700);
14028
+ }, dwell);
14029
+ }, delay);
14030
+ }
14031
+ _textStyle() {
14032
+ if (!this._isOverflowing) return ``;
14033
+ const distance = this._overflow + END_PADDING;
14034
+ const duration = Math.max(1.5, distance / SPEED);
14035
+ const revealEnd = this.direction === `end`;
14036
+ const restX = revealEnd ? 0 : -distance;
14037
+ const hoverX = revealEnd ? -distance : 0;
14038
+ if (this._loopActive && this.mode === `loop`) {
14039
+ if (this._loopAtEnd) return [
14040
+ `transform: translateX(${hoverX}px)`,
14041
+ `transition: transform ${duration}s ${PAN_EASING}`,
14042
+ `overflow: visible`,
14043
+ `text-overflow: clip`
14044
+ ].join(`; `);
14045
+ return [
14046
+ `transform: translateX(${restX}px)`,
14047
+ `transition: transform ${RESET_DURATION}ms ${PAN_EASING}`,
14048
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
14049
+ ].filter(Boolean).join(`; `);
14050
+ }
14051
+ if (this._isHovering) return [
14052
+ `transform: translateX(${hoverX}px)`,
14053
+ `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
14054
+ `overflow: visible`,
14055
+ `text-overflow: clip`
14056
+ ].join(`; `);
14057
+ return [
14058
+ `transform: translateX(${restX}px)`,
14059
+ `transition: transform 400ms ${PAN_EASING}`,
14060
+ revealEnd ? `` : `overflow: visible; text-overflow: clip`
14061
+ ].filter(Boolean).join(`; `);
14062
+ }
14063
+ render() {
14064
+ const textClasses = {
14065
+ "is-overflowing": this._isOverflowing,
14066
+ "is-hovering": this._isHovering
14067
+ };
14068
+ return b`
14069
+ <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
14070
+ <slot @slotchange=${this._measure}></slot>
14071
+ </span>
14072
+ `;
14073
+ }
14074
+ static {
14075
+ this.styles = [themeFallbacks, i$3`
14076
+ /* Renders like plain inline text: no colours, border, padding or margin
14077
+ of its own — it only adds the clipping needed to pan on hover. */
14078
+ :host {
14079
+ display: inline-block;
14080
+ overflow: hidden;
14081
+ white-space: nowrap;
14082
+ max-width: 100%;
14083
+ box-sizing: border-box;
14084
+ font: inherit;
14085
+ color: inherit;
14086
+ }
14087
+
14088
+ :host([disabled]) {
14089
+ opacity: var(--disabled-opacity, 0.5);
14090
+ pointer-events: none;
14091
+ }
14092
+
14093
+ .text {
14094
+ display: block;
14095
+ overflow: hidden;
14096
+ text-overflow: ellipsis;
14097
+ white-space: nowrap;
14098
+ will-change: transform;
14099
+ }
14100
+
14101
+ @media (prefers-reduced-motion: reduce) {
14102
+ .text {
14103
+ transition: none !important;
14104
+ transform: none !important;
14105
+ }
14106
+ }
14107
+ `];
14108
+ }
14109
+ };
14110
+ __decorate([property({
14111
+ type: String,
14112
+ attribute: `direction`
14113
+ })], PanTextElement.prototype, "direction", void 0);
14114
+ __decorate([property({
14115
+ type: Boolean,
14116
+ attribute: `disabled`
14117
+ })], PanTextElement.prototype, "disabled", void 0);
14118
+ __decorate([property({
14119
+ type: String,
14120
+ attribute: `mode`,
14121
+ reflect: true
14122
+ })], PanTextElement.prototype, "mode", void 0);
14123
+ __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
14124
+ __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
14125
+ __decorate([state()], PanTextElement.prototype, "_loopActive", void 0);
14126
+ __decorate([state()], PanTextElement.prototype, "_loopAtEnd", void 0);
14127
+ __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
14128
+ PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
14129
+ //#endregion
13903
14130
  //#region src/data-grid/data-grid.ts
13904
14131
  let DataGridElement = class DataGridElement extends i {
13905
14132
  constructor(..._args) {
@@ -13913,12 +14140,16 @@ let DataGridElement = class DataGridElement extends i {
13913
14140
  this.getRowKey = (r, i) => r.key ?? r.id ?? String(i);
13914
14141
  this.editable = false;
13915
14142
  this.editability = `user`;
14143
+ this.cellOverflow = `ellipsis`;
14144
+ this.zebraStripes = false;
13916
14145
  this.groupBy = null;
13917
14146
  this.allowGrouping = false;
13918
14147
  this.crossGroupSelection = false;
13919
14148
  this.columnMenu = `auto`;
13920
14149
  this.itemsDraggable = false;
13921
14150
  this._columnFormatters = /* @__PURE__ */ new Map();
14151
+ this._resizingColKey = void 0;
14152
+ this._animatingColKeys = /* @__PURE__ */ new Set();
13922
14153
  this._selectedKeys = /* @__PURE__ */ new Set();
13923
14154
  this._scrollTop = 0;
13924
14155
  this._scrollLeft = 0;
@@ -14199,6 +14430,14 @@ let DataGridElement = class DataGridElement extends i {
14199
14430
  --data-grid-header-bg: var(--surface-2);
14200
14431
  --data-grid-border: var(--border);
14201
14432
  --data-grid-header-text: var(--surface-3-text);
14433
+ --data-grid-reorder-handle-width: 10px;
14434
+ /* How strongly the alternate ("zebra") row stands out from the base row
14435
+ background when [zebra-stripes] is set - a color-mix() percentage. */
14436
+ --data-grid-zebra-intensity: 6%;
14437
+ /* Duration of the width transition when a column is auto-sized via
14438
+ autoSizeColumn(). Read back in JS (_columnResizeAnimationMs) to know
14439
+ when the animation has finished, so keep it a plain time value. */
14440
+ --data-grid-column-resize-duration: 180ms;
14202
14441
  position: relative;
14203
14442
  display: block;
14204
14443
  overflow: hidden;
@@ -14227,11 +14466,28 @@ let DataGridElement = class DataGridElement extends i {
14227
14466
  box-sizing: border-box;
14228
14467
  }
14229
14468
  .header-cell[data-tickled] { background: var(--surface-5); }
14469
+ /* Applied to a header-cell / cell while its column is animating towards
14470
+ an autoSizeColumn() target width. */
14471
+ .header-cell.width-animating, .cell.width-animating {
14472
+ transition: width var(--data-grid-column-resize-duration, 180ms) ease-out;
14473
+ }
14474
+ @media (prefers-reduced-motion: reduce) {
14475
+ .header-cell.width-animating, .cell.width-animating { transition: none; }
14476
+ }
14230
14477
  .header-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
14231
14478
  .sort-icon { font-size: 10px; opacity: 0.7; }
14232
- .resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1; }
14233
- .reorder-handle { cursor: grab; opacity: 0.5; padding: 0 2px; display: inline-flex; align-items: center; }
14234
- .reorder-handle .ixfx-handle { --ixfx-handle-spot-size: 14px; --ixfx-handle-dot-size: 3px; }
14479
+ .resize-handle {
14480
+ position: absolute; right: -4px; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1;
14481
+ display: flex; align-items: stretch; justify-content: center;
14482
+ }
14483
+ .resize-handle::before {
14484
+ content: ''; width: 2px; margin: 2px 0; border-radius: 1px; background: var(--split-gutter-color, transparent);
14485
+ transition: background-color var(--transition, 0.15s ease-out);
14486
+ }
14487
+ .resize-handle:hover::before { background: var(--split-gutter-hover-color, var(--surface-6)); }
14488
+ .resize-handle[data-active]::before { background: var(--split-gutter-active-color, var(--accent)); }
14489
+ .reorder-handle { cursor: grab; opacity: 0.5; display: inline-flex; align-items: center; }
14490
+ .reorder-handle .ixfx-handle { --ixfx-handle-length: 14px; --ixfx-handle-thickness: var(--data-grid-reorder-handle-width, 10px); --ixfx-handle-dot-size: 3px; }
14235
14491
  .flyout-trigger { flex-shrink: 0; width: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-left: var(--border-subtle); background: var(--data-grid-header-bg); }
14236
14492
  .viewport { flex: 1; overflow: auto; position: relative; }
14237
14493
  .spacer { position: relative; }
@@ -14239,14 +14495,28 @@ let DataGridElement = class DataGridElement extends i {
14239
14495
  position: absolute; left: 0; right: 0; display: flex; align-items: stretch;
14240
14496
  gap: 0;
14241
14497
  height: var(--data-grid-row-height);
14242
- border: none;
14243
- box-shadow: inset 0 0 0 1px var(--item-border-color, transparent);
14498
+ /* Border width is always 1px, even when not visually shown (transparent),
14499
+ so hover/selection never changes the box size and shifts content. */
14500
+ border: 1px solid transparent;
14244
14501
  background: var(--item-bg);
14245
14502
  border-radius: var(--item-border-radius);
14246
14503
  padding: 0;
14247
14504
  box-sizing: border-box;
14505
+ /* Only background transitions - hover ("tickled") no longer changes
14506
+ border, so there is nothing else to animate. */
14507
+ transition: background var(--transition, 0.1s ease-out);
14248
14508
  }
14249
- .row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
14509
+ /* Zebra striping: alternate rows tint towards the text colour, which
14510
+ lightens in dark themes and darkens in light themes - so it reads as
14511
+ "more contrast" either way. The zebra-odd class is only applied when
14512
+ zebraStripes is set (see render()). Declared before .tickled /
14513
+ .selected so those states (same specificity) still win when a striped
14514
+ row is also hovered or selected. */
14515
+ .row.item.zebra-odd {
14516
+ background: color-mix(in oklch, transparent calc(100% - var(--data-grid-zebra-intensity, 6%)), var(--surface-3-text) var(--data-grid-zebra-intensity, 6%));
14517
+ }
14518
+ /* Hover: background only, no border change (avoids layout shift + colour flash). */
14519
+ .row.item.tickled { background: var(--item-bg-tickled); color: var(--item-text-tickled); }
14250
14520
  .row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
14251
14521
  .row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
14252
14522
  .row.item.dragging-origin { opacity: 0.6; }
@@ -14295,6 +14565,7 @@ let DataGridElement = class DataGridElement extends i {
14295
14565
  `
14296
14566
  ];
14297
14567
  }
14568
+ #viewportResizeObserver;
14298
14569
  #nativeDragging;
14299
14570
  #dragOriginKey;
14300
14571
  #postDragSuppressTimer;
@@ -14412,6 +14683,8 @@ let DataGridElement = class DataGridElement extends i {
14412
14683
  clearTimeout(this.#postDragSuppressTimer);
14413
14684
  this.#postDragSuppressTimer = 0;
14414
14685
  }
14686
+ this.#viewportResizeObserver?.disconnect();
14687
+ this.#viewportResizeObserver = void 0;
14415
14688
  }
14416
14689
  /**
14417
14690
  * Dispatch a command to this grid — routes through the command registry so
@@ -14421,14 +14694,35 @@ let DataGridElement = class DataGridElement extends i {
14421
14694
  this.#searchRegistry?.invoke(commandId, args);
14422
14695
  }
14423
14696
  firstUpdated() {
14424
- new ResizeObserver(() => {
14425
- if (!this._viewportEl) return;
14426
- this._viewportH = this._viewportEl.clientHeight;
14427
- this._viewportW = this._viewportEl.clientWidth;
14428
- this.requestUpdate();
14429
- }).observe(this._viewportEl);
14430
- this._viewportH = this._viewportEl.clientHeight;
14431
- this._viewportW = this._viewportEl.clientWidth;
14697
+ this.#viewportResizeObserver = new ResizeObserver(() => this.#measureViewport());
14698
+ this.#viewportResizeObserver.observe(this._viewportEl);
14699
+ this.#measureViewport();
14700
+ }
14701
+ /**
14702
+ * Re-read the viewport's size and update `_viewportH`/`_viewportW` if it
14703
+ * changed. A zero-sized read (e.g. while hosted in a hidden/inactive dock
14704
+ * tab) is ignored so it never clobbers the last known-good geometry — the
14705
+ * observer fires again with the real size once the host becomes visible.
14706
+ */
14707
+ #measureViewport() {
14708
+ if (!this._viewportEl) return;
14709
+ const w = this._viewportEl.clientWidth;
14710
+ const h = this._viewportEl.clientHeight;
14711
+ if (w <= 0 || h <= 0) return;
14712
+ if (w === this._viewportW && h === this._viewportH) return;
14713
+ this._viewportW = w;
14714
+ this._viewportH = h;
14715
+ }
14716
+ /**
14717
+ * Force a fresh re-measure of the viewport and re-render. Normally
14718
+ * unnecessary — a `ResizeObserver` tracks the viewport automatically — but
14719
+ * gives a consumer an escape hatch to recover if an ancestor's
14720
+ * layout/visibility changes without the observer noticing (matches
14721
+ * `ixfx-grid-list`'s `refreshLayout()`).
14722
+ */
14723
+ refreshLayout() {
14724
+ this.#measureViewport();
14725
+ this.requestUpdate();
14432
14726
  }
14433
14727
  updated(changed) {
14434
14728
  super.updated(changed);
@@ -15091,7 +15385,13 @@ let DataGridElement = class DataGridElement extends i {
15091
15385
  composed: true
15092
15386
  }));
15093
15387
  }
15094
- _autoSizeColumn(col) {
15388
+ /**
15389
+ * Measure the width a column would need to show its header label and every
15390
+ * row's (formatted) value without truncation, clamped to `minWidth`/`maxWidth`.
15391
+ * Values are measured from their formatted string via canvas `measureText` -
15392
+ * accurate for plain text, an estimate for anything else.
15393
+ */
15394
+ _measureAutoWidth(col) {
15095
15395
  const cs = getComputedStyle(this);
15096
15396
  const font = `${cs.fontSize} ${cs.fontFamily}`;
15097
15397
  const ctx = document.createElement(`canvas`).getContext(`2d`);
@@ -15106,12 +15406,15 @@ let DataGridElement = class DataGridElement extends i {
15106
15406
  for (const r of this.rows) {
15107
15407
  const v = r[col.key];
15108
15408
  if (v === null || v === void 0 || v === ``) continue;
15109
- const w = measure(String(v)) + cellPad;
15409
+ const w = measure(String(this._formatValue(v, col, r))) + cellPad;
15110
15410
  if (w > maxW) maxW = w;
15111
15411
  }
15112
15412
  const min = col.minWidth ?? 40;
15113
15413
  const max = col.maxWidth ?? 600;
15114
- const w = Math.max(min, Math.min(max, maxW));
15414
+ return Math.max(min, Math.min(max, maxW));
15415
+ }
15416
+ _autoSizeColumn(col) {
15417
+ const w = this._measureAutoWidth(col);
15115
15418
  col.width = w;
15116
15419
  this.requestUpdate();
15117
15420
  this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
@@ -15123,11 +15426,50 @@ let DataGridElement = class DataGridElement extends i {
15123
15426
  composed: true
15124
15427
  }));
15125
15428
  }
15429
+ /**
15430
+ * Resize a single column, by key, to fit its contents (header label + every
15431
+ * row's formatted value), animating the width change smoothly. Only works
15432
+ * for plain columns - one with a custom `renderer` can't be measured
15433
+ * reliably from its raw value, so it's left untouched. Returns `false` (and
15434
+ * does nothing) if the column doesn't exist, isn't resizable, or has a
15435
+ * `renderer`.
15436
+ */
15437
+ autoSizeColumn(key) {
15438
+ const col = this.columns.find((c) => c.key === key);
15439
+ if (!col || col.resizable === false || col.renderer) return false;
15440
+ const target = this._measureAutoWidth(col);
15441
+ if ((col.width ?? 150) === target) return true;
15442
+ this._animatingColKeys = new Set(this._animatingColKeys).add(key);
15443
+ requestAnimationFrame(() => {
15444
+ col.width = target;
15445
+ this.requestUpdate();
15446
+ this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
15447
+ detail: {
15448
+ key,
15449
+ width: target
15450
+ },
15451
+ bubbles: true,
15452
+ composed: true
15453
+ }));
15454
+ window.setTimeout(() => {
15455
+ const next = new Set(this._animatingColKeys);
15456
+ next.delete(key);
15457
+ this._animatingColKeys = next;
15458
+ }, this._columnResizeAnimationMs());
15459
+ });
15460
+ return true;
15461
+ }
15462
+ _columnResizeAnimationMs() {
15463
+ const raw = getComputedStyle(this).getPropertyValue(`--data-grid-column-resize-duration`).trim();
15464
+ const ms = raw.endsWith(`ms`) ? Number.parseFloat(raw) : raw.endsWith(`s`) ? Number.parseFloat(raw) * 1e3 : Number.parseFloat(raw);
15465
+ return (Number.isFinite(ms) && ms > 0 ? ms : 180) + 30;
15466
+ }
15126
15467
  _onResizePointerDown(e, col) {
15127
15468
  e.preventDefault();
15128
15469
  e.stopPropagation();
15129
15470
  const startX = e.clientX;
15130
15471
  const startW = col.width ?? 150;
15472
+ this._resizingColKey = col.key;
15131
15473
  const onMove = (ev) => {
15132
15474
  col.width = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
15133
15475
  this.requestUpdate();
@@ -15135,6 +15477,7 @@ let DataGridElement = class DataGridElement extends i {
15135
15477
  const onUp = (ev) => {
15136
15478
  window.removeEventListener(`pointermove`, onMove);
15137
15479
  window.removeEventListener(`pointerup`, onUp);
15480
+ this._resizingColKey = void 0;
15138
15481
  const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
15139
15482
  this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
15140
15483
  detail: {
@@ -15186,20 +15529,20 @@ let DataGridElement = class DataGridElement extends i {
15186
15529
  const visibleFlat = this._flat.slice(startRow, endRow);
15187
15530
  const showCheckbox = this.interactionMode === `checked`;
15188
15531
  return b`
15189
- <div class="grid" style="height:400px">
15532
+ <div class="grid">
15190
15533
  <div class="header-bar">
15191
15534
  <div class="header-scroll">
15192
15535
  ${showCheckbox ? b`<div class="header-cell checkbox-col"></div>` : A}
15193
15536
  ${repeat(this._visibleColumns, (c) => c.key, (col, idx) => b`
15194
- <div class="header-cell" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
15537
+ <div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
15195
15538
  @click=${() => this._onHeaderClick(col)}
15196
15539
  @pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
15197
15540
  ${col.reorderable !== false ? b`<span class="reorder-handle" draggable="false"
15198
15541
  @pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
15199
- ><span class="ixfx-handle" data-handle="dotted" data-shape="spot" part="reorder-handle"></span></span>` : A}
15542
+ ><span class="ixfx-handle" data-handle="dotted" data-shape="vertical" part="reorder-handle"></span></span>` : A}
15200
15543
  <span class="header-label">${col.label}</span>
15201
15544
  ${this.sortState?.key === col.key ? b`<span class="sort-icon">${this.sortState.direction === `asc` ? `▲` : `▼`}</span>` : A}
15202
- ${col.resizable !== false ? b`<span class="resize-handle" @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
15545
+ ${col.resizable !== false ? b`<span class="resize-handle" ?data-active=${this._resizingColKey === col.key} @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
15203
15546
  e.preventDefault();
15204
15547
  e.stopPropagation();
15205
15548
  this._autoSizeColumn(col);
@@ -15270,7 +15613,7 @@ let DataGridElement = class DataGridElement extends i {
15270
15613
  const selected = this._selectedKeys.has(rowKey);
15271
15614
  const tickled = this._tickled.isTickled(rowKey);
15272
15615
  return b`
15273
- <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
15616
+ <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.zebraStripes && absIdx2 % 2 === 1 ? `zebra-odd` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
15274
15617
  data-key=${rowKey}
15275
15618
  draggable=${this.#nativeDrag.draggableAttr}
15276
15619
  @click=${(e) => {
@@ -15376,7 +15719,9 @@ let DataGridElement = class DataGridElement extends i {
15376
15719
  const rendered = col.renderer ? col.renderer(row, col, absIdx2) : displayVal;
15377
15720
  const cellContent = col.renderer ? rendered : displayVal;
15378
15721
  const textEditable = this._isEditable(row, col) && this._isTextEditor(col);
15379
- return b`<div class="cell ${textEditable ? `editable-text` : ``}" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}"
15722
+ const indent = col.align !== `right` && col.align !== `center` && col.reorderable !== false ? `padding-left:calc(var(--space-s) + var(--data-grid-reorder-handle-width, 10px) + var(--space-xs));` : ``;
15723
+ const canPan = this.cellOverflow === `pan-text` && !col.renderer && (selected || tickled);
15724
+ return b`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent}"
15380
15725
  @click=${(e) => {
15381
15726
  this._lastClickedCell = {
15382
15727
  rowKey,
@@ -15398,7 +15743,7 @@ let DataGridElement = class DataGridElement extends i {
15398
15743
  composed: true
15399
15744
  }));
15400
15745
  }}
15401
- >${cellContent}</div>`;
15746
+ >${canPan ? b`<ixfx-pan-text mode="loop">${cellContent}</ixfx-pan-text>` : cellContent}</div>`;
15402
15747
  })}
15403
15748
  </div>
15404
15749
  `;
@@ -15432,6 +15777,15 @@ __decorate([property({
15432
15777
  type: String,
15433
15778
  reflect: true
15434
15779
  })], DataGridElement.prototype, "editability", void 0);
15780
+ __decorate([property({
15781
+ type: String,
15782
+ attribute: `cell-overflow`
15783
+ })], DataGridElement.prototype, "cellOverflow", void 0);
15784
+ __decorate([property({
15785
+ type: Boolean,
15786
+ attribute: `zebra-stripes`,
15787
+ reflect: true
15788
+ })], DataGridElement.prototype, "zebraStripes", void 0);
15435
15789
  __decorate([property({ attribute: false })], DataGridElement.prototype, "groupBy", void 0);
15436
15790
  __decorate([property({
15437
15791
  type: Boolean,
@@ -15462,6 +15816,8 @@ __decorate([property({
15462
15816
  reflect: true
15463
15817
  })], DataGridElement.prototype, "itemsDraggable", void 0);
15464
15818
  __decorate([property({ attribute: false })], DataGridElement.prototype, "dragPreviewRenderer", void 0);
15819
+ __decorate([state()], DataGridElement.prototype, "_resizingColKey", void 0);
15820
+ __decorate([state()], DataGridElement.prototype, "_animatingColKeys", void 0);
15465
15821
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
15466
15822
  __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
15467
15823
  __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
@@ -22672,233 +23028,6 @@ var DataGridListSource = class {
22672
23028
  }
22673
23029
  };
22674
23030
  //#endregion
22675
- //#region src/pan-text/pan-text.ts
22676
- const SPEED = 45;
22677
- const END_PADDING = 4;
22678
- const START_DELAY = 300;
22679
- const END_DWELL = 2e3;
22680
- const RESET_DURATION = 400;
22681
- const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
22682
- let PanTextElement = class PanTextElement extends i {
22683
- constructor(..._args) {
22684
- super(..._args);
22685
- this.direction = `end`;
22686
- this.disabled = false;
22687
- this.mode = `hover`;
22688
- this._isOverflowing = false;
22689
- this._isHovering = false;
22690
- this._loopActive = false;
22691
- this._loopAtEnd = false;
22692
- this._overflow = 0;
22693
- this._resizeObserver = null;
22694
- this._loopTimer = 0;
22695
- this._onMouseEnter = () => {
22696
- this._isHovering = true;
22697
- };
22698
- this._onMouseLeave = () => {
22699
- this._isHovering = false;
22700
- };
22701
- this._measure = () => {
22702
- if (this._isHovering || this._loopAtEnd) return;
22703
- const el = this._textEl;
22704
- if (!el) return;
22705
- this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
22706
- const overflowing = this._overflow > 0;
22707
- this.classList.toggle(`is-overflowing`, overflowing);
22708
- if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
22709
- if (this._loopActive && this.mode === `loop` && !this.disabled && overflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
22710
- if (!overflowing && this._loopAtEnd) {
22711
- this._loopAtEnd = false;
22712
- this.requestUpdate();
22713
- }
22714
- };
22715
- }
22716
- connectedCallback() {
22717
- super.connectedCallback();
22718
- this._resizeObserver = new ResizeObserver(() => this._measure());
22719
- this.addEventListener(`mouseenter`, this._onMouseEnter);
22720
- this.addEventListener(`mouseleave`, this._onMouseLeave);
22721
- }
22722
- disconnectedCallback() {
22723
- super.disconnectedCallback();
22724
- this._clearLoopTimer();
22725
- this._loopActive = false;
22726
- this._loopAtEnd = false;
22727
- this._resizeObserver?.disconnect();
22728
- this._resizeObserver = null;
22729
- this.removeEventListener(`mouseenter`, this._onMouseEnter);
22730
- this.removeEventListener(`mouseleave`, this._onMouseLeave);
22731
- }
22732
- firstUpdated() {
22733
- this._measure();
22734
- this._resizeObserver?.observe(this);
22735
- document.fonts?.ready.then(() => this._measure());
22736
- if (this.mode === `loop`) this.startLoop();
22737
- }
22738
- updated(changed) {
22739
- if (changed.has(`mode`)) {
22740
- if (this.mode === `loop`) this.startLoop();
22741
- else this.stopLoop();
22742
- }
22743
- if (changed.has(`disabled`) && this.disabled) this.stopLoop();
22744
- this._measure();
22745
- }
22746
- /**
22747
- * Whether a loop pan is currently active (between `startLoop()` and `stopLoop()`).
22748
- */
22749
- get looping() {
22750
- return this._loopActive;
22751
- }
22752
- /**
22753
- * Begin looping: pan to the end, dwell there for 2 seconds, reset to the
22754
- * start and pan again, repeating until `stopLoop()` is called. A no-op when
22755
- * `disabled`, when there is nothing to reveal, or under reduced-motion.
22756
- */
22757
- startLoop() {
22758
- if (this.disabled || this._prefersReducedMotion()) return;
22759
- this._loopActive = true;
22760
- this._loopAtEnd = false;
22761
- this.requestUpdate();
22762
- if (this._isOverflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
22763
- }
22764
- /**
22765
- * Stop a loop started with `startLoop()` and snap back to the rest position.
22766
- */
22767
- stopLoop() {
22768
- if (!this._loopActive && !this._loopAtEnd && this._loopTimer === 0) return;
22769
- this._clearLoopTimer();
22770
- this._loopActive = false;
22771
- this._loopAtEnd = false;
22772
- this.requestUpdate();
22773
- }
22774
- _prefersReducedMotion() {
22775
- return globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches ?? false;
22776
- }
22777
- _panDuration() {
22778
- return Math.max(1.5, (this._overflow + END_PADDING) / SPEED);
22779
- }
22780
- _clearLoopTimer() {
22781
- if (this._loopTimer !== 0) {
22782
- clearTimeout(this._loopTimer);
22783
- this._loopTimer = 0;
22784
- }
22785
- }
22786
- _scheduleLoopPan(delay) {
22787
- this._clearLoopTimer();
22788
- this._loopTimer = window.setTimeout(() => {
22789
- this._loopTimer = 0;
22790
- if (!this._loopActive || this.disabled || !this._isOverflowing) return;
22791
- this._loopAtEnd = true;
22792
- this.requestUpdate();
22793
- const dwell = this._panDuration() * 1e3 + END_DWELL;
22794
- this._loopTimer = window.setTimeout(() => {
22795
- this._loopTimer = 0;
22796
- if (!this._loopActive) return;
22797
- this._loopAtEnd = false;
22798
- this.requestUpdate();
22799
- this._scheduleLoopPan(700);
22800
- }, dwell);
22801
- }, delay);
22802
- }
22803
- _textStyle() {
22804
- if (!this._isOverflowing) return ``;
22805
- const distance = this._overflow + END_PADDING;
22806
- const duration = Math.max(1.5, distance / SPEED);
22807
- const revealEnd = this.direction === `end`;
22808
- const restX = revealEnd ? 0 : -distance;
22809
- const hoverX = revealEnd ? -distance : 0;
22810
- if (this._loopActive && this.mode === `loop`) {
22811
- if (this._loopAtEnd) return [
22812
- `transform: translateX(${hoverX}px)`,
22813
- `transition: transform ${duration}s ${PAN_EASING}`,
22814
- `overflow: visible`,
22815
- `text-overflow: clip`
22816
- ].join(`; `);
22817
- return [
22818
- `transform: translateX(${restX}px)`,
22819
- `transition: transform ${RESET_DURATION}ms ${PAN_EASING}`,
22820
- revealEnd ? `` : `overflow: visible; text-overflow: clip`
22821
- ].filter(Boolean).join(`; `);
22822
- }
22823
- if (this._isHovering) return [
22824
- `transform: translateX(${hoverX}px)`,
22825
- `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
22826
- `overflow: visible`,
22827
- `text-overflow: clip`
22828
- ].join(`; `);
22829
- return [
22830
- `transform: translateX(${restX}px)`,
22831
- `transition: transform 400ms ${PAN_EASING}`,
22832
- revealEnd ? `` : `overflow: visible; text-overflow: clip`
22833
- ].filter(Boolean).join(`; `);
22834
- }
22835
- render() {
22836
- const textClasses = {
22837
- "is-overflowing": this._isOverflowing,
22838
- "is-hovering": this._isHovering
22839
- };
22840
- return b`
22841
- <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
22842
- <slot @slotchange=${this._measure}></slot>
22843
- </span>
22844
- `;
22845
- }
22846
- static {
22847
- this.styles = [themeFallbacks, i$3`
22848
- /* Renders like plain inline text: no colours, border, padding or margin
22849
- of its own — it only adds the clipping needed to pan on hover. */
22850
- :host {
22851
- display: inline-block;
22852
- overflow: hidden;
22853
- white-space: nowrap;
22854
- max-width: 100%;
22855
- box-sizing: border-box;
22856
- font: inherit;
22857
- color: inherit;
22858
- }
22859
-
22860
- :host([disabled]) {
22861
- opacity: var(--disabled-opacity, 0.5);
22862
- pointer-events: none;
22863
- }
22864
-
22865
- .text {
22866
- display: block;
22867
- overflow: hidden;
22868
- text-overflow: ellipsis;
22869
- white-space: nowrap;
22870
- will-change: transform;
22871
- }
22872
-
22873
- @media (prefers-reduced-motion: reduce) {
22874
- .text {
22875
- transition: none !important;
22876
- transform: none !important;
22877
- }
22878
- }
22879
- `];
22880
- }
22881
- };
22882
- __decorate([property({
22883
- type: String,
22884
- attribute: `direction`
22885
- })], PanTextElement.prototype, "direction", void 0);
22886
- __decorate([property({
22887
- type: Boolean,
22888
- attribute: `disabled`
22889
- })], PanTextElement.prototype, "disabled", void 0);
22890
- __decorate([property({
22891
- type: String,
22892
- attribute: `mode`,
22893
- reflect: true
22894
- })], PanTextElement.prototype, "mode", void 0);
22895
- __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
22896
- __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
22897
- __decorate([state()], PanTextElement.prototype, "_loopActive", void 0);
22898
- __decorate([state()], PanTextElement.prototype, "_loopAtEnd", void 0);
22899
- __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
22900
- PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
22901
- //#endregion
22902
23031
  //#region src/grid-list/grid-list.ts
22903
23032
  const ITEM_SELECTOR$1 = `:scope > *`;
22904
23033
  const MIN_ITEM_SIZE = 16;