@ixfx/components 0.9.7 → 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 +92 -64
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +349 -244
  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-8N0Kd5-6.d.ts → data-grid-BUeZjnvj.d.ts} +30 -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 +123 -17
  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-8N0Kd5-6.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; }
@@ -15115,7 +15385,13 @@ let DataGridElement = class DataGridElement extends i {
15115
15385
  composed: true
15116
15386
  }));
15117
15387
  }
15118
- _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) {
15119
15395
  const cs = getComputedStyle(this);
15120
15396
  const font = `${cs.fontSize} ${cs.fontFamily}`;
15121
15397
  const ctx = document.createElement(`canvas`).getContext(`2d`);
@@ -15130,12 +15406,15 @@ let DataGridElement = class DataGridElement extends i {
15130
15406
  for (const r of this.rows) {
15131
15407
  const v = r[col.key];
15132
15408
  if (v === null || v === void 0 || v === ``) continue;
15133
- const w = measure(String(v)) + cellPad;
15409
+ const w = measure(String(this._formatValue(v, col, r))) + cellPad;
15134
15410
  if (w > maxW) maxW = w;
15135
15411
  }
15136
15412
  const min = col.minWidth ?? 40;
15137
15413
  const max = col.maxWidth ?? 600;
15138
- 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);
15139
15418
  col.width = w;
15140
15419
  this.requestUpdate();
15141
15420
  this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
@@ -15147,11 +15426,50 @@ let DataGridElement = class DataGridElement extends i {
15147
15426
  composed: true
15148
15427
  }));
15149
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
+ }
15150
15467
  _onResizePointerDown(e, col) {
15151
15468
  e.preventDefault();
15152
15469
  e.stopPropagation();
15153
15470
  const startX = e.clientX;
15154
15471
  const startW = col.width ?? 150;
15472
+ this._resizingColKey = col.key;
15155
15473
  const onMove = (ev) => {
15156
15474
  col.width = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
15157
15475
  this.requestUpdate();
@@ -15159,6 +15477,7 @@ let DataGridElement = class DataGridElement extends i {
15159
15477
  const onUp = (ev) => {
15160
15478
  window.removeEventListener(`pointermove`, onMove);
15161
15479
  window.removeEventListener(`pointerup`, onUp);
15480
+ this._resizingColKey = void 0;
15162
15481
  const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
15163
15482
  this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
15164
15483
  detail: {
@@ -15215,15 +15534,15 @@ let DataGridElement = class DataGridElement extends i {
15215
15534
  <div class="header-scroll">
15216
15535
  ${showCheckbox ? b`<div class="header-cell checkbox-col"></div>` : A}
15217
15536
  ${repeat(this._visibleColumns, (c) => c.key, (col, idx) => b`
15218
- <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}
15219
15538
  @click=${() => this._onHeaderClick(col)}
15220
15539
  @pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
15221
15540
  ${col.reorderable !== false ? b`<span class="reorder-handle" draggable="false"
15222
15541
  @pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
15223
- ><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}
15224
15543
  <span class="header-label">${col.label}</span>
15225
15544
  ${this.sortState?.key === col.key ? b`<span class="sort-icon">${this.sortState.direction === `asc` ? `▲` : `▼`}</span>` : A}
15226
- ${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) => {
15227
15546
  e.preventDefault();
15228
15547
  e.stopPropagation();
15229
15548
  this._autoSizeColumn(col);
@@ -15294,7 +15613,7 @@ let DataGridElement = class DataGridElement extends i {
15294
15613
  const selected = this._selectedKeys.has(rowKey);
15295
15614
  const tickled = this._tickled.isTickled(rowKey);
15296
15615
  return b`
15297
- <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"
15298
15617
  data-key=${rowKey}
15299
15618
  draggable=${this.#nativeDrag.draggableAttr}
15300
15619
  @click=${(e) => {
@@ -15400,7 +15719,9 @@ let DataGridElement = class DataGridElement extends i {
15400
15719
  const rendered = col.renderer ? col.renderer(row, col, absIdx2) : displayVal;
15401
15720
  const cellContent = col.renderer ? rendered : displayVal;
15402
15721
  const textEditable = this._isEditable(row, col) && this._isTextEditor(col);
15403
- 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}"
15404
15725
  @click=${(e) => {
15405
15726
  this._lastClickedCell = {
15406
15727
  rowKey,
@@ -15422,7 +15743,7 @@ let DataGridElement = class DataGridElement extends i {
15422
15743
  composed: true
15423
15744
  }));
15424
15745
  }}
15425
- >${cellContent}</div>`;
15746
+ >${canPan ? b`<ixfx-pan-text mode="loop">${cellContent}</ixfx-pan-text>` : cellContent}</div>`;
15426
15747
  })}
15427
15748
  </div>
15428
15749
  `;
@@ -15456,6 +15777,15 @@ __decorate([property({
15456
15777
  type: String,
15457
15778
  reflect: true
15458
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);
15459
15789
  __decorate([property({ attribute: false })], DataGridElement.prototype, "groupBy", void 0);
15460
15790
  __decorate([property({
15461
15791
  type: Boolean,
@@ -15486,6 +15816,8 @@ __decorate([property({
15486
15816
  reflect: true
15487
15817
  })], DataGridElement.prototype, "itemsDraggable", void 0);
15488
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);
15489
15821
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
15490
15822
  __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
15491
15823
  __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
@@ -22696,233 +23028,6 @@ var DataGridListSource = class {
22696
23028
  }
22697
23029
  };
22698
23030
  //#endregion
22699
- //#region src/pan-text/pan-text.ts
22700
- const SPEED = 45;
22701
- const END_PADDING = 4;
22702
- const START_DELAY = 300;
22703
- const END_DWELL = 2e3;
22704
- const RESET_DURATION = 400;
22705
- const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
22706
- let PanTextElement = class PanTextElement extends i {
22707
- constructor(..._args) {
22708
- super(..._args);
22709
- this.direction = `end`;
22710
- this.disabled = false;
22711
- this.mode = `hover`;
22712
- this._isOverflowing = false;
22713
- this._isHovering = false;
22714
- this._loopActive = false;
22715
- this._loopAtEnd = false;
22716
- this._overflow = 0;
22717
- this._resizeObserver = null;
22718
- this._loopTimer = 0;
22719
- this._onMouseEnter = () => {
22720
- this._isHovering = true;
22721
- };
22722
- this._onMouseLeave = () => {
22723
- this._isHovering = false;
22724
- };
22725
- this._measure = () => {
22726
- if (this._isHovering || this._loopAtEnd) return;
22727
- const el = this._textEl;
22728
- if (!el) return;
22729
- this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
22730
- const overflowing = this._overflow > 0;
22731
- this.classList.toggle(`is-overflowing`, overflowing);
22732
- if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
22733
- if (this._loopActive && this.mode === `loop` && !this.disabled && overflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
22734
- if (!overflowing && this._loopAtEnd) {
22735
- this._loopAtEnd = false;
22736
- this.requestUpdate();
22737
- }
22738
- };
22739
- }
22740
- connectedCallback() {
22741
- super.connectedCallback();
22742
- this._resizeObserver = new ResizeObserver(() => this._measure());
22743
- this.addEventListener(`mouseenter`, this._onMouseEnter);
22744
- this.addEventListener(`mouseleave`, this._onMouseLeave);
22745
- }
22746
- disconnectedCallback() {
22747
- super.disconnectedCallback();
22748
- this._clearLoopTimer();
22749
- this._loopActive = false;
22750
- this._loopAtEnd = false;
22751
- this._resizeObserver?.disconnect();
22752
- this._resizeObserver = null;
22753
- this.removeEventListener(`mouseenter`, this._onMouseEnter);
22754
- this.removeEventListener(`mouseleave`, this._onMouseLeave);
22755
- }
22756
- firstUpdated() {
22757
- this._measure();
22758
- this._resizeObserver?.observe(this);
22759
- document.fonts?.ready.then(() => this._measure());
22760
- if (this.mode === `loop`) this.startLoop();
22761
- }
22762
- updated(changed) {
22763
- if (changed.has(`mode`)) {
22764
- if (this.mode === `loop`) this.startLoop();
22765
- else this.stopLoop();
22766
- }
22767
- if (changed.has(`disabled`) && this.disabled) this.stopLoop();
22768
- this._measure();
22769
- }
22770
- /**
22771
- * Whether a loop pan is currently active (between `startLoop()` and `stopLoop()`).
22772
- */
22773
- get looping() {
22774
- return this._loopActive;
22775
- }
22776
- /**
22777
- * Begin looping: pan to the end, dwell there for 2 seconds, reset to the
22778
- * start and pan again, repeating until `stopLoop()` is called. A no-op when
22779
- * `disabled`, when there is nothing to reveal, or under reduced-motion.
22780
- */
22781
- startLoop() {
22782
- if (this.disabled || this._prefersReducedMotion()) return;
22783
- this._loopActive = true;
22784
- this._loopAtEnd = false;
22785
- this.requestUpdate();
22786
- if (this._isOverflowing && this._loopTimer === 0) this._scheduleLoopPan(START_DELAY);
22787
- }
22788
- /**
22789
- * Stop a loop started with `startLoop()` and snap back to the rest position.
22790
- */
22791
- stopLoop() {
22792
- if (!this._loopActive && !this._loopAtEnd && this._loopTimer === 0) return;
22793
- this._clearLoopTimer();
22794
- this._loopActive = false;
22795
- this._loopAtEnd = false;
22796
- this.requestUpdate();
22797
- }
22798
- _prefersReducedMotion() {
22799
- return globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches ?? false;
22800
- }
22801
- _panDuration() {
22802
- return Math.max(1.5, (this._overflow + END_PADDING) / SPEED);
22803
- }
22804
- _clearLoopTimer() {
22805
- if (this._loopTimer !== 0) {
22806
- clearTimeout(this._loopTimer);
22807
- this._loopTimer = 0;
22808
- }
22809
- }
22810
- _scheduleLoopPan(delay) {
22811
- this._clearLoopTimer();
22812
- this._loopTimer = window.setTimeout(() => {
22813
- this._loopTimer = 0;
22814
- if (!this._loopActive || this.disabled || !this._isOverflowing) return;
22815
- this._loopAtEnd = true;
22816
- this.requestUpdate();
22817
- const dwell = this._panDuration() * 1e3 + END_DWELL;
22818
- this._loopTimer = window.setTimeout(() => {
22819
- this._loopTimer = 0;
22820
- if (!this._loopActive) return;
22821
- this._loopAtEnd = false;
22822
- this.requestUpdate();
22823
- this._scheduleLoopPan(700);
22824
- }, dwell);
22825
- }, delay);
22826
- }
22827
- _textStyle() {
22828
- if (!this._isOverflowing) return ``;
22829
- const distance = this._overflow + END_PADDING;
22830
- const duration = Math.max(1.5, distance / SPEED);
22831
- const revealEnd = this.direction === `end`;
22832
- const restX = revealEnd ? 0 : -distance;
22833
- const hoverX = revealEnd ? -distance : 0;
22834
- if (this._loopActive && this.mode === `loop`) {
22835
- if (this._loopAtEnd) return [
22836
- `transform: translateX(${hoverX}px)`,
22837
- `transition: transform ${duration}s ${PAN_EASING}`,
22838
- `overflow: visible`,
22839
- `text-overflow: clip`
22840
- ].join(`; `);
22841
- return [
22842
- `transform: translateX(${restX}px)`,
22843
- `transition: transform ${RESET_DURATION}ms ${PAN_EASING}`,
22844
- revealEnd ? `` : `overflow: visible; text-overflow: clip`
22845
- ].filter(Boolean).join(`; `);
22846
- }
22847
- if (this._isHovering) return [
22848
- `transform: translateX(${hoverX}px)`,
22849
- `transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
22850
- `overflow: visible`,
22851
- `text-overflow: clip`
22852
- ].join(`; `);
22853
- return [
22854
- `transform: translateX(${restX}px)`,
22855
- `transition: transform 400ms ${PAN_EASING}`,
22856
- revealEnd ? `` : `overflow: visible; text-overflow: clip`
22857
- ].filter(Boolean).join(`; `);
22858
- }
22859
- render() {
22860
- const textClasses = {
22861
- "is-overflowing": this._isOverflowing,
22862
- "is-hovering": this._isHovering
22863
- };
22864
- return b`
22865
- <span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
22866
- <slot @slotchange=${this._measure}></slot>
22867
- </span>
22868
- `;
22869
- }
22870
- static {
22871
- this.styles = [themeFallbacks, i$3`
22872
- /* Renders like plain inline text: no colours, border, padding or margin
22873
- of its own — it only adds the clipping needed to pan on hover. */
22874
- :host {
22875
- display: inline-block;
22876
- overflow: hidden;
22877
- white-space: nowrap;
22878
- max-width: 100%;
22879
- box-sizing: border-box;
22880
- font: inherit;
22881
- color: inherit;
22882
- }
22883
-
22884
- :host([disabled]) {
22885
- opacity: var(--disabled-opacity, 0.5);
22886
- pointer-events: none;
22887
- }
22888
-
22889
- .text {
22890
- display: block;
22891
- overflow: hidden;
22892
- text-overflow: ellipsis;
22893
- white-space: nowrap;
22894
- will-change: transform;
22895
- }
22896
-
22897
- @media (prefers-reduced-motion: reduce) {
22898
- .text {
22899
- transition: none !important;
22900
- transform: none !important;
22901
- }
22902
- }
22903
- `];
22904
- }
22905
- };
22906
- __decorate([property({
22907
- type: String,
22908
- attribute: `direction`
22909
- })], PanTextElement.prototype, "direction", void 0);
22910
- __decorate([property({
22911
- type: Boolean,
22912
- attribute: `disabled`
22913
- })], PanTextElement.prototype, "disabled", void 0);
22914
- __decorate([property({
22915
- type: String,
22916
- attribute: `mode`,
22917
- reflect: true
22918
- })], PanTextElement.prototype, "mode", void 0);
22919
- __decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
22920
- __decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
22921
- __decorate([state()], PanTextElement.prototype, "_loopActive", void 0);
22922
- __decorate([state()], PanTextElement.prototype, "_loopAtEnd", void 0);
22923
- __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
22924
- PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
22925
- //#endregion
22926
23031
  //#region src/grid-list/grid-list.ts
22927
23032
  const ITEM_SELECTOR$1 = `:scope > *`;
22928
23033
  const MIN_ITEM_SIZE = 16;