@ixfx/components 0.13.3 → 0.13.4
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/bundle/index.d.ts +1449 -11
- package/bundle/index.js +1529 -45918
- package/bundle/rolldown-runtime-CEoa-xHZ.js +1 -0
- package/dist/ac-text.d.ts +2 -3
- package/dist/ac-text.js +0 -2
- package/dist/{ac-token-TCLAZeVg.d.ts → ac-token--_vCHXWw.d.ts} +2 -3
- package/dist/ac-token.d.ts +1 -1
- package/dist/ac-token.js +0 -2
- package/dist/button-B7mgqtxR.js +0 -2
- package/dist/{button-CTL45aBl.d.ts → button-BYuf72aR.d.ts} +2 -3
- package/dist/button.d.ts +1 -1
- package/dist/checkbox.d.ts +1 -2
- package/dist/checkbox.js +0 -2
- package/dist/checked-select-CglRukbP.d.ts +1 -2
- package/dist/checked-select-lDgAQQ7v.js +0 -2
- package/dist/colour-picker-BCMU7W7R.js +0 -2
- package/dist/colour-picker.d.ts +1 -1
- package/dist/crumbs.d.ts +2 -3
- package/dist/crumbs.js +1 -3
- package/dist/data-controller-DOlmqNKb.js +0 -2
- package/dist/data-display.d.ts +1 -2
- package/dist/data-display.js +0 -2
- package/dist/{data-grid-DHWJN66w.d.ts → data-grid-BtBZt8M1.d.ts} +5 -6
- package/dist/data-grid-FHr7wZ_k.js +2108 -0
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +1 -2013
- package/dist/data-provider-35TyWclX.js +0 -2
- package/dist/data-provider-BAn_-oR1.d.ts +1 -2
- package/dist/decorate-VRK8rslU.js +0 -2
- package/dist/defaults-B4ePs4LB.js +0 -2
- package/dist/{detail-list-C96B5Txz.js → detail-list-BfYHNh6B.js} +1 -3
- package/dist/{detail-list-dX7jLO6b.d.ts → detail-list-u2rWnAXQ.d.ts} +4 -5
- package/dist/detail-list.d.ts +2 -2
- package/dist/detail-list.js +1 -1
- package/dist/drag-ghost-DtSDvjri.js +0 -2
- package/dist/editable-label-rzXkjf1H.d.ts +1 -2
- package/dist/editable-label.js +0 -2
- package/dist/fallbacks-DQ_5OsmR.js +0 -2
- package/dist/{flat-list-Y98IqH00.d.ts → flat-list-DNnTQ65v.d.ts} +3 -4
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +0 -2
- package/dist/form-CsXJX15p.js +0 -2
- package/dist/form.d.ts +1 -2
- package/dist/{grid-list-CoT9e1jB.d.ts → grid-list-BLnV9oZE.d.ts} +2 -3
- package/dist/{grid-list-DeqAd9-h.js → grid-list-CkU8gHCh.js} +1 -3
- package/dist/grid-list.d.ts +1 -1
- package/dist/grid-list.js +1 -1
- package/dist/{grouped-item-lister-Bhyo-dkH.d.ts → grouped-item-lister-DETte0Dh.d.ts} +2 -3
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/grouped-item-lister.js +0 -2
- package/dist/grouping-CUKgmkMm.d.ts +1 -2
- package/dist/grouping-RYPnHkrS.js +0 -2
- package/dist/handle-DAXs7kbJ.js +0 -2
- package/dist/hex-editor-Caq8vNQk.d.ts +1 -2
- package/dist/hex-editor-DiYpgIKs.js +0 -2
- package/dist/highlight-B-eQrhhb.js +0 -2
- package/dist/icon-1gpwtPx8.js +0 -2
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +2 -3
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +0 -2
- package/dist/incr-search-JIhwB49y.js +0 -2
- package/dist/incr-search.d.ts +1 -1
- package/dist/{index-Dq30pOnn.d.ts → index-B-hzcIP0.d.ts} +3 -4
- package/dist/{index-Dlah7Zfi.d.ts → index-BYIRgv49.d.ts} +2 -3
- package/dist/{index-CnQPCywE.d.ts → index-CCC0G1nO.d.ts} +2 -3
- package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +2 -3
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +2 -3
- package/dist/index-DV3iaUBW.d.ts +1 -2
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +2 -3
- package/dist/index-o-CWDt11.d.ts +1 -2
- package/dist/{index-BOUaDIW-.d.ts → index-tq6f8007.d.ts} +2 -3
- package/dist/index.d.ts +21 -22
- package/dist/index.js +4 -6
- package/dist/item-appearance-BjcMyauw.js +0 -2
- package/dist/keyboard-B5xD7Fz-.js +0 -2
- package/dist/labelled-input-base-BFa4xlYV.js +0 -2
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +2 -3
- package/dist/labelled-radial-input.d.ts +3 -4
- package/dist/labelled-radial-input.js +0 -2
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-range-input.js +0 -2
- package/dist/labelled-slider-input-FdCRi6st.js +0 -2
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/led.d.ts +1 -2
- package/dist/led.js +0 -2
- package/dist/list-group-header.d.ts +2 -3
- package/dist/list-group-header.js +0 -2
- package/dist/list-item-animation-BUoEKi5s.js +0 -2
- package/dist/list-native-drag-controller-B9xO0XWn.js +0 -2
- package/dist/list-native-drag-controller-ChIi3eK5.d.ts +1 -2
- package/dist/list-selection-controller-BeE70lbB.js +0 -2
- package/dist/list-selection-types-PjKdhF-a.d.ts +1 -2
- package/dist/list-viewport-types-BRZsLcvp.d.ts +1 -2
- package/dist/menu-CwcXXeLM.js +0 -2
- package/dist/menu-item-BDEekBVy.js +0 -2
- package/dist/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +2 -3
- package/dist/menu.d.ts +2 -2
- package/dist/miller.d.ts +2 -3
- package/dist/miller.js +0 -2
- package/dist/narrowed-text.js +0 -2
- package/dist/notification-pill-DyZydXoZ.d.ts +1 -2
- package/dist/notifier.js +0 -2
- package/dist/pan-text-CoYmt2qd.d.ts +1 -2
- package/dist/pan-text-CqtvjPyK.js +0 -2
- package/dist/panel.d.ts +2 -3
- package/dist/panel.js +0 -2
- package/dist/polar-pad.d.ts +1 -2
- package/dist/polar-pad.js +0 -2
- package/dist/popup-D80sj2QC.js +0 -2
- package/dist/prominence-DlFShic3.js +0 -2
- package/dist/radial-input-DU6t2hYO.d.ts +1 -2
- package/dist/radial-input-KMVltQfe.js +0 -2
- package/dist/range-input.d.ts +1 -2
- package/dist/range-input.js +0 -2
- package/dist/range.d.ts +1 -2
- package/dist/range.js +0 -2
- package/dist/registry-BEtb8_wB.js +0 -2
- package/dist/{scroll-fade-BrNUIYnm.js → scroll-fade-Xx28D7mH.js} +11 -7
- package/dist/select-horiz.d.ts +1 -2
- package/dist/select-horiz.js +1 -3
- package/dist/slider-input-BxURAAmD.d.ts +1 -2
- package/dist/slider-input-ByHdotzp.js +0 -2
- package/dist/snackbar.d.ts +1 -2
- package/dist/snackbar.js +0 -2
- package/dist/snap-container.d.ts +1 -2
- package/dist/snap-container.js +0 -2
- package/dist/split-layout-Deoxy47d.js +0 -2
- package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-JK3xWVX2.d.ts} +2 -3
- package/dist/split-layout.d.ts +1 -1
- package/dist/swipe.d.ts +1 -2
- package/dist/swipe.js +0 -2
- package/dist/{tab-list-D_dmImJM.d.ts → tab-list-CexFFg6O.d.ts} +2 -3
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +1 -3
- package/dist/tickled-controller-D-HZymaZ.js +0 -2
- package/dist/tickled-controller-DodxD3eQ.d.ts +1 -2
- package/dist/tickled-styles-BrAHEj1q.js +0 -2
- package/dist/timeline-BP1Ux2EV.js +0 -2
- package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +2 -3
- package/dist/titlebar.d.ts +1 -1
- package/dist/titlebar.js +0 -2
- package/dist/tooltip-BlvBUQxx.d.ts +1 -2
- package/dist/tooltip-CJt3WLmN.js +0 -2
- package/dist/transitory-label.d.ts +1 -2
- package/dist/transitory-label.js +0 -2
- package/dist/tree-DD8OaqsK.js +0 -2
- package/dist/tree-component-Cj8V3uar.d.ts +1 -2
- package/dist/tree.d.ts +1 -1
- package/dist/types-DQ7KOUIj.d.ts +1 -2
- package/dist/{vertical-list-lj_19DCx.d.ts → vertical-list-m7GJtXwv.d.ts} +2 -3
- package/dist/vertical-list.d.ts +1 -1
- package/dist/vertical-list.js +1 -3
- package/dist/wheel-nudge-By5s_ioH.d.ts +1 -2
- package/dist/wheel-nudge-D5rmht3v.js +0 -2
- package/dist/xy-axis-CyMd_Bn1.js +0 -2
- package/dist/xy-axis-DQqd2yXJ.d.ts +1 -2
- package/dist/xy-pad.d.ts +1 -2
- package/dist/xy-pad.js +0 -2
- package/docs-user/index.json +2 -2
- package/package.json +1 -1
- package/bundle/index.d.ts.map +0 -1
- package/bundle/index.js.map +0 -1
- package/bundle/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/ac-text.d.ts.map +0 -1
- package/dist/ac-text.js.map +0 -1
- package/dist/ac-token-TCLAZeVg.d.ts.map +0 -1
- package/dist/ac-token.js.map +0 -1
- package/dist/button-B7mgqtxR.js.map +0 -1
- package/dist/button-CTL45aBl.d.ts.map +0 -1
- package/dist/checkbox.d.ts.map +0 -1
- package/dist/checkbox.js.map +0 -1
- package/dist/checked-select-CglRukbP.d.ts.map +0 -1
- package/dist/checked-select-lDgAQQ7v.js.map +0 -1
- package/dist/colour-picker-BCMU7W7R.js.map +0 -1
- package/dist/crumbs.d.ts.map +0 -1
- package/dist/crumbs.js.map +0 -1
- package/dist/data-controller-DOlmqNKb.js.map +0 -1
- package/dist/data-display.d.ts.map +0 -1
- package/dist/data-display.js.map +0 -1
- package/dist/data-grid-DHWJN66w.d.ts.map +0 -1
- package/dist/data-grid.js.map +0 -1
- package/dist/data-provider-35TyWclX.js.map +0 -1
- package/dist/data-provider-BAn_-oR1.d.ts.map +0 -1
- package/dist/decorate-VRK8rslU.js.map +0 -1
- package/dist/defaults-B4ePs4LB.js.map +0 -1
- package/dist/detail-list-C96B5Txz.js.map +0 -1
- package/dist/detail-list-dX7jLO6b.d.ts.map +0 -1
- package/dist/drag-ghost-DtSDvjri.js.map +0 -1
- package/dist/editable-label-rzXkjf1H.d.ts.map +0 -1
- package/dist/editable-label.js.map +0 -1
- package/dist/fallbacks-DQ_5OsmR.js.map +0 -1
- package/dist/flat-list-Y98IqH00.d.ts.map +0 -1
- package/dist/flat-list.js.map +0 -1
- package/dist/form-CsXJX15p.js.map +0 -1
- package/dist/form.d.ts.map +0 -1
- package/dist/grid-list-CoT9e1jB.d.ts.map +0 -1
- package/dist/grid-list-DeqAd9-h.js.map +0 -1
- package/dist/grouped-item-lister-Bhyo-dkH.d.ts.map +0 -1
- package/dist/grouped-item-lister.js.map +0 -1
- package/dist/grouping-CUKgmkMm.d.ts.map +0 -1
- package/dist/grouping-RYPnHkrS.js.map +0 -1
- package/dist/handle-DAXs7kbJ.js.map +0 -1
- package/dist/hex-editor-Caq8vNQk.d.ts.map +0 -1
- package/dist/hex-editor-DiYpgIKs.js.map +0 -1
- package/dist/highlight-B-eQrhhb.js.map +0 -1
- package/dist/icon-1gpwtPx8.js.map +0 -1
- package/dist/icon-DR_3rSyZ.d.ts.map +0 -1
- package/dist/icons.js.map +0 -1
- package/dist/incr-search-JIhwB49y.js.map +0 -1
- package/dist/index-BOUaDIW-.d.ts.map +0 -1
- package/dist/index-BUB5SICW.d.ts.map +0 -1
- package/dist/index-CJ5fshD3.d.ts.map +0 -1
- package/dist/index-CnQPCywE.d.ts.map +0 -1
- package/dist/index-DV3iaUBW.d.ts.map +0 -1
- package/dist/index-Dlah7Zfi.d.ts.map +0 -1
- package/dist/index-Dq30pOnn.d.ts.map +0 -1
- package/dist/index-garxxqgV.d.ts.map +0 -1
- package/dist/index-o-CWDt11.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/item-appearance-BjcMyauw.js.map +0 -1
- package/dist/keyboard-B5xD7Fz-.js.map +0 -1
- package/dist/labelled-input-base-BFa4xlYV.js.map +0 -1
- package/dist/labelled-input-base-Dp_9KP0G.d.ts.map +0 -1
- package/dist/labelled-radial-input.d.ts.map +0 -1
- package/dist/labelled-radial-input.js.map +0 -1
- package/dist/labelled-range-input.js.map +0 -1
- package/dist/labelled-slider-input-FdCRi6st.js.map +0 -1
- package/dist/led.d.ts.map +0 -1
- package/dist/led.js.map +0 -1
- package/dist/list-group-header.d.ts.map +0 -1
- package/dist/list-group-header.js.map +0 -1
- package/dist/list-item-animation-BUoEKi5s.js.map +0 -1
- package/dist/list-native-drag-controller-B9xO0XWn.js.map +0 -1
- package/dist/list-native-drag-controller-ChIi3eK5.d.ts.map +0 -1
- package/dist/list-selection-controller-BeE70lbB.js.map +0 -1
- package/dist/list-selection-types-PjKdhF-a.d.ts.map +0 -1
- package/dist/list-viewport-types-BRZsLcvp.d.ts.map +0 -1
- package/dist/menu-CwcXXeLM.js.map +0 -1
- package/dist/menu-item-B0zqw2xp.d.ts.map +0 -1
- package/dist/menu-item-BDEekBVy.js.map +0 -1
- package/dist/miller.d.ts.map +0 -1
- package/dist/miller.js.map +0 -1
- package/dist/narrowed-text.js.map +0 -1
- package/dist/notification-pill-DyZydXoZ.d.ts.map +0 -1
- package/dist/notifier.js.map +0 -1
- package/dist/pan-text-CoYmt2qd.d.ts.map +0 -1
- package/dist/pan-text-CqtvjPyK.js.map +0 -1
- package/dist/panel.d.ts.map +0 -1
- package/dist/panel.js.map +0 -1
- package/dist/polar-pad.d.ts.map +0 -1
- package/dist/polar-pad.js.map +0 -1
- package/dist/popup-D80sj2QC.js.map +0 -1
- package/dist/prominence-DlFShic3.js.map +0 -1
- package/dist/radial-input-DU6t2hYO.d.ts.map +0 -1
- package/dist/radial-input-KMVltQfe.js.map +0 -1
- package/dist/range-input.d.ts.map +0 -1
- package/dist/range-input.js.map +0 -1
- package/dist/range.d.ts.map +0 -1
- package/dist/range.js.map +0 -1
- package/dist/registry-BEtb8_wB.js.map +0 -1
- package/dist/scroll-fade-BrNUIYnm.js.map +0 -1
- package/dist/select-horiz.d.ts.map +0 -1
- package/dist/select-horiz.js.map +0 -1
- package/dist/slider-input-BxURAAmD.d.ts.map +0 -1
- package/dist/slider-input-ByHdotzp.js.map +0 -1
- package/dist/snackbar.d.ts.map +0 -1
- package/dist/snackbar.js.map +0 -1
- package/dist/snap-container.d.ts.map +0 -1
- package/dist/snap-container.js.map +0 -1
- package/dist/split-layout-C5SYbDrq.d.ts.map +0 -1
- package/dist/split-layout-Deoxy47d.js.map +0 -1
- package/dist/swipe.d.ts.map +0 -1
- package/dist/swipe.js.map +0 -1
- package/dist/tab-list-D_dmImJM.d.ts.map +0 -1
- package/dist/tabs.js.map +0 -1
- package/dist/tickled-controller-D-HZymaZ.js.map +0 -1
- package/dist/tickled-controller-DodxD3eQ.d.ts.map +0 -1
- package/dist/tickled-styles-BrAHEj1q.js.map +0 -1
- package/dist/timeline-BP1Ux2EV.js.map +0 -1
- package/dist/titlebar-B5zSTZnF.d.ts.map +0 -1
- package/dist/titlebar.js.map +0 -1
- package/dist/tooltip-BlvBUQxx.d.ts.map +0 -1
- package/dist/tooltip-CJt3WLmN.js.map +0 -1
- package/dist/transitory-label.d.ts.map +0 -1
- package/dist/transitory-label.js.map +0 -1
- package/dist/tree-DD8OaqsK.js.map +0 -1
- package/dist/tree-component-Cj8V3uar.d.ts.map +0 -1
- package/dist/types-DQ7KOUIj.d.ts.map +0 -1
- package/dist/vertical-list-lj_19DCx.d.ts.map +0 -1
- package/dist/vertical-list.js.map +0 -1
- package/dist/wheel-nudge-By5s_ioH.d.ts.map +0 -1
- package/dist/wheel-nudge-D5rmht3v.js.map +0 -1
- package/dist/xy-axis-CyMd_Bn1.js.map +0 -1
- package/dist/xy-axis-DQqd2yXJ.d.ts.map +0 -1
- package/dist/xy-pad.d.ts.map +0 -1
- package/dist/xy-pad.js.map +0 -1
|
@@ -0,0 +1,2108 @@
|
|
|
1
|
+
import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
|
|
2
|
+
import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
|
|
3
|
+
import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
4
|
+
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
5
|
+
import "./icon-1gpwtPx8.js";
|
|
6
|
+
import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES } from "./incr-search-JIhwB49y.js";
|
|
7
|
+
import { t as handleStyles } from "./handle-DAXs7kbJ.js";
|
|
8
|
+
import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
|
|
9
|
+
import { i as listItemFadeKeyframes } from "./list-item-animation-BUoEKi5s.js";
|
|
10
|
+
import "./interaction-ZTh4htzr.js";
|
|
11
|
+
import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
|
|
12
|
+
import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
|
|
13
|
+
import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
|
|
14
|
+
import "./menu-CwcXXeLM.js";
|
|
15
|
+
import "./pan-text-CqtvjPyK.js";
|
|
16
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
17
|
+
import { property, query, state } from "lit/decorators.js";
|
|
18
|
+
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
19
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
20
|
+
//#region src/data-grid/shift-wheel.ts
|
|
21
|
+
/**
|
|
22
|
+
* Shift + vertical mouse wheel → horizontal scroll.
|
|
23
|
+
*
|
|
24
|
+
* Browsers usually leave this translation to the OS (macOS turns Shift+wheel
|
|
25
|
+
* into a horizontal delta itself), but some mice, mouse utilities and
|
|
26
|
+
* platforms deliver it as a plain vertical delta with `shiftKey` set — which
|
|
27
|
+
* a two-axis scroller then scrolls vertically.
|
|
28
|
+
*
|
|
29
|
+
* Handling that needs a non-passive `wheel` listener, and a non-passive
|
|
30
|
+
* listener makes the compositor wait on the main thread before every wheel
|
|
31
|
+
* scroll, and not waiting is what keeps a busy virtualised grid's scrolling smooth.
|
|
32
|
+
* So the listener is only attached while Shift is held.
|
|
33
|
+
*
|
|
34
|
+
* @param getScroller Returns the element to scroll horizontally (and listen on).
|
|
35
|
+
* @returns Detach function.
|
|
36
|
+
*/
|
|
37
|
+
function attachShiftWheel(getScroller) {
|
|
38
|
+
let attachedTo;
|
|
39
|
+
const onWheel = (e) => {
|
|
40
|
+
const el = e.currentTarget;
|
|
41
|
+
if (!e.shiftKey || e.deltaX !== 0 || e.deltaY === 0 || el.scrollWidth <= el.clientWidth) return;
|
|
42
|
+
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? el.clientWidth : 1;
|
|
43
|
+
el.scrollLeft += e.deltaY * scale;
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
};
|
|
46
|
+
const detachWheel = () => {
|
|
47
|
+
attachedTo?.removeEventListener(`wheel`, onWheel);
|
|
48
|
+
attachedTo = void 0;
|
|
49
|
+
};
|
|
50
|
+
const onKey = (e) => {
|
|
51
|
+
if (e.key !== `Shift`) return;
|
|
52
|
+
detachWheel();
|
|
53
|
+
if (e.type === `keydown`) {
|
|
54
|
+
attachedTo = getScroller();
|
|
55
|
+
attachedTo?.addEventListener(`wheel`, onWheel, { passive: false });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener(`keydown`, onKey);
|
|
59
|
+
window.addEventListener(`keyup`, onKey);
|
|
60
|
+
window.addEventListener(`blur`, detachWheel);
|
|
61
|
+
return () => {
|
|
62
|
+
window.removeEventListener(`keydown`, onKey);
|
|
63
|
+
window.removeEventListener(`keyup`, onKey);
|
|
64
|
+
window.removeEventListener(`blur`, detachWheel);
|
|
65
|
+
detachWheel();
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/data-grid/data-grid.ts
|
|
70
|
+
const EMPTY_CELL_LOOK = {
|
|
71
|
+
textColor: void 0,
|
|
72
|
+
icon: void 0,
|
|
73
|
+
iconColor: void 0,
|
|
74
|
+
badge: void 0
|
|
75
|
+
};
|
|
76
|
+
/** Shallow key-array equality, treating `undefined` (never dispatched) as different. */
|
|
77
|
+
function sameKeys(a, b) {
|
|
78
|
+
if (a === void 0 || a.length !== b.length) return false;
|
|
79
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
/** Element-wise reference equality, treating `undefined` (never computed) as different. */
|
|
83
|
+
function sameRefs(a, b) {
|
|
84
|
+
return a !== void 0 && a.length === b.length && a.every((v, i) => v === b[i]);
|
|
85
|
+
}
|
|
86
|
+
let DataGridElement = class DataGridElement extends LitElement {
|
|
87
|
+
constructor(..._args) {
|
|
88
|
+
super(..._args);
|
|
89
|
+
this.columns = [];
|
|
90
|
+
this.rows = [];
|
|
91
|
+
this.selectionMode = `single`;
|
|
92
|
+
this.interactionMode = `standard`;
|
|
93
|
+
this.sortState = null;
|
|
94
|
+
this.rowHeight = 28;
|
|
95
|
+
this.getRowKey = (r, i) => r.key ?? r.id ?? String(i);
|
|
96
|
+
this.editable = false;
|
|
97
|
+
this.editability = `user`;
|
|
98
|
+
this.cellOverflow = `ellipsis`;
|
|
99
|
+
this.zebraStripes = false;
|
|
100
|
+
this.groupBy = null;
|
|
101
|
+
this.allowGrouping = false;
|
|
102
|
+
this.crossGroupSelection = false;
|
|
103
|
+
this.columnMenu = `none`;
|
|
104
|
+
this.textColorColumn = ``;
|
|
105
|
+
this.iconColumn = ``;
|
|
106
|
+
this.badgeColumn = ``;
|
|
107
|
+
this.itemsDraggable = false;
|
|
108
|
+
this.columnButtonIcon = `ellipsis`;
|
|
109
|
+
this._columnFormatters = /* @__PURE__ */ new Map();
|
|
110
|
+
this._resizingColKey = void 0;
|
|
111
|
+
this._animatingColKeys = /* @__PURE__ */ new Set();
|
|
112
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
113
|
+
this._scrollTop = 0;
|
|
114
|
+
this._scrollLeft = 0;
|
|
115
|
+
this._viewportH = 300;
|
|
116
|
+
this._viewportW = 600;
|
|
117
|
+
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
118
|
+
this._editing = null;
|
|
119
|
+
this._editingRow = null;
|
|
120
|
+
this._lastClick = null;
|
|
121
|
+
this._lastClickedCell = null;
|
|
122
|
+
this._editValue = ``;
|
|
123
|
+
this._tickled = new TickledController(this, {
|
|
124
|
+
getItems: () => this.#tickledItems ??= this._flat.map((f) => ({
|
|
125
|
+
id: f.key,
|
|
126
|
+
enabled: f.type === `row`
|
|
127
|
+
})),
|
|
128
|
+
onStateChange: () => this.requestUpdate(),
|
|
129
|
+
hoverDisabled: () => this.#nativeDrag.isDragging
|
|
130
|
+
});
|
|
131
|
+
this.#nativeDragging = false;
|
|
132
|
+
this.#postDragSuppressTimer = 0;
|
|
133
|
+
this.#pointerEventSuppressess = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
this.#nativeDrag = new ListNativeDragController(this, {
|
|
135
|
+
source: `data-grid`,
|
|
136
|
+
isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._flat.some((f) => f.type === `row`),
|
|
137
|
+
resolveEventToKey: (target) => {
|
|
138
|
+
if (!(target instanceof Element)) return void 0;
|
|
139
|
+
if (target.closest?.(`.header-cell`)) return void 0;
|
|
140
|
+
return (target.closest?.(`.row`))?.dataset.key;
|
|
141
|
+
},
|
|
142
|
+
prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
|
|
143
|
+
getMetrics: () => ({ rowHeight: this.rowHeight }),
|
|
144
|
+
getRenderer: () => this.dragPreviewRenderer,
|
|
145
|
+
getDragPreviewSourceResolver: () => this.dragPreviewSource,
|
|
146
|
+
dispatch: (detail) => this.dispatchEvent(new CustomEvent(`list-native-drag-start`, {
|
|
147
|
+
detail,
|
|
148
|
+
bubbles: true,
|
|
149
|
+
composed: true,
|
|
150
|
+
cancelable: true
|
|
151
|
+
})),
|
|
152
|
+
onActiveChange: (active, originKey) => {
|
|
153
|
+
this.#nativeDragging = active;
|
|
154
|
+
this.#dragOriginKey = active ? originKey : void 0;
|
|
155
|
+
this.toggleAttribute(`data-native-dragging`, active);
|
|
156
|
+
if (active) {
|
|
157
|
+
const count = this._selectedKeys.size;
|
|
158
|
+
this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
|
|
159
|
+
} else this.removeAttribute(`aria-label`);
|
|
160
|
+
this.requestUpdate();
|
|
161
|
+
},
|
|
162
|
+
onEnd: () => {
|
|
163
|
+
this.#nativeDragging = false;
|
|
164
|
+
this.#dragOriginKey = void 0;
|
|
165
|
+
this.toggleAttribute(`data-native-dragging`, false);
|
|
166
|
+
this.removeAttribute(`aria-label`);
|
|
167
|
+
this.#startPostDragSuppressTimer();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
this._sortedRows = [];
|
|
171
|
+
this._flat = [];
|
|
172
|
+
this._flatKeys = [];
|
|
173
|
+
this.#flatIndexByKey = /* @__PURE__ */ new Map();
|
|
174
|
+
this.#flatVersion = 0;
|
|
175
|
+
this.#notedFlatVersion = -1;
|
|
176
|
+
this.#searchKeyByRow = /* @__PURE__ */ new Map();
|
|
177
|
+
this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
|
|
178
|
+
this.#prevFlatKeys = [];
|
|
179
|
+
this.#enteringRowKeys = /* @__PURE__ */ new Set();
|
|
180
|
+
this.#rowReflowTimer = 0;
|
|
181
|
+
this.#dataGridRenderedOnce = false;
|
|
182
|
+
this._visibleColumns = [];
|
|
183
|
+
this._colOffsets = [];
|
|
184
|
+
this._colWidthSum = 0;
|
|
185
|
+
this._raf = 0;
|
|
186
|
+
this.#viewportChangeRaf = 0;
|
|
187
|
+
this.#resortRaf = 0;
|
|
188
|
+
this._drag = null;
|
|
189
|
+
this._onFocusOut = (e) => {
|
|
190
|
+
if (this._suppressBlurCommit) return;
|
|
191
|
+
if (this.editability === `immediate`) return;
|
|
192
|
+
if (!this._editing && !this._editingRow) return;
|
|
193
|
+
const next = e.relatedTarget;
|
|
194
|
+
if (next && this.contains(next)) return;
|
|
195
|
+
this._editing = null;
|
|
196
|
+
this._editingRow = null;
|
|
197
|
+
this.requestUpdate();
|
|
198
|
+
};
|
|
199
|
+
this._onScroll = (e) => {
|
|
200
|
+
const el = e.target;
|
|
201
|
+
cancelAnimationFrame(this._raf);
|
|
202
|
+
this._raf = requestAnimationFrame(() => {
|
|
203
|
+
const before = this.#windowRows(2);
|
|
204
|
+
this._scrollTop = el.scrollTop;
|
|
205
|
+
this._scrollLeft = el.scrollLeft;
|
|
206
|
+
const after = this.#windowRows(2);
|
|
207
|
+
if (before.start !== after.start || before.end !== after.end) this.requestUpdate();
|
|
208
|
+
cancelAnimationFrame(this.#viewportChangeRaf);
|
|
209
|
+
this.#viewportChangeRaf = 0;
|
|
210
|
+
this.#emitViewportChangeIfChanged();
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
this._onKeyDown = (e) => {
|
|
214
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === `a`) {
|
|
215
|
+
if (this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode` || this.interactionMode === `standard`) {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
const prev = new Set(this._selectedKeys);
|
|
218
|
+
if (this.groupBy && !this.crossGroupSelection && this._anchorKey) {
|
|
219
|
+
const g = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
|
|
220
|
+
this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row` && this._getGroupKey(f.row) === g).map((f) => f.key));
|
|
221
|
+
} else this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
|
|
222
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
223
|
+
detail: {
|
|
224
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
225
|
+
previous: prev
|
|
226
|
+
},
|
|
227
|
+
bubbles: true,
|
|
228
|
+
composed: true
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (this._editing && (e.key === `ArrowDown` || e.key === `ArrowUp`)) {
|
|
234
|
+
if ((this.shadowRoot?.activeElement)?.tagName === `SELECT`) return;
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
const cur = this._editing;
|
|
237
|
+
const dir = e.key === `ArrowDown` ? 1 : -1;
|
|
238
|
+
const colKey = cur.colKey;
|
|
239
|
+
this._suppressBlurCommit = true;
|
|
240
|
+
this._commitEdit();
|
|
241
|
+
const rowKeys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
|
|
242
|
+
const idx = rowKeys.indexOf(cur.rowKey);
|
|
243
|
+
let nextIdx = idx + dir;
|
|
244
|
+
while (nextIdx >= 0 && nextIdx < rowKeys.length) {
|
|
245
|
+
const nk = rowKeys[nextIdx];
|
|
246
|
+
const row = this.#rowEntry(nk)?.row;
|
|
247
|
+
const col = this._visibleColumns.find((c) => c.key === colKey);
|
|
248
|
+
if (row && col && this._isEditable(row, col)) {
|
|
249
|
+
this._startEdit(nk, colKey);
|
|
250
|
+
this._tickled.handleKeyboardNavigation(nextIdx > idx ? `next` : `previous`);
|
|
251
|
+
this._scrollRowIntoView(nk);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
nextIdx += dir;
|
|
255
|
+
}
|
|
256
|
+
if (nextIdx < 0 || nextIdx >= rowKeys.length) {
|
|
257
|
+
this._tickled.handleKeyboardNavigation(dir > 0 ? `next` : `previous`);
|
|
258
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
259
|
+
}
|
|
260
|
+
requestAnimationFrame(() => {
|
|
261
|
+
this._suppressBlurCommit = false;
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (e.key === `ArrowDown` || e.key === `ArrowUp` || e.key === `Home` || e.key === `End`) {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
this._tickled.setInputMode(`keyboard`);
|
|
268
|
+
}
|
|
269
|
+
switch (e.key) {
|
|
270
|
+
case `ArrowDown`:
|
|
271
|
+
this._tickled.handleKeyboardNavigation(`next`);
|
|
272
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
273
|
+
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
274
|
+
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
275
|
+
break;
|
|
276
|
+
case `ArrowUp`:
|
|
277
|
+
this._tickled.handleKeyboardNavigation(`previous`);
|
|
278
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
279
|
+
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
280
|
+
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
281
|
+
break;
|
|
282
|
+
case `Home`:
|
|
283
|
+
this._tickled.handleKeyboardNavigation(`first`);
|
|
284
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
285
|
+
break;
|
|
286
|
+
case `End`:
|
|
287
|
+
this._tickled.handleKeyboardNavigation(`last`);
|
|
288
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
289
|
+
break;
|
|
290
|
+
case `Enter`: {
|
|
291
|
+
const key = this._tickled.tickledId;
|
|
292
|
+
if (!key) break;
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
if (!this._editing && this.editability === `user`) {
|
|
295
|
+
const entry = this.#rowEntry(key);
|
|
296
|
+
if (entry) {
|
|
297
|
+
const row = entry.row;
|
|
298
|
+
if (this._lastClickedCell?.rowKey === key) {
|
|
299
|
+
const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
|
|
300
|
+
if (col && this._isEditable(row, col)) {
|
|
301
|
+
this._startEdit(key, col.key);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const firstEditable = this._visibleColumns.find((c) => this._isEditable(row, c));
|
|
306
|
+
if (firstEditable) {
|
|
307
|
+
this._startEdit(key, firstEditable.key);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!this._editing) {
|
|
313
|
+
const entry = this.#rowEntry(key);
|
|
314
|
+
const idx = entry?.idx ?? -1;
|
|
315
|
+
if (this._selectedKeys.has(key)) {
|
|
316
|
+
this._activateRow(key, entry?.row, idx);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
|
|
320
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
|
|
321
|
+
detail: {
|
|
322
|
+
rowKey: key,
|
|
323
|
+
colKey: ``,
|
|
324
|
+
rowIndex: idx,
|
|
325
|
+
colIndex: -1
|
|
326
|
+
},
|
|
327
|
+
bubbles: true,
|
|
328
|
+
composed: true
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case ` `: {
|
|
334
|
+
if (this._editing) {
|
|
335
|
+
if ((this.columns.find((c) => c.key === this._editing.colKey)?.dataType ?? `text`) === `boolean`) {
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
const isImmediate = this.editability === `immediate`;
|
|
338
|
+
const rowKey = this._editing.rowKey;
|
|
339
|
+
const colKey = this._editing.colKey;
|
|
340
|
+
const active = this.shadowRoot?.activeElement;
|
|
341
|
+
let next;
|
|
342
|
+
if (active?.type === `checkbox`) {
|
|
343
|
+
next = !active.checked;
|
|
344
|
+
active.checked = next;
|
|
345
|
+
} else {
|
|
346
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
347
|
+
next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
|
|
348
|
+
}
|
|
349
|
+
if (isImmediate) this._commitImmediate(rowKey, colKey, next);
|
|
350
|
+
else {
|
|
351
|
+
this._editValue = next;
|
|
352
|
+
this._commitEdit();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
const key = this._tickled.tickledId;
|
|
358
|
+
if (!key) break;
|
|
359
|
+
e.preventDefault();
|
|
360
|
+
if (this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `standard` || this.interactionMode === `vscode` || this.interactionMode === `manual`) this._toggleSelection(key);
|
|
361
|
+
else this._replaceSelection(key);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case `Escape`: if (this._editing || this._editingRow) {
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
this._editing = null;
|
|
367
|
+
this._editingRow = null;
|
|
368
|
+
this.requestUpdate();
|
|
369
|
+
} else if (this._selectedKeys.size) {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
const prev = new Set(this._selectedKeys);
|
|
372
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
373
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
374
|
+
detail: {
|
|
375
|
+
selectedKeys: /* @__PURE__ */ new Set(),
|
|
376
|
+
previous: prev
|
|
377
|
+
},
|
|
378
|
+
bubbles: true,
|
|
379
|
+
composed: true
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
this._suppressBlurCommit = false;
|
|
385
|
+
}
|
|
386
|
+
static {
|
|
387
|
+
this.styles = [
|
|
388
|
+
themeFallbacks,
|
|
389
|
+
handleStyles,
|
|
390
|
+
tickledStyles,
|
|
391
|
+
tickledItemStyles,
|
|
392
|
+
listItemFadeKeyframes,
|
|
393
|
+
listSearchStyles,
|
|
394
|
+
scrollFadeStyles(`x`),
|
|
395
|
+
scrollFadeStyles(`y`),
|
|
396
|
+
css`
|
|
397
|
+
:host {
|
|
398
|
+
--data-grid-row-height: 28px;
|
|
399
|
+
--data-grid-header-height: 32px;
|
|
400
|
+
--data-grid-bg: var(--surface-3);
|
|
401
|
+
--data-grid-header-bg: var(--surface-2);
|
|
402
|
+
--data-grid-border: var(--border);
|
|
403
|
+
--data-grid-header-text: var(--surface-3-text);
|
|
404
|
+
--data-grid-reorder-handle-width: 4px;
|
|
405
|
+
/* How strongly the alternate ("zebra") row stands out from the base row
|
|
406
|
+
background when [zebra-stripes] is set - a color-mix() percentage. */
|
|
407
|
+
--data-grid-zebra-intensity: 6%;
|
|
408
|
+
/* Duration of the width transition when a column is auto-sized via
|
|
409
|
+
autoSizeColumn(). Read back in JS (_columnResizeAnimationMs) to know
|
|
410
|
+
when the animation has finished, so keep it a plain time value. */
|
|
411
|
+
--data-grid-column-resize-duration: 180ms;
|
|
412
|
+
/* Dense 28px rows read badly under the global 30px full-strength fade,
|
|
413
|
+
so default to a shorter, shallower edge hint. Consumers can override
|
|
414
|
+
--scroll-fade-width / --scroll-fade-strength per instance. */
|
|
415
|
+
--scroll-fade-width: 20px;
|
|
416
|
+
--scroll-fade-strength: 0.8;
|
|
417
|
+
position: relative;
|
|
418
|
+
display: block;
|
|
419
|
+
overflow: hidden;
|
|
420
|
+
border: var(--data-grid-border);
|
|
421
|
+
border-radius: var(--radius-m);
|
|
422
|
+
background: var(--data-grid-bg);
|
|
423
|
+
color: var(--surface-3-text);
|
|
424
|
+
font-family: var(--font-family);
|
|
425
|
+
font-size: var(--text-m);
|
|
426
|
+
}
|
|
427
|
+
:host(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
428
|
+
.grid { display: flex; flex-direction: column; height: 100%; min-height: 200px; position: relative; }
|
|
429
|
+
/* The header row lives inside .viewport and is sticky to its top, so it
|
|
430
|
+
scrolls horizontally with the rows on the compositor. Syncing a
|
|
431
|
+
separate header's scrollLeft from JS lags behind whenever the main
|
|
432
|
+
thread is busy rendering rows. */
|
|
433
|
+
.header-row {
|
|
434
|
+
position: sticky; top: 0; z-index: 3;
|
|
435
|
+
display: flex; align-items: stretch; height: var(--data-grid-header-height);
|
|
436
|
+
min-width: 100%; width: max-content; box-sizing: border-box;
|
|
437
|
+
background: var(--data-grid-header-bg); border-bottom: var(--data-grid-border);
|
|
438
|
+
/* Room for the last column to scroll out from under the column menu. */
|
|
439
|
+
padding-inline-end: var(--data-grid-actions-width, 0px);
|
|
440
|
+
}
|
|
441
|
+
/* Horizontal edge fade. The header row is not itself a scroller, so the
|
|
442
|
+
shared .scroll-fade-x animation reads the viewport's timeline instead,
|
|
443
|
+
and a second animation slides a viewport-wide mask along the
|
|
444
|
+
(full-width) row so the fade stays on the visible edges. */
|
|
445
|
+
.header-row.scroll-fade-x {
|
|
446
|
+
--scroll-fade-inset-start: 0px;
|
|
447
|
+
--scroll-fade-inset-end: var(--data-grid-actions-width, 0px);
|
|
448
|
+
scroll-timeline: none;
|
|
449
|
+
animation: scroll-fade-x-fade, data-grid-header-mask-track;
|
|
450
|
+
animation-timing-function: linear;
|
|
451
|
+
animation-timeline: scroll(nearest x), scroll(nearest x);
|
|
452
|
+
mask-size: var(--data-grid-viewport-width, 100%) 100%;
|
|
453
|
+
mask-repeat: no-repeat;
|
|
454
|
+
}
|
|
455
|
+
@keyframes data-grid-header-mask-track {
|
|
456
|
+
from { mask-position: 0 0; }
|
|
457
|
+
to { mask-position: 100% 0; }
|
|
458
|
+
}
|
|
459
|
+
.header-actions {
|
|
460
|
+
position: absolute; top: 0; z-index: 4;
|
|
461
|
+
right: var(--data-grid-scrollbar-width, 0px);
|
|
462
|
+
width: var(--data-grid-actions-width); height: var(--data-grid-header-height);
|
|
463
|
+
display: flex; box-sizing: border-box; border-bottom: var(--data-grid-border);
|
|
464
|
+
}
|
|
465
|
+
.header-cell {
|
|
466
|
+
display: flex; align-items: center; gap: var(--space-xs);
|
|
467
|
+
padding: 0 var(--space-s); border-right: var(--border-subtle);
|
|
468
|
+
flex-shrink: 0; user-select: none; cursor: pointer; position: relative;
|
|
469
|
+
background: var(--data-grid-header-bg);
|
|
470
|
+
box-sizing: border-box;
|
|
471
|
+
}
|
|
472
|
+
.header-cell[data-tickled] { background: var(--surface-5); }
|
|
473
|
+
/* Applied to a header-cell / cell while its column is animating towards
|
|
474
|
+
an autoSizeColumn() target width. */
|
|
475
|
+
.header-cell.width-animating, .cell.width-animating {
|
|
476
|
+
transition: width var(--data-grid-column-resize-duration, 180ms) ease-out;
|
|
477
|
+
}
|
|
478
|
+
@media (prefers-reduced-motion: reduce) {
|
|
479
|
+
.header-cell.width-animating, .cell.width-animating { transition: none; }
|
|
480
|
+
}
|
|
481
|
+
.header-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
482
|
+
.sort-icon { display: inline-flex; align-items: center; opacity: 0.7; }
|
|
483
|
+
.resize-handle {
|
|
484
|
+
position: absolute; right: -4px; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1;
|
|
485
|
+
display: flex; align-items: stretch; justify-content: center;
|
|
486
|
+
}
|
|
487
|
+
.resize-handle::before {
|
|
488
|
+
content: ''; width: 2px; margin: 2px 0; border-radius: 1px; background: var(--split-gutter-color, transparent);
|
|
489
|
+
transition: background-color var(--transition, 0.15s ease-out);
|
|
490
|
+
}
|
|
491
|
+
.resize-handle:hover::before { background: var(--split-gutter-hover-color, var(--surface-6)); }
|
|
492
|
+
.resize-handle[data-active]::before { background: var(--split-gutter-active-color, var(--accent)); }
|
|
493
|
+
.reorder-handle { cursor: grab; opacity: 0; display: inline-flex; align-items: center; transition: opacity var(--transition, 0.15s ease-out); }
|
|
494
|
+
.header-cell:hover .reorder-handle, .header-cell:focus-within .reorder-handle, .header-cell[drag-source] .reorder-handle { opacity: var(--ixfx-handle-opacity, 0.5); }
|
|
495
|
+
.col-button { opacity: 0; pointer-events: none; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: var(--radius-s); cursor: pointer; flex-shrink: 0; margin-left: var(--space-xs); transition: opacity var(--transition, 0.15s ease-out), background var(--transition, 0.15s ease-out); }
|
|
496
|
+
.col-button ixfx-icon { width: 14px; height: 14px; }
|
|
497
|
+
.header-cell:hover .col-button, .header-cell:focus-within .col-button { opacity: 1; pointer-events: auto; }
|
|
498
|
+
.col-button:hover { background: var(--surface-4); }
|
|
499
|
+
.reorder-handle .ixfx-handle { --ixfx-handle-length: 14px; --ixfx-handle-thickness: var(--data-grid-reorder-handle-width, 10px); --ixfx-handle-dot-size: 3px; }
|
|
500
|
+
.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); }
|
|
501
|
+
.grid.has-actions { --data-grid-actions-width: 44px; }
|
|
502
|
+
.viewport {
|
|
503
|
+
flex: 1; overflow: auto; position: relative;
|
|
504
|
+
/* Keyboard / focus scrolling must not park rows under the sticky header. */
|
|
505
|
+
scroll-padding-top: var(--data-grid-header-height);
|
|
506
|
+
--scroll-fade-inset-start: var(--data-grid-header-height);
|
|
507
|
+
}
|
|
508
|
+
/* Vertical edge fade from the shared .scroll-fade-y class
|
|
509
|
+
(see ../styles/scroll-fade.ts). The mask is on the viewport, inset by
|
|
510
|
+
the header height so the top fade starts below the sticky column
|
|
511
|
+
headers. Reach/strength tune via --scroll-fade-width /
|
|
512
|
+
--scroll-fade-strength (defaults set on :host above). */
|
|
513
|
+
.spacer { position: relative; }
|
|
514
|
+
.row.item {
|
|
515
|
+
position: absolute; left: 0; right: 0; display: flex; align-items: stretch;
|
|
516
|
+
gap: 0;
|
|
517
|
+
height: var(--data-grid-row-height);
|
|
518
|
+
/* Border width is always 1px, even when not visually shown (transparent),
|
|
519
|
+
so hover/selection never changes the box size and shifts content. */
|
|
520
|
+
border: 1px solid transparent;
|
|
521
|
+
background: var(--item-bg);
|
|
522
|
+
border-radius: var(--item-border-radius);
|
|
523
|
+
padding: 0;
|
|
524
|
+
box-sizing: border-box;
|
|
525
|
+
/* Only background transitions - hover ("tickled") no longer changes
|
|
526
|
+
border, so there is nothing else to animate. */
|
|
527
|
+
transition: background var(--transition, 0.1s ease-out);
|
|
528
|
+
}
|
|
529
|
+
/* Zebra striping: alternate rows tint towards the text colour, which
|
|
530
|
+
lightens in dark themes and darkens in light themes - so it reads as
|
|
531
|
+
"more contrast" either way. The zebra-odd class is only applied when
|
|
532
|
+
zebraStripes is set (see render()). Declared before .tickled /
|
|
533
|
+
.selected so those states (same specificity) still win when a striped
|
|
534
|
+
row is also hovered or selected. */
|
|
535
|
+
.row.item.zebra-odd {
|
|
536
|
+
background: color-mix(in oklch, transparent calc(100% - var(--data-grid-zebra-intensity, 6%)), var(--surface-3-text) var(--data-grid-zebra-intensity, 6%));
|
|
537
|
+
}
|
|
538
|
+
/* Hover: background only, no border change (avoids layout shift + colour flash). */
|
|
539
|
+
.row.item.tickled { background: var(--item-bg-tickled); color: var(--item-text-tickled); }
|
|
540
|
+
.row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
|
|
541
|
+
.row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
|
|
542
|
+
.row.item.dragging-origin { opacity: 0.6; }
|
|
543
|
+
|
|
544
|
+
/* Insertion / removal animation. Only while [reflowing] — the ~0.3s after
|
|
545
|
+
the row set changes — do rows slide (their top transitions) to make or
|
|
546
|
+
close a gap; a freshly inserted row also fades in. Kept scoped so it
|
|
547
|
+
never fires while scrolling the virtualised list. */
|
|
548
|
+
:host([reflowing]) .row.item,
|
|
549
|
+
:host([reflowing]) .group-row {
|
|
550
|
+
transition: top var(--data-grid-row-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1);
|
|
551
|
+
}
|
|
552
|
+
.row.item.entering {
|
|
553
|
+
animation: ixfx-list-item-in var(--data-grid-row-move-duration, 200ms) ease both;
|
|
554
|
+
}
|
|
555
|
+
@media (prefers-reduced-motion: reduce) {
|
|
556
|
+
:host([reflowing]) .row.item, :host([reflowing]) .group-row { transition: none; }
|
|
557
|
+
.row.item.entering { animation: none; }
|
|
558
|
+
}
|
|
559
|
+
.cell {
|
|
560
|
+
flex-shrink: 0; display: flex; align-items: center; padding: 0 var(--space-s);
|
|
561
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-right: var(--border-subtle);
|
|
562
|
+
box-sizing: border-box;
|
|
563
|
+
font-variant-numeric: tabular-nums;
|
|
564
|
+
}
|
|
565
|
+
.cell.editable-text { cursor: text; }
|
|
566
|
+
.cell input, .cell select { width: 100%; font: inherit; }
|
|
567
|
+
/* Per-row icon / badge, enabled by nominating iconColumn / badgeColumn.
|
|
568
|
+
The badge self-justifies to the cell's trailing edge; the value text
|
|
569
|
+
keeps its own flex sizing so the cell still ellipsises. */
|
|
570
|
+
.cell-icon {
|
|
571
|
+
flex: none;
|
|
572
|
+
display: inline-flex;
|
|
573
|
+
align-items: center;
|
|
574
|
+
justify-content: center;
|
|
575
|
+
margin-inline-end: var(--data-grid-cell-icon-gap, 6px);
|
|
576
|
+
width: var(--data-grid-cell-icon-size, 1em);
|
|
577
|
+
height: var(--data-grid-cell-icon-size, 1em);
|
|
578
|
+
}
|
|
579
|
+
.cell-badge {
|
|
580
|
+
flex: none;
|
|
581
|
+
display: inline-flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
margin-inline-start: auto;
|
|
584
|
+
padding-inline-start: var(--data-grid-badge-gap, 6px);
|
|
585
|
+
}
|
|
586
|
+
.checkbox-col { width: 28px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
587
|
+
ixfx-menu-trigger { flex: 1; border-left: var(--border-subtle); background: var(--data-grid-header-bg); display: flex; align-items: center; --ixfx-button-padding: var(--space-s) var(--space-m); min-height: var(--data-grid-header-height); justify-content: center; }
|
|
588
|
+
.header-cell[drag-source] { opacity: 0.4; }
|
|
589
|
+
.drop-caret {
|
|
590
|
+
position: absolute; top: 0; bottom: 0; width: 2px;
|
|
591
|
+
background: var(--accent); border-radius: 1px; pointer-events: none; z-index: 2;
|
|
592
|
+
}
|
|
593
|
+
.group-row {
|
|
594
|
+
position: absolute; left: 0; right: 0; display: flex; align-items: center; gap: var(--space-s);
|
|
595
|
+
height: var(--data-grid-row-height); background: transparent; color: var(--surface-3-text);
|
|
596
|
+
border-bottom: 1px solid var(--border); font-weight: 600; padding: 0 var(--space-s);
|
|
597
|
+
cursor: pointer; user-select: none;
|
|
598
|
+
}
|
|
599
|
+
.group-row .caret { transition: transform 0.15s; display: inline-flex; }
|
|
600
|
+
.group-row.collapsed .caret { transform: rotate(-90deg); }
|
|
601
|
+
.group-row .divider { flex: 1; height: 1px; background: var(--border); margin-left: var(--space-s); }
|
|
602
|
+
.group-row .count { opacity: 0.6; font-weight: 400; font-size: 0.9em; }
|
|
603
|
+
|
|
604
|
+
`
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
#viewportResizeObserver;
|
|
608
|
+
#nativeDragging;
|
|
609
|
+
#dragOriginKey;
|
|
610
|
+
#postDragSuppressTimer;
|
|
611
|
+
#pointerEventSuppressess;
|
|
612
|
+
#nativeDrag;
|
|
613
|
+
#prepareNativeDrag(originKey) {
|
|
614
|
+
const rows = this._flat.filter((f) => f.type === `row`);
|
|
615
|
+
if (!rows.some((f) => f.key === originKey)) return void 0;
|
|
616
|
+
if (!this._selectedKeys.has(originKey)) this._replaceSelection(originKey);
|
|
617
|
+
const selected = [];
|
|
618
|
+
let displayIndex = 0;
|
|
619
|
+
for (const entry of rows) {
|
|
620
|
+
if (!this._selectedKeys.has(entry.key)) {
|
|
621
|
+
displayIndex++;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const rowEl = this.renderRoot.querySelector(`.row[data-key="${CSS.escape(entry.key)}"]`) ?? void 0;
|
|
625
|
+
selected.push({
|
|
626
|
+
key: entry.key,
|
|
627
|
+
index: displayIndex,
|
|
628
|
+
item: entry.row,
|
|
629
|
+
...rowEl ? { element: rowEl } : {}
|
|
630
|
+
});
|
|
631
|
+
displayIndex++;
|
|
632
|
+
}
|
|
633
|
+
const origin = selected.find((entry) => entry.key === originKey);
|
|
634
|
+
if (!origin) return void 0;
|
|
635
|
+
return {
|
|
636
|
+
origin,
|
|
637
|
+
selected
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
#startPostDragSuppressTimer() {
|
|
641
|
+
if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
|
|
642
|
+
this.#postDragSuppressTimer = window.setTimeout(() => {
|
|
643
|
+
this.#postDragSuppressTimer = 0;
|
|
644
|
+
}, 0);
|
|
645
|
+
}
|
|
646
|
+
#flatIndexByKey;
|
|
647
|
+
/** Bumped whenever `_flat` changes; drives `#noteFlatMutation`. */
|
|
648
|
+
#flatVersion;
|
|
649
|
+
#notedFlatVersion;
|
|
650
|
+
/** Inputs `_sortedRows` / `_flat` were last derived from — see `#syncDerivedRows`. */
|
|
651
|
+
#sortInputs;
|
|
652
|
+
#flatInputs;
|
|
653
|
+
/** `_flat` projected for the tickled controller, cached until `_flat` changes. */
|
|
654
|
+
#tickledItems;
|
|
655
|
+
#searchRegistry;
|
|
656
|
+
#unregisterSearchCommands;
|
|
657
|
+
#searchKeyByRow;
|
|
658
|
+
/** Command registry carrying this grid's `list.search.*` commands. */
|
|
659
|
+
get registry() {
|
|
660
|
+
return this.#searchRegistry;
|
|
661
|
+
}
|
|
662
|
+
#rowSearchText(row) {
|
|
663
|
+
if (this.rowSearchText) return this.rowSearchText(row);
|
|
664
|
+
const rec = row;
|
|
665
|
+
return this._visibleColumns.map((c) => String(rec[c.key] ?? ``)).join(` `);
|
|
666
|
+
}
|
|
667
|
+
#createSearchAdapter() {
|
|
668
|
+
return {
|
|
669
|
+
getItems: () => {
|
|
670
|
+
this.#searchKeyByRow.clear();
|
|
671
|
+
this._sortedRows.forEach((r, i) => this.#searchKeyByRow.set(r, this.getRowKey(r, i)));
|
|
672
|
+
return this._sortedRows;
|
|
673
|
+
},
|
|
674
|
+
getLabel: (row) => this.#rowSearchText(row),
|
|
675
|
+
getKey: (row) => this.#searchKeyByRow.get(row) ?? String(this._sortedRows.indexOf(row)),
|
|
676
|
+
focusItem: (row) => {
|
|
677
|
+
if (row === void 0) return;
|
|
678
|
+
const key = this.#searchKeyByRow.get(row);
|
|
679
|
+
if (key !== void 0) this.tickleRow(key);
|
|
680
|
+
},
|
|
681
|
+
selectItems: (rows) => {
|
|
682
|
+
this.selectKeys(rows.map((r) => this.#searchKeyByRow.get(r)).filter((k) => k !== void 0));
|
|
683
|
+
},
|
|
684
|
+
applyFilter: (matched) => {
|
|
685
|
+
this.filterPredicate = matched === void 0 ? void 0 : (row) => {
|
|
686
|
+
const k = this.#searchKeyByRow.get(row);
|
|
687
|
+
return k === void 0 || matched.has(k);
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
#effectiveSearchModes() {
|
|
693
|
+
const raw = this.searchModes?.trim();
|
|
694
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
695
|
+
const requested = new Set(raw.split(/\s+/));
|
|
696
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
697
|
+
}
|
|
698
|
+
#syncSearchCommands() {
|
|
699
|
+
if (!this.#searchRegistry) return;
|
|
700
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
701
|
+
this.#unregisterSearchCommands?.();
|
|
702
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
703
|
+
}
|
|
704
|
+
/** Row-key snapshot from the previous update, for the insertion / removal animation. */
|
|
705
|
+
#prevFlatKeys;
|
|
706
|
+
#enteringRowKeys;
|
|
707
|
+
#rowReflowTimer;
|
|
708
|
+
#dataGridRenderedOnce;
|
|
709
|
+
#viewportChangeRaf;
|
|
710
|
+
/** Key sets as of the last `datagrid-viewport-change` dispatch, for dedupe. */
|
|
711
|
+
#lastViewportRenderedKeys;
|
|
712
|
+
#lastViewportVisibleKeys;
|
|
713
|
+
#resortRaf;
|
|
714
|
+
#detachShiftWheel;
|
|
715
|
+
connectedCallback() {
|
|
716
|
+
super.connectedCallback();
|
|
717
|
+
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
718
|
+
this.addEventListener(`keydown`, this._onKeyDown);
|
|
719
|
+
this.addEventListener(`focusout`, this._onFocusOut);
|
|
720
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
721
|
+
this.#syncSearchCommands();
|
|
722
|
+
this.#detachShiftWheel = attachShiftWheel(() => this._viewportEl ?? void 0);
|
|
723
|
+
}
|
|
724
|
+
disconnectedCallback() {
|
|
725
|
+
super.disconnectedCallback();
|
|
726
|
+
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
727
|
+
this.removeEventListener(`focusout`, this._onFocusOut);
|
|
728
|
+
cancelAnimationFrame(this._raf);
|
|
729
|
+
cancelAnimationFrame(this.#viewportChangeRaf);
|
|
730
|
+
this.#viewportChangeRaf = 0;
|
|
731
|
+
this.#unregisterSearchCommands?.();
|
|
732
|
+
this.#unregisterSearchCommands = void 0;
|
|
733
|
+
this.#searchRegistry = void 0;
|
|
734
|
+
if (this.#rowReflowTimer) {
|
|
735
|
+
clearTimeout(this.#rowReflowTimer);
|
|
736
|
+
this.#rowReflowTimer = 0;
|
|
737
|
+
}
|
|
738
|
+
if (this.#postDragSuppressTimer) {
|
|
739
|
+
clearTimeout(this.#postDragSuppressTimer);
|
|
740
|
+
this.#postDragSuppressTimer = 0;
|
|
741
|
+
}
|
|
742
|
+
this.#viewportResizeObserver?.disconnect();
|
|
743
|
+
this.#viewportResizeObserver = void 0;
|
|
744
|
+
this.#detachShiftWheel?.();
|
|
745
|
+
this.#detachShiftWheel = void 0;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Dispatch a command to this grid — routes through the command registry so
|
|
749
|
+
* `list.search.*` (and any other registered commands) work.
|
|
750
|
+
*/
|
|
751
|
+
dispatch(commandId, args) {
|
|
752
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
753
|
+
}
|
|
754
|
+
firstUpdated() {
|
|
755
|
+
this.#viewportResizeObserver = new ResizeObserver(() => this.#measureViewport());
|
|
756
|
+
this.#viewportResizeObserver.observe(this._viewportEl);
|
|
757
|
+
this.#measureViewport();
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Re-read the viewport's size and update `_viewportH`/`_viewportW` if it
|
|
761
|
+
* changed. A zero-sized read (e.g. while hosted in a hidden/inactive dock
|
|
762
|
+
* tab) is ignored so it never clobbers the last known-good geometry — the
|
|
763
|
+
* observer fires again with the real size once the host becomes visible.
|
|
764
|
+
*/
|
|
765
|
+
#measureViewport() {
|
|
766
|
+
if (!this._viewportEl) return;
|
|
767
|
+
const vp = this._viewportEl;
|
|
768
|
+
const w = vp.clientWidth;
|
|
769
|
+
const h = this.#bodyHeight();
|
|
770
|
+
if (w <= 0 || h <= 0) return;
|
|
771
|
+
const grid = vp.parentElement;
|
|
772
|
+
grid.style.setProperty(`--data-grid-scrollbar-width`, `${vp.offsetWidth - w}px`);
|
|
773
|
+
grid.style.setProperty(`--data-grid-viewport-width`, `${w}px`);
|
|
774
|
+
if (w === this._viewportW && h === this._viewportH) return;
|
|
775
|
+
this._viewportW = w;
|
|
776
|
+
this._viewportH = h;
|
|
777
|
+
this.#scheduleViewportChangeEmit();
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Force a fresh re-measure of the viewport and re-render. Normally
|
|
781
|
+
* unnecessary — a `ResizeObserver` tracks the viewport automatically — but
|
|
782
|
+
* gives a consumer an escape hatch to recover if an ancestor's
|
|
783
|
+
* layout/visibility changes without the observer noticing (matches
|
|
784
|
+
* `ixfx-grid-list`'s `refreshLayout()`).
|
|
785
|
+
*/
|
|
786
|
+
refreshLayout() {
|
|
787
|
+
this.#measureViewport();
|
|
788
|
+
this.requestUpdate();
|
|
789
|
+
}
|
|
790
|
+
updated(changed) {
|
|
791
|
+
super.updated(changed);
|
|
792
|
+
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`filterPredicate`) || changed.has(`rowHeight`)) this.#scheduleViewportChangeEmit();
|
|
793
|
+
if (changed.has(`_editing`) || changed.has(`_editingRow`)) this.updateComplete.then(() => {
|
|
794
|
+
let sel = null;
|
|
795
|
+
if (this._editing) sel = `.row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] input, .row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] select`;
|
|
796
|
+
else if (this._editingRow) sel = `.row[data-key="${this._editingRow}"] .cell input, .row[data-key="${this._editingRow}"] .cell select`;
|
|
797
|
+
if (!sel) return;
|
|
798
|
+
const el = this.renderRoot.querySelector(sel);
|
|
799
|
+
el?.focus();
|
|
800
|
+
if (el instanceof HTMLInputElement && el.type === `text`) el.select();
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
willUpdate(changed) {
|
|
804
|
+
super.willUpdate(changed);
|
|
805
|
+
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
806
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
807
|
+
this.#syncDerivedRows();
|
|
808
|
+
this._visibleColumns = this.columns.filter((c) => c.visible !== false);
|
|
809
|
+
let off = 0;
|
|
810
|
+
this._colOffsets = this._visibleColumns.map((c) => {
|
|
811
|
+
const o = off;
|
|
812
|
+
off += c.width ?? 150;
|
|
813
|
+
return o;
|
|
814
|
+
});
|
|
815
|
+
this._colWidthSum = off;
|
|
816
|
+
if (this.interactionMode === `checked`) this._colWidthSum += 28;
|
|
817
|
+
if (this.#flatVersion !== this.#notedFlatVersion) {
|
|
818
|
+
this.#notedFlatVersion = this.#flatVersion;
|
|
819
|
+
this.#noteFlatMutation();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Re-derive `_sortedRows` / `_flat` only when one of their inputs changed
|
|
824
|
+
* identity. Scroll, hover, selection and edit-state updates touch none of
|
|
825
|
+
* them, so those frames cost O(window) instead of O(rows).
|
|
826
|
+
*/
|
|
827
|
+
#syncDerivedRows() {
|
|
828
|
+
const sortInputs = [
|
|
829
|
+
this.rows,
|
|
830
|
+
this.sortState,
|
|
831
|
+
this.columns
|
|
832
|
+
];
|
|
833
|
+
const flatInputs = [
|
|
834
|
+
this.filterPredicate,
|
|
835
|
+
this.groupBy,
|
|
836
|
+
this.groupLabel,
|
|
837
|
+
this._collapsedGroups,
|
|
838
|
+
this.getRowKey
|
|
839
|
+
];
|
|
840
|
+
const resort = !sameRefs(this.#sortInputs, sortInputs);
|
|
841
|
+
if (resort) {
|
|
842
|
+
this.#sortInputs = sortInputs;
|
|
843
|
+
this._sortedRows = this._applySort([...this.rows]);
|
|
844
|
+
}
|
|
845
|
+
if (resort || !sameRefs(this.#flatInputs, flatInputs)) {
|
|
846
|
+
this.#flatInputs = flatInputs;
|
|
847
|
+
this._rebuildFlat();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/** Mark the derived rows as matching the current inputs (after an incremental update). */
|
|
851
|
+
#markDerivedRowsSynced() {
|
|
852
|
+
this.#sortInputs = [
|
|
853
|
+
this.rows,
|
|
854
|
+
this.sortState,
|
|
855
|
+
this.columns
|
|
856
|
+
];
|
|
857
|
+
this.#flatInputs = [
|
|
858
|
+
this.filterPredicate,
|
|
859
|
+
this.groupBy,
|
|
860
|
+
this.groupLabel,
|
|
861
|
+
this._collapsedGroups,
|
|
862
|
+
this.getRowKey
|
|
863
|
+
];
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Diff the row-key list against the previous update. Rows that appeared get an
|
|
867
|
+
* enter fade; any add/remove opens a short `[reflowing]` window so the rows
|
|
868
|
+
* that shift slide (their `top` transitions) instead of snapping. Bounded in
|
|
869
|
+
* time so it never bleeds into scroll virtualisation.
|
|
870
|
+
*/
|
|
871
|
+
#noteFlatMutation() {
|
|
872
|
+
const now = this._flatKeys;
|
|
873
|
+
const prev = this.#prevFlatKeys;
|
|
874
|
+
this.#prevFlatKeys = now.slice();
|
|
875
|
+
if (!this.#dataGridRenderedOnce) {
|
|
876
|
+
this.#dataGridRenderedOnce = true;
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (prev.length === 0 || now.length === 0) {
|
|
880
|
+
this.#enteringRowKeys.clear();
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const before = new Set(prev);
|
|
884
|
+
const added = now.filter((k) => !before.has(k));
|
|
885
|
+
const removedCount = prev.length - (now.length - added.length);
|
|
886
|
+
if (added.length === now.length || added.length === 0 && removedCount === 0) return;
|
|
887
|
+
this.#enteringRowKeys = new Set(added);
|
|
888
|
+
this.toggleAttribute(`reflowing`, true);
|
|
889
|
+
if (this.#rowReflowTimer) clearTimeout(this.#rowReflowTimer);
|
|
890
|
+
this.#rowReflowTimer = window.setTimeout(() => {
|
|
891
|
+
this.#rowReflowTimer = 0;
|
|
892
|
+
this.#enteringRowKeys.clear();
|
|
893
|
+
this.toggleAttribute(`reflowing`, false);
|
|
894
|
+
this.requestUpdate();
|
|
895
|
+
}, 320);
|
|
896
|
+
}
|
|
897
|
+
_getGroupKey(row) {
|
|
898
|
+
if (!this.groupBy) return null;
|
|
899
|
+
if (typeof this.groupBy === `string`) return String(row[this.groupBy] ?? ``);
|
|
900
|
+
return String(this.groupBy(row));
|
|
901
|
+
}
|
|
902
|
+
_rebuildFlat() {
|
|
903
|
+
const pred = this.filterPredicate;
|
|
904
|
+
const sourceIdx = [];
|
|
905
|
+
for (let i = 0; i < this._sortedRows.length; i++) if (!pred || pred(this._sortedRows[i])) sourceIdx.push(i);
|
|
906
|
+
const flat = [];
|
|
907
|
+
const keys = [];
|
|
908
|
+
const pushRow = (idx) => {
|
|
909
|
+
const row = this._sortedRows[idx];
|
|
910
|
+
const key = this.getRowKey(row, idx);
|
|
911
|
+
flat.push({
|
|
912
|
+
type: `row`,
|
|
913
|
+
key,
|
|
914
|
+
row,
|
|
915
|
+
idx
|
|
916
|
+
});
|
|
917
|
+
keys.push(key);
|
|
918
|
+
};
|
|
919
|
+
if (!this.groupBy) sourceIdx.forEach(pushRow);
|
|
920
|
+
else {
|
|
921
|
+
const groups = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const idx of sourceIdx) {
|
|
923
|
+
const k = this._getGroupKey(this._sortedRows[idx]) ?? ``;
|
|
924
|
+
const arr = groups.get(k);
|
|
925
|
+
if (arr) arr.push(idx);
|
|
926
|
+
else groups.set(k, [idx]);
|
|
927
|
+
}
|
|
928
|
+
for (const [gKey, idxs] of groups) {
|
|
929
|
+
const label = this.groupLabel ? this.groupLabel(gKey, idxs.map((i) => this._sortedRows[i])) : gKey;
|
|
930
|
+
const gId = `__group__${gKey}`;
|
|
931
|
+
flat.push({
|
|
932
|
+
type: `group`,
|
|
933
|
+
key: gId,
|
|
934
|
+
label,
|
|
935
|
+
count: idxs.length
|
|
936
|
+
});
|
|
937
|
+
keys.push(gId);
|
|
938
|
+
if (!this._collapsedGroups.has(gKey)) idxs.forEach(pushRow);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
this._flat = flat;
|
|
942
|
+
this._flatKeys = keys;
|
|
943
|
+
this.#rebuildFlatIndex();
|
|
944
|
+
}
|
|
945
|
+
#rebuildFlatIndex() {
|
|
946
|
+
this.#flatIndexByKey.clear();
|
|
947
|
+
this._flat.forEach((f, i) => this.#flatIndexByKey.set(f.key, i));
|
|
948
|
+
this.#flatVersion++;
|
|
949
|
+
this.#tickledItems = void 0;
|
|
950
|
+
}
|
|
951
|
+
/** Flat entry for a row key, or `undefined` for unknown keys and group headers. */
|
|
952
|
+
#rowEntry(key) {
|
|
953
|
+
const i = this.#flatIndexByKey.get(key);
|
|
954
|
+
const entry = i === void 0 ? void 0 : this._flat[i];
|
|
955
|
+
if (entry?.type === `row`) return entry;
|
|
956
|
+
const idx = this._sortedRows.findIndex((r, j) => this.getRowKey(r, j) === key);
|
|
957
|
+
return idx < 0 ? void 0 : {
|
|
958
|
+
row: this._sortedRows[idx],
|
|
959
|
+
idx
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
_toggleGroup(gKey) {
|
|
963
|
+
const next = new Set(this._collapsedGroups);
|
|
964
|
+
if (next.has(gKey)) next.delete(gKey);
|
|
965
|
+
else next.add(gKey);
|
|
966
|
+
this._collapsedGroups = next;
|
|
967
|
+
}
|
|
968
|
+
_applySort(rows) {
|
|
969
|
+
if (!this.sortState) return rows;
|
|
970
|
+
const col = this.columns.find((c) => c.key === this.sortState.key);
|
|
971
|
+
if (!col) return rows;
|
|
972
|
+
const dir = this.sortState.direction === `asc` ? 1 : -1;
|
|
973
|
+
if (col.sortFn) return rows.sort((a, b) => dir * col.sortFn(a, b));
|
|
974
|
+
const dataType = col.dataType ?? `text`;
|
|
975
|
+
return rows.sort((a, b) => {
|
|
976
|
+
const av = a[col.key];
|
|
977
|
+
const bv = b[col.key];
|
|
978
|
+
if (av === bv) return 0;
|
|
979
|
+
if (dataType === `number`) return dir * (av - bv);
|
|
980
|
+
if (dataType === `boolean`) return dir * (Boolean(av) === Boolean(bv) ? 0 : av ? 1 : -1);
|
|
981
|
+
return dir * String(av).localeCompare(String(bv));
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
_onHeaderClick(col, e) {
|
|
985
|
+
if (col.sortable === false) return;
|
|
986
|
+
if (e) {
|
|
987
|
+
const pointerEvent = e.composedPath()[0];
|
|
988
|
+
if (pointerEvent && this.#pointerEventSuppressess.get(pointerEvent)) {
|
|
989
|
+
this.#pointerEventSuppressess.delete(pointerEvent);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const prev = this.sortState;
|
|
994
|
+
let next = null;
|
|
995
|
+
if (!prev || prev.key !== col.key) next = {
|
|
996
|
+
key: col.key,
|
|
997
|
+
direction: `asc`
|
|
998
|
+
};
|
|
999
|
+
else if (prev.direction === `asc`) next = {
|
|
1000
|
+
key: col.key,
|
|
1001
|
+
direction: `desc`
|
|
1002
|
+
};
|
|
1003
|
+
else next = null;
|
|
1004
|
+
this.sortState = next;
|
|
1005
|
+
this.dispatchEvent(new CustomEvent(`datagrid-sort`, {
|
|
1006
|
+
detail: {
|
|
1007
|
+
sort: next,
|
|
1008
|
+
previous: prev
|
|
1009
|
+
},
|
|
1010
|
+
bubbles: true,
|
|
1011
|
+
composed: true
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
1014
|
+
_onHeaderPointerUp(col, e) {
|
|
1015
|
+
let stopped = false;
|
|
1016
|
+
const customEvent = new CustomEvent(`datagrid-header-pointerup`, {
|
|
1017
|
+
detail: {
|
|
1018
|
+
key: col.key,
|
|
1019
|
+
column: col
|
|
1020
|
+
},
|
|
1021
|
+
bubbles: true,
|
|
1022
|
+
composed: true,
|
|
1023
|
+
cancelable: true
|
|
1024
|
+
});
|
|
1025
|
+
this.dispatchEvent(customEvent);
|
|
1026
|
+
if (customEvent.defaultPrevented) stopped = true;
|
|
1027
|
+
if (stopped) this.#pointerEventSuppressess.set(e, true);
|
|
1028
|
+
}
|
|
1029
|
+
_onColButtonClick(col, e) {
|
|
1030
|
+
e.stopPropagation();
|
|
1031
|
+
const icon = col.button === false ? this.columnButtonIcon : typeof col.button === `object` && col.button.icon ? col.button.icon : this.columnButtonIcon;
|
|
1032
|
+
const target = e.currentTarget;
|
|
1033
|
+
const customEvent = new CustomEvent(`datagrid-header-button-click`, {
|
|
1034
|
+
detail: {
|
|
1035
|
+
key: col.key,
|
|
1036
|
+
column: col,
|
|
1037
|
+
icon,
|
|
1038
|
+
clientX: e.clientX,
|
|
1039
|
+
clientY: e.clientY,
|
|
1040
|
+
anchorElement: target
|
|
1041
|
+
},
|
|
1042
|
+
bubbles: true,
|
|
1043
|
+
composed: true,
|
|
1044
|
+
cancelable: true
|
|
1045
|
+
});
|
|
1046
|
+
this.dispatchEvent(customEvent);
|
|
1047
|
+
if (customEvent.defaultPrevented) this.#pointerEventSuppressess.set(e, true);
|
|
1048
|
+
}
|
|
1049
|
+
_onRowClick(rowKey, col, e) {
|
|
1050
|
+
if (this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
|
|
1051
|
+
if (col && col.selectable === false) return;
|
|
1052
|
+
if (this.selectionMode === `none` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) return;
|
|
1053
|
+
if (this.interactionMode === `manual`) return;
|
|
1054
|
+
if (this.interactionMode === `implicit`) {
|
|
1055
|
+
this._replaceSelection(rowKey);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (this.interactionMode === `checked`) return;
|
|
1059
|
+
if (this.interactionMode === `sticky`) {
|
|
1060
|
+
this._toggleSelection(rowKey);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1064
|
+
if (this.selectionMode === `multiple` || this.interactionMode === `vscode`) this._toggleSelection(rowKey);
|
|
1065
|
+
} else if (e.shiftKey && (this.selectionMode === `multiple` || this.interactionMode === `vscode`)) this._rangeSelectTo(rowKey);
|
|
1066
|
+
else this._replaceSelection(rowKey);
|
|
1067
|
+
}
|
|
1068
|
+
_onCheckboxClick(rowKey, e) {
|
|
1069
|
+
e.stopPropagation();
|
|
1070
|
+
this._onRowClick(rowKey, null, e);
|
|
1071
|
+
}
|
|
1072
|
+
_onVisibilityToggle(col, checked) {
|
|
1073
|
+
col.visible = checked;
|
|
1074
|
+
this.columns = [...this.columns];
|
|
1075
|
+
this.dispatchEvent(new CustomEvent(`datagrid-column-visibility`, {
|
|
1076
|
+
detail: {
|
|
1077
|
+
key: col.key,
|
|
1078
|
+
visible: checked
|
|
1079
|
+
},
|
|
1080
|
+
bubbles: true,
|
|
1081
|
+
composed: true
|
|
1082
|
+
}));
|
|
1083
|
+
}
|
|
1084
|
+
_beginHeaderDrag(e, col, idx) {
|
|
1085
|
+
e.stopPropagation();
|
|
1086
|
+
const anchor = e.currentTarget.closest(`.header-cell`) ?? e.currentTarget;
|
|
1087
|
+
this._drag = {
|
|
1088
|
+
pointerId: e.pointerId,
|
|
1089
|
+
from: idx,
|
|
1090
|
+
startX: e.clientX,
|
|
1091
|
+
startY: e.clientY,
|
|
1092
|
+
active: false,
|
|
1093
|
+
ghost: null,
|
|
1094
|
+
gap: void 0,
|
|
1095
|
+
sourceCol: col
|
|
1096
|
+
};
|
|
1097
|
+
const onMove = (ev) => {
|
|
1098
|
+
if (!this._drag || ev.pointerId !== this._drag.pointerId) return;
|
|
1099
|
+
const dx = ev.clientX - this._drag.startX;
|
|
1100
|
+
const dy = ev.clientY - this._drag.startY;
|
|
1101
|
+
if (!this._drag.active) {
|
|
1102
|
+
if (Math.hypot(dx, dy) < 5) return;
|
|
1103
|
+
this._drag.active = true;
|
|
1104
|
+
const content = document.createElement(`span`);
|
|
1105
|
+
content.textContent = col.label;
|
|
1106
|
+
try {
|
|
1107
|
+
this._drag.ghost = createDragGhost({
|
|
1108
|
+
anchor,
|
|
1109
|
+
content
|
|
1110
|
+
});
|
|
1111
|
+
this._drag.ghost.move(ev.clientX, ev.clientY);
|
|
1112
|
+
} catch {}
|
|
1113
|
+
try {
|
|
1114
|
+
this._headerRowEl?.setPointerCapture(ev.pointerId);
|
|
1115
|
+
} catch {}
|
|
1116
|
+
}
|
|
1117
|
+
this._drag.ghost?.move(ev.clientX, ev.clientY);
|
|
1118
|
+
const x = ev.clientX - (this._headerRowEl?.getBoundingClientRect().left ?? 0);
|
|
1119
|
+
let gap = this._drag.from;
|
|
1120
|
+
let bestDist = Infinity;
|
|
1121
|
+
for (let i = 0; i < this._visibleColumns.length; i++) {
|
|
1122
|
+
if (i === this._drag.from) continue;
|
|
1123
|
+
const rectLeft = this._colOffsets[i];
|
|
1124
|
+
const rectRight = rectLeft + (this._visibleColumns[i].width ?? 150);
|
|
1125
|
+
const mid = (rectLeft + rectRight) / 2;
|
|
1126
|
+
const gapPos = x < mid ? rectLeft : rectRight;
|
|
1127
|
+
const gapIdx = x < mid ? i : i + 1;
|
|
1128
|
+
const d = Math.abs(x - gapPos);
|
|
1129
|
+
if (d < bestDist) {
|
|
1130
|
+
bestDist = d;
|
|
1131
|
+
gap = gapIdx > this._drag.from ? gapIdx - 1 : gapIdx;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (this._visibleColumns.length > 0) {
|
|
1135
|
+
const lastOff = this._colOffsets[this._colOffsets.length - 1] + (this._visibleColumns[this._visibleColumns.length - 1].width ?? 150);
|
|
1136
|
+
const d = Math.abs(x - lastOff);
|
|
1137
|
+
if (d < bestDist) {
|
|
1138
|
+
bestDist = d;
|
|
1139
|
+
gap = this._visibleColumns.length - 1;
|
|
1140
|
+
if (this._drag.from === this._visibleColumns.length - 1) gap = this._drag.from;
|
|
1141
|
+
else if (gap >= this._drag.from) gap = this._visibleColumns.length - 1;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const siblings = this._visibleColumns.length - 1;
|
|
1145
|
+
let normalizedGap = gap;
|
|
1146
|
+
if (normalizedGap < 0) normalizedGap = 0;
|
|
1147
|
+
if (normalizedGap > siblings) normalizedGap = siblings;
|
|
1148
|
+
this._drag.gap = normalizedGap;
|
|
1149
|
+
this.requestUpdate();
|
|
1150
|
+
};
|
|
1151
|
+
const onUp = (ev) => {
|
|
1152
|
+
if (!this._drag || ev.pointerId !== this._drag.pointerId) return;
|
|
1153
|
+
const active = this._drag.active;
|
|
1154
|
+
const from = this._drag.from;
|
|
1155
|
+
const gap = this._drag.gap;
|
|
1156
|
+
this._drag.ghost?.destroy();
|
|
1157
|
+
try {
|
|
1158
|
+
this._headerRowEl?.releasePointerCapture(ev.pointerId);
|
|
1159
|
+
} catch {}
|
|
1160
|
+
window.removeEventListener(`pointermove`, onMove);
|
|
1161
|
+
window.removeEventListener(`pointerup`, onUp);
|
|
1162
|
+
window.removeEventListener(`pointercancel`, onUp);
|
|
1163
|
+
this._drag = null;
|
|
1164
|
+
this.requestUpdate();
|
|
1165
|
+
if (active && gap !== void 0 && gap !== from) this._onReorderDrag(from, gap);
|
|
1166
|
+
};
|
|
1167
|
+
window.addEventListener(`pointermove`, onMove);
|
|
1168
|
+
window.addEventListener(`pointerup`, onUp);
|
|
1169
|
+
window.addEventListener(`pointercancel`, onUp);
|
|
1170
|
+
}
|
|
1171
|
+
_onReorderDrag(fromIdx, toIdx) {
|
|
1172
|
+
if (fromIdx === toIdx) return;
|
|
1173
|
+
const cols = [...this.columns];
|
|
1174
|
+
const vis = cols.filter((c) => c.visible !== false);
|
|
1175
|
+
const hidden = cols.filter((c) => c.visible === false);
|
|
1176
|
+
const [moved] = vis.splice(fromIdx, 1);
|
|
1177
|
+
vis.splice(toIdx, 0, moved);
|
|
1178
|
+
this.columns = [...vis, ...hidden];
|
|
1179
|
+
this.dispatchEvent(new CustomEvent(`datagrid-column-reorder`, {
|
|
1180
|
+
detail: {
|
|
1181
|
+
from: fromIdx,
|
|
1182
|
+
to: toIdx,
|
|
1183
|
+
columns: [...this.columns]
|
|
1184
|
+
},
|
|
1185
|
+
bubbles: true,
|
|
1186
|
+
composed: true
|
|
1187
|
+
}));
|
|
1188
|
+
}
|
|
1189
|
+
_scrollRowIntoView(key) {
|
|
1190
|
+
const idx = this.#flatIndexByKey.get(key);
|
|
1191
|
+
if (idx === void 0 || !this._viewportEl) return;
|
|
1192
|
+
const rh = this.rowHeight;
|
|
1193
|
+
const top = (idx > 0 && this._flat[idx - 1].type === `group` ? idx - 1 : idx) * rh;
|
|
1194
|
+
const bottom = (idx + 1) * rh;
|
|
1195
|
+
const vt = this._viewportEl.scrollTop;
|
|
1196
|
+
const vh = this.#bodyHeight();
|
|
1197
|
+
let next = vt;
|
|
1198
|
+
if (top < vt) next = top;
|
|
1199
|
+
else if (bottom > vt + vh) next = bottom - vh;
|
|
1200
|
+
if (next !== vt) {
|
|
1201
|
+
this._viewportEl.scrollTop = next;
|
|
1202
|
+
this._scrollTop = next;
|
|
1203
|
+
this.requestUpdate();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
_getRowForKey(key) {
|
|
1207
|
+
const i = this.#flatIndexByKey.get(key);
|
|
1208
|
+
const entry = i === void 0 ? void 0 : this._flat[i];
|
|
1209
|
+
return entry?.type === `row` ? entry.row : void 0;
|
|
1210
|
+
}
|
|
1211
|
+
/** The selected row keys (read-only). */
|
|
1212
|
+
get selectedKeys() {
|
|
1213
|
+
return this._selectedKeys;
|
|
1214
|
+
}
|
|
1215
|
+
/** Replace the selection with these row keys (clamped to known rows). */
|
|
1216
|
+
set selectedKeys(keys) {
|
|
1217
|
+
this.selectKeys(keys);
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Replace the selection with the given row keys. Unknown keys are dropped;
|
|
1221
|
+
* in a single-select mode only the last surviving key is kept;
|
|
1222
|
+
* `selectionMode="none"` clears. Fires `datagrid-select` with the previous
|
|
1223
|
+
* set when the selection actually changes.
|
|
1224
|
+
*/
|
|
1225
|
+
selectKeys(keys) {
|
|
1226
|
+
let next = new Set([...keys].filter((k) => this._getRowForKey(k) !== void 0));
|
|
1227
|
+
const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
|
|
1228
|
+
if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
|
|
1229
|
+
else if (!multi && next.size > 1) {
|
|
1230
|
+
const last = [...next].at(-1);
|
|
1231
|
+
next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
|
|
1232
|
+
}
|
|
1233
|
+
const prev = new Set(this._selectedKeys);
|
|
1234
|
+
if (prev.size === next.size && [...next].every((k) => prev.has(k))) return;
|
|
1235
|
+
this._selectedKeys = next;
|
|
1236
|
+
this._anchorKey = [...next].at(-1);
|
|
1237
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1238
|
+
detail: {
|
|
1239
|
+
selectedKeys: new Set(next),
|
|
1240
|
+
previous: prev
|
|
1241
|
+
},
|
|
1242
|
+
bubbles: true,
|
|
1243
|
+
composed: true
|
|
1244
|
+
}));
|
|
1245
|
+
}
|
|
1246
|
+
/** The row key under the cursor, or `undefined`. */
|
|
1247
|
+
get tickledKey() {
|
|
1248
|
+
return this._tickled.tickledId;
|
|
1249
|
+
}
|
|
1250
|
+
/** Move the cursor to this row key and scroll it into view. */
|
|
1251
|
+
tickleRow(key) {
|
|
1252
|
+
if (!this.#flatIndexByKey.has(key) || key.startsWith(`__group__`)) return;
|
|
1253
|
+
this._tickled.focus(key);
|
|
1254
|
+
this._scrollRowIntoView(key);
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Append rows without the full O(n log n) rebuild that reassigning `rows`
|
|
1258
|
+
* incurs. `rows` still gets a new array reference (reactivity is preserved),
|
|
1259
|
+
* but the derived arrays are extended over the appended slice only. When a
|
|
1260
|
+
* sort is active the new rows show unsorted for one frame, then a single
|
|
1261
|
+
* coalesced re-sort runs. Scroll position, selection and cursor are kept.
|
|
1262
|
+
*/
|
|
1263
|
+
appendRows(rows) {
|
|
1264
|
+
if (rows.length === 0) return;
|
|
1265
|
+
const startIdx = this.rows.length;
|
|
1266
|
+
const inSync = this._sortedRows.length === startIdx && sameRefs(this.#sortInputs, [
|
|
1267
|
+
this.rows,
|
|
1268
|
+
this.sortState,
|
|
1269
|
+
this.columns
|
|
1270
|
+
]);
|
|
1271
|
+
this.rows = [...this.rows, ...rows];
|
|
1272
|
+
if (!inSync) return;
|
|
1273
|
+
const appended = this.rows.slice(startIdx);
|
|
1274
|
+
this._sortedRows = [...this._sortedRows, ...appended];
|
|
1275
|
+
this.#appendToFlat(appended, startIdx);
|
|
1276
|
+
this.#markDerivedRowsSynced();
|
|
1277
|
+
if (this.sortState != null) {
|
|
1278
|
+
cancelAnimationFrame(this.#resortRaf);
|
|
1279
|
+
this.#resortRaf = requestAnimationFrame(() => {
|
|
1280
|
+
this.#sortInputs = void 0;
|
|
1281
|
+
this.requestUpdate();
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
#appendToFlat(appended, startIdx) {
|
|
1286
|
+
if (this.groupBy) {
|
|
1287
|
+
this._rebuildFlat();
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
for (let j = 0; j < appended.length; j++) {
|
|
1291
|
+
const idx = startIdx + j;
|
|
1292
|
+
if (this.filterPredicate && !this.filterPredicate(appended[j])) continue;
|
|
1293
|
+
const key = this.getRowKey(appended[j], idx);
|
|
1294
|
+
this._flat.push({
|
|
1295
|
+
type: `row`,
|
|
1296
|
+
key,
|
|
1297
|
+
row: appended[j],
|
|
1298
|
+
idx
|
|
1299
|
+
});
|
|
1300
|
+
this._flatKeys.push(key);
|
|
1301
|
+
this.#flatIndexByKey.set(key, this._flat.length - 1);
|
|
1302
|
+
}
|
|
1303
|
+
this.#flatVersion++;
|
|
1304
|
+
this.#tickledItems = void 0;
|
|
1305
|
+
}
|
|
1306
|
+
_replaceSelection(key) {
|
|
1307
|
+
if (this.selectionMode === `none`) return;
|
|
1308
|
+
const prev = new Set(this._selectedKeys);
|
|
1309
|
+
if (this.groupBy && !this.crossGroupSelection) {
|
|
1310
|
+
const g = this._getGroupKey(this._getRowForKey(key) ?? { key });
|
|
1311
|
+
[...prev].filter((k) => this._getGroupKey(this._getRowForKey(k) ?? { key: k }) === g);
|
|
1312
|
+
this._selectedKeys = /* @__PURE__ */ new Set([key]);
|
|
1313
|
+
} else this._selectedKeys = /* @__PURE__ */ new Set([key]);
|
|
1314
|
+
this._anchorKey = key;
|
|
1315
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1316
|
+
detail: {
|
|
1317
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
1318
|
+
previous: prev
|
|
1319
|
+
},
|
|
1320
|
+
bubbles: true,
|
|
1321
|
+
composed: true
|
|
1322
|
+
}));
|
|
1323
|
+
}
|
|
1324
|
+
_toggleSelection(key) {
|
|
1325
|
+
const prev = new Set(this._selectedKeys);
|
|
1326
|
+
const next = new Set(this._selectedKeys);
|
|
1327
|
+
if (next.has(key)) next.delete(key);
|
|
1328
|
+
else {
|
|
1329
|
+
if (this.groupBy && !this.crossGroupSelection) {
|
|
1330
|
+
const g = this._getGroupKey(this._getRowForKey(key) ?? { key });
|
|
1331
|
+
for (const k of [...next]) if (this._getGroupKey(this._getRowForKey(k) ?? { key: k }) !== g) next.delete(k);
|
|
1332
|
+
}
|
|
1333
|
+
if (this.selectionMode !== `multiple` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) next.clear();
|
|
1334
|
+
next.add(key);
|
|
1335
|
+
}
|
|
1336
|
+
this._selectedKeys = next;
|
|
1337
|
+
this._anchorKey = key;
|
|
1338
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1339
|
+
detail: {
|
|
1340
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
1341
|
+
previous: prev
|
|
1342
|
+
},
|
|
1343
|
+
bubbles: true,
|
|
1344
|
+
composed: true
|
|
1345
|
+
}));
|
|
1346
|
+
}
|
|
1347
|
+
_rangeSelectTo(key) {
|
|
1348
|
+
if (!this._anchorKey) {
|
|
1349
|
+
this._replaceSelection(key);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const keys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
|
|
1353
|
+
const a = keys.indexOf(this._anchorKey);
|
|
1354
|
+
const b = keys.indexOf(key);
|
|
1355
|
+
if (a < 0 || b < 0) {
|
|
1356
|
+
this._replaceSelection(key);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
if (this.groupBy && !this.crossGroupSelection) {
|
|
1360
|
+
const anchorGroup = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
|
|
1361
|
+
if (anchorGroup !== this._getGroupKey(this._getRowForKey(key) ?? { key })) {
|
|
1362
|
+
this._replaceSelection(key);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const groupKeys = keys.filter((k) => this._getGroupKey(this._getRowForKey(k) ?? { key: k }) === anchorGroup);
|
|
1366
|
+
const ga = groupKeys.indexOf(this._anchorKey);
|
|
1367
|
+
const gb = groupKeys.indexOf(key);
|
|
1368
|
+
if (ga < 0 || gb < 0) {
|
|
1369
|
+
this._replaceSelection(key);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const lo = Math.min(ga, gb);
|
|
1373
|
+
const hi = Math.max(ga, gb);
|
|
1374
|
+
const prev = new Set(this._selectedKeys);
|
|
1375
|
+
this._selectedKeys = new Set(groupKeys.slice(lo, hi + 1));
|
|
1376
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1377
|
+
detail: {
|
|
1378
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
1379
|
+
previous: prev
|
|
1380
|
+
},
|
|
1381
|
+
bubbles: true,
|
|
1382
|
+
composed: true
|
|
1383
|
+
}));
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const lo = Math.min(a, b);
|
|
1387
|
+
const hi = Math.max(a, b);
|
|
1388
|
+
const prev = new Set(this._selectedKeys);
|
|
1389
|
+
this._selectedKeys = new Set(keys.slice(lo, hi + 1));
|
|
1390
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1391
|
+
detail: {
|
|
1392
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
1393
|
+
previous: prev
|
|
1394
|
+
},
|
|
1395
|
+
bubbles: true,
|
|
1396
|
+
composed: true
|
|
1397
|
+
}));
|
|
1398
|
+
}
|
|
1399
|
+
_isEditable(row, col) {
|
|
1400
|
+
if (this.editability === `disabled`) return false;
|
|
1401
|
+
if (typeof this.editable === `function`) return this.editable(row, col);
|
|
1402
|
+
if (this.editable === true) return col.editable !== false;
|
|
1403
|
+
return !!col.editable;
|
|
1404
|
+
}
|
|
1405
|
+
_isTextEditor(col) {
|
|
1406
|
+
const e = col.editor;
|
|
1407
|
+
return !e || e === `auto`;
|
|
1408
|
+
}
|
|
1409
|
+
_isCellEditing(rowKey, colKey) {
|
|
1410
|
+
if (this.editability === `immediate` && this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey))) return true;
|
|
1411
|
+
if (this._editingRow === rowKey) return this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey));
|
|
1412
|
+
return this._editing?.rowKey === rowKey && this._editing?.colKey === colKey;
|
|
1413
|
+
}
|
|
1414
|
+
_activateRow(rowKey, row, rowIndex) {
|
|
1415
|
+
this.dispatchEvent(new CustomEvent(`datagrid-row-activate`, {
|
|
1416
|
+
detail: {
|
|
1417
|
+
rowKey,
|
|
1418
|
+
row,
|
|
1419
|
+
rowIndex
|
|
1420
|
+
},
|
|
1421
|
+
bubbles: true,
|
|
1422
|
+
composed: true
|
|
1423
|
+
}));
|
|
1424
|
+
}
|
|
1425
|
+
_startEdit(rowKey, colKey) {
|
|
1426
|
+
if (this.editability === `disabled`) return;
|
|
1427
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
1428
|
+
if (!row) return;
|
|
1429
|
+
const col = this.columns.find((c) => c.key === colKey);
|
|
1430
|
+
if (!col || !this._isEditable(row, col)) return;
|
|
1431
|
+
this._editing = {
|
|
1432
|
+
rowKey,
|
|
1433
|
+
colKey
|
|
1434
|
+
};
|
|
1435
|
+
this._editingRow = null;
|
|
1436
|
+
this._editValue = row[colKey];
|
|
1437
|
+
}
|
|
1438
|
+
/** Programmatic: put a single cell in edit mode. Works in manual/user/immediate (no-op if disabled or not editable). */
|
|
1439
|
+
startEdit(rowKey, colKey) {
|
|
1440
|
+
this._startEdit(rowKey, colKey);
|
|
1441
|
+
}
|
|
1442
|
+
/** Programmatic: put all editable cells of a row in edit mode. */
|
|
1443
|
+
startRowEdit(rowKey) {
|
|
1444
|
+
if (this.editability === `disabled`) return;
|
|
1445
|
+
if (!this.#rowEntry(rowKey)?.row) return;
|
|
1446
|
+
this._editing = null;
|
|
1447
|
+
this._editingRow = rowKey;
|
|
1448
|
+
this.requestUpdate();
|
|
1449
|
+
}
|
|
1450
|
+
stopEdit() {
|
|
1451
|
+
this._editing = null;
|
|
1452
|
+
this._editingRow = null;
|
|
1453
|
+
}
|
|
1454
|
+
setColumnFormatter(columnKey, formatter, options) {
|
|
1455
|
+
if (!formatter) {
|
|
1456
|
+
this._columnFormatters.delete(columnKey);
|
|
1457
|
+
this.requestUpdate();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
if (typeof formatter === `string`) {
|
|
1461
|
+
const digits = options?.digits ?? (formatter === `integer` ? 0 : 2);
|
|
1462
|
+
if (formatter === `fixed`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? v.toFixed(digits) : String(v ?? ``));
|
|
1463
|
+
else if (formatter === `percentage`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? `${v.toFixed(digits)}%` : String(v ?? ``));
|
|
1464
|
+
else if (formatter === `integer`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? Math.round(v).toString() : String(v ?? ``));
|
|
1465
|
+
} else this._columnFormatters.set(columnKey, formatter);
|
|
1466
|
+
this.requestUpdate();
|
|
1467
|
+
}
|
|
1468
|
+
_formatValue(value, col, row) {
|
|
1469
|
+
const perCol = this._columnFormatters.get(col.key);
|
|
1470
|
+
if (perCol) return perCol(value);
|
|
1471
|
+
if (col.formatter) return col.formatter(value, row, col);
|
|
1472
|
+
if (this.globalFormatter) return this.globalFormatter(value, col.key, row);
|
|
1473
|
+
return value;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Resolve the per-row appearance for the cell at `colKey`, honouring the
|
|
1477
|
+
* nominated columns. Additive — `col.renderer` / `col.formatter` still own
|
|
1478
|
+
* the cell's value; this only decorates it. Returns all-`undefined` for
|
|
1479
|
+
* every cell when nothing is nominated (the common case).
|
|
1480
|
+
*/
|
|
1481
|
+
_cellLook(row, colKey) {
|
|
1482
|
+
if (this.textColorColumn === `` && this.iconColumn === `` && this.badgeColumn === ``) return EMPTY_CELL_LOOK;
|
|
1483
|
+
const rec = typeof row === `object` && row !== null ? row : {};
|
|
1484
|
+
const textColor = colKey === this.textColorColumn ? appearanceColor(rec.textColor) : void 0;
|
|
1485
|
+
const icon = colKey === this.iconColumn && !appearanceAbsent(rec.icon) ? rec.icon : void 0;
|
|
1486
|
+
const iconColor = icon === void 0 ? void 0 : appearanceColor(rec.iconColor);
|
|
1487
|
+
const badge = colKey === this.badgeColumn && !appearanceAbsent(rec.badge) ? rec.badge : void 0;
|
|
1488
|
+
if (textColor === void 0 && icon === void 0 && badge === void 0) return EMPTY_CELL_LOOK;
|
|
1489
|
+
return {
|
|
1490
|
+
textColor,
|
|
1491
|
+
icon,
|
|
1492
|
+
iconColor,
|
|
1493
|
+
badge
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
_handleCellSecondClick(rowKey, col) {
|
|
1497
|
+
if (this.editability !== `user`) return;
|
|
1498
|
+
const now = Date.now();
|
|
1499
|
+
if (this._lastClick && this._lastClick.key === rowKey && this._lastClick.col === col.key && now - this._lastClick.time < 700 && now - this._lastClick.time > 150) {
|
|
1500
|
+
this._startEdit(rowKey, col.key);
|
|
1501
|
+
this._lastClick = null;
|
|
1502
|
+
} else this._lastClick = {
|
|
1503
|
+
key: rowKey,
|
|
1504
|
+
col: col.key,
|
|
1505
|
+
time: now
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
_commitEdit(fromImmediate = false) {
|
|
1509
|
+
if (!this._editing && !fromImmediate) return;
|
|
1510
|
+
const target = this._editing;
|
|
1511
|
+
if (!target) return;
|
|
1512
|
+
const { rowKey, colKey } = target;
|
|
1513
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
1514
|
+
if (!row) {
|
|
1515
|
+
this._editing = null;
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const oldValue = row[colKey];
|
|
1519
|
+
const newValue = this._editValue;
|
|
1520
|
+
const col = this.columns.find((c) => c.key === colKey);
|
|
1521
|
+
if (col?.editor && typeof col.editor === `function`) {} else {
|
|
1522
|
+
row[colKey] = newValue;
|
|
1523
|
+
this.rows = [...this.rows];
|
|
1524
|
+
}
|
|
1525
|
+
this._editing = null;
|
|
1526
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-edit`, {
|
|
1527
|
+
detail: {
|
|
1528
|
+
rowKey,
|
|
1529
|
+
colKey,
|
|
1530
|
+
oldValue,
|
|
1531
|
+
newValue
|
|
1532
|
+
},
|
|
1533
|
+
bubbles: true,
|
|
1534
|
+
composed: true
|
|
1535
|
+
}));
|
|
1536
|
+
}
|
|
1537
|
+
_tabToNext(rowKey, curColKey, dir) {
|
|
1538
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
1539
|
+
if (!row) return;
|
|
1540
|
+
const cols = this._visibleColumns;
|
|
1541
|
+
let next = cols.findIndex((c) => c.key === curColKey) + dir;
|
|
1542
|
+
while (next >= 0 && next < cols.length) {
|
|
1543
|
+
if (this._isEditable(row, cols[next])) {
|
|
1544
|
+
if (this._editing) this._commitEdit();
|
|
1545
|
+
this._suppressBlurCommit = true;
|
|
1546
|
+
this._startEdit(rowKey, cols[next].key);
|
|
1547
|
+
requestAnimationFrame(() => {
|
|
1548
|
+
this._suppressBlurCommit = false;
|
|
1549
|
+
});
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
next += dir;
|
|
1553
|
+
}
|
|
1554
|
+
if (this._editing) this._commitEdit();
|
|
1555
|
+
this._editing = null;
|
|
1556
|
+
this.requestUpdate();
|
|
1557
|
+
}
|
|
1558
|
+
_commitImmediate(rowKey, colKey, value) {
|
|
1559
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
1560
|
+
if (!row) return;
|
|
1561
|
+
const oldValue = row[colKey];
|
|
1562
|
+
if (oldValue === value) return;
|
|
1563
|
+
row[colKey] = value;
|
|
1564
|
+
this.rows = [...this.rows];
|
|
1565
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-edit`, {
|
|
1566
|
+
detail: {
|
|
1567
|
+
rowKey,
|
|
1568
|
+
colKey,
|
|
1569
|
+
oldValue,
|
|
1570
|
+
newValue: value
|
|
1571
|
+
},
|
|
1572
|
+
bubbles: true,
|
|
1573
|
+
composed: true
|
|
1574
|
+
}));
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Measure the width a column would need to show its header label and every
|
|
1578
|
+
* row's (formatted) value without truncation, clamped to `minWidth`/`maxWidth`.
|
|
1579
|
+
* Values are measured from their formatted string via canvas `measureText` -
|
|
1580
|
+
* accurate for plain text, an estimate for anything else.
|
|
1581
|
+
*/
|
|
1582
|
+
_measureAutoWidth(col) {
|
|
1583
|
+
const cs = getComputedStyle(this);
|
|
1584
|
+
const font = `${cs.fontSize} ${cs.fontFamily}`;
|
|
1585
|
+
const ctx = document.createElement(`canvas`).getContext(`2d`);
|
|
1586
|
+
const cellPad = 24;
|
|
1587
|
+
const headerExtra = 40;
|
|
1588
|
+
const measure = (text) => {
|
|
1589
|
+
if (!ctx) return text.length * 8;
|
|
1590
|
+
ctx.font = font;
|
|
1591
|
+
return Math.ceil(ctx.measureText(text).width);
|
|
1592
|
+
};
|
|
1593
|
+
let maxW = measure(col.label) + headerExtra;
|
|
1594
|
+
for (const r of this.rows) {
|
|
1595
|
+
const v = r[col.key];
|
|
1596
|
+
if (v === null || v === void 0 || v === ``) continue;
|
|
1597
|
+
const w = measure(String(this._formatValue(v, col, r))) + cellPad;
|
|
1598
|
+
if (w > maxW) maxW = w;
|
|
1599
|
+
}
|
|
1600
|
+
const min = col.minWidth ?? 40;
|
|
1601
|
+
const max = col.maxWidth ?? 600;
|
|
1602
|
+
return Math.max(min, Math.min(max, maxW));
|
|
1603
|
+
}
|
|
1604
|
+
_autoSizeColumn(col) {
|
|
1605
|
+
const w = this._measureAutoWidth(col);
|
|
1606
|
+
col.width = w;
|
|
1607
|
+
this.requestUpdate();
|
|
1608
|
+
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1609
|
+
detail: {
|
|
1610
|
+
key: col.key,
|
|
1611
|
+
width: w
|
|
1612
|
+
},
|
|
1613
|
+
bubbles: true,
|
|
1614
|
+
composed: true
|
|
1615
|
+
}));
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Resize a single column, by key, to fit its contents (header label + every
|
|
1619
|
+
* row's formatted value), animating the width change smoothly. Only works
|
|
1620
|
+
* for plain columns - one with a custom `renderer` can't be measured
|
|
1621
|
+
* reliably from its raw value, so it's left untouched. Returns `false` (and
|
|
1622
|
+
* does nothing) if the column doesn't exist, isn't resizable, or has a
|
|
1623
|
+
* `renderer`.
|
|
1624
|
+
*/
|
|
1625
|
+
autoSizeColumn(key) {
|
|
1626
|
+
const col = this.columns.find((c) => c.key === key);
|
|
1627
|
+
if (!col || col.resizable === false || col.renderer) return false;
|
|
1628
|
+
const target = this._measureAutoWidth(col);
|
|
1629
|
+
if ((col.width ?? 150) === target) return true;
|
|
1630
|
+
this._animatingColKeys = new Set(this._animatingColKeys).add(key);
|
|
1631
|
+
requestAnimationFrame(() => {
|
|
1632
|
+
col.width = target;
|
|
1633
|
+
this.requestUpdate();
|
|
1634
|
+
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1635
|
+
detail: {
|
|
1636
|
+
key,
|
|
1637
|
+
width: target
|
|
1638
|
+
},
|
|
1639
|
+
bubbles: true,
|
|
1640
|
+
composed: true
|
|
1641
|
+
}));
|
|
1642
|
+
window.setTimeout(() => {
|
|
1643
|
+
const next = new Set(this._animatingColKeys);
|
|
1644
|
+
next.delete(key);
|
|
1645
|
+
this._animatingColKeys = next;
|
|
1646
|
+
}, this._columnResizeAnimationMs());
|
|
1647
|
+
});
|
|
1648
|
+
return true;
|
|
1649
|
+
}
|
|
1650
|
+
_columnResizeAnimationMs() {
|
|
1651
|
+
const raw = getComputedStyle(this).getPropertyValue(`--data-grid-column-resize-duration`).trim();
|
|
1652
|
+
const ms = raw.endsWith(`ms`) ? Number.parseFloat(raw) : raw.endsWith(`s`) ? Number.parseFloat(raw) * 1e3 : Number.parseFloat(raw);
|
|
1653
|
+
return (Number.isFinite(ms) && ms > 0 ? ms : 180) + 30;
|
|
1654
|
+
}
|
|
1655
|
+
_onResizePointerDown(e, col) {
|
|
1656
|
+
e.preventDefault();
|
|
1657
|
+
e.stopPropagation();
|
|
1658
|
+
const startX = e.clientX;
|
|
1659
|
+
const startW = col.width ?? 150;
|
|
1660
|
+
this._resizingColKey = col.key;
|
|
1661
|
+
const onMove = (ev) => {
|
|
1662
|
+
col.width = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
|
|
1663
|
+
this.requestUpdate();
|
|
1664
|
+
};
|
|
1665
|
+
const onUp = (ev) => {
|
|
1666
|
+
window.removeEventListener(`pointermove`, onMove);
|
|
1667
|
+
window.removeEventListener(`pointerup`, onUp);
|
|
1668
|
+
this._resizingColKey = void 0;
|
|
1669
|
+
const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
|
|
1670
|
+
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1671
|
+
detail: {
|
|
1672
|
+
key: col.key,
|
|
1673
|
+
width: w
|
|
1674
|
+
},
|
|
1675
|
+
bubbles: true,
|
|
1676
|
+
composed: true
|
|
1677
|
+
}));
|
|
1678
|
+
};
|
|
1679
|
+
window.addEventListener(`pointermove`, onMove);
|
|
1680
|
+
window.addEventListener(`pointerup`, onUp);
|
|
1681
|
+
}
|
|
1682
|
+
/** Height of the row area: the viewport minus the sticky header row inside it. */
|
|
1683
|
+
#bodyHeight() {
|
|
1684
|
+
return this._viewportEl.clientHeight - (this._headerRowEl?.offsetHeight ?? 0);
|
|
1685
|
+
}
|
|
1686
|
+
/** Flat-index window `[start, end)` intersecting the viewport, widened by `overscan` rows each side. */
|
|
1687
|
+
#windowRows(overscan) {
|
|
1688
|
+
const rh = this.rowHeight;
|
|
1689
|
+
return {
|
|
1690
|
+
start: Math.max(0, Math.floor(this._scrollTop / rh) - overscan),
|
|
1691
|
+
end: Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + overscan)
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
getVisibleRange() {
|
|
1695
|
+
const { start: startRow, end: endRow } = this.#windowRows(2);
|
|
1696
|
+
return {
|
|
1697
|
+
startRow,
|
|
1698
|
+
endRow,
|
|
1699
|
+
startCol: this._findFirstVisibleCol(this._scrollLeft),
|
|
1700
|
+
endCol: this._findLastVisibleCol(this._scrollLeft + this._viewportW)
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
/** Row keys currently within the windowed/visible range, in display order. */
|
|
1704
|
+
renderedKeys() {
|
|
1705
|
+
const { startRow, endRow } = this.getVisibleRange();
|
|
1706
|
+
return this._flat.slice(startRow, endRow).filter((f) => f.type === `row`).map((f) => f.key);
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Row keys strictly intersecting the viewport (no overscan), mirroring
|
|
1710
|
+
* `ixfx-grid-list.visibleKeys()` for the `datagrid-viewport-change` detail.
|
|
1711
|
+
*/
|
|
1712
|
+
#visibleKeys() {
|
|
1713
|
+
const { start, end } = this.#windowRows(0);
|
|
1714
|
+
return this._flat.slice(start, end).filter((f) => f.type === `row`).map((f) => f.key);
|
|
1715
|
+
}
|
|
1716
|
+
/** Coalesce viewport-change dispatches to at most one per animation frame. */
|
|
1717
|
+
#scheduleViewportChangeEmit() {
|
|
1718
|
+
if (this.#viewportChangeRaf !== 0) return;
|
|
1719
|
+
this.#viewportChangeRaf = requestAnimationFrame(() => {
|
|
1720
|
+
this.#viewportChangeRaf = 0;
|
|
1721
|
+
this.#emitViewportChangeIfChanged();
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Dispatch `datagrid-viewport-change` with the current key sets — unless
|
|
1726
|
+
* neither set changed since the last dispatch, so coalesced frames and
|
|
1727
|
+
* no-op scrolls stay silent.
|
|
1728
|
+
*/
|
|
1729
|
+
#emitViewportChangeIfChanged() {
|
|
1730
|
+
const renderedKeys = this.renderedKeys();
|
|
1731
|
+
const visibleKeys = this.#visibleKeys();
|
|
1732
|
+
if (sameKeys(this.#lastViewportRenderedKeys, renderedKeys) && sameKeys(this.#lastViewportVisibleKeys, visibleKeys)) return;
|
|
1733
|
+
this.#lastViewportRenderedKeys = renderedKeys;
|
|
1734
|
+
this.#lastViewportVisibleKeys = visibleKeys;
|
|
1735
|
+
this.dispatchEvent(new CustomEvent(`datagrid-viewport-change`, {
|
|
1736
|
+
detail: {
|
|
1737
|
+
renderedKeys,
|
|
1738
|
+
visibleKeys
|
|
1739
|
+
},
|
|
1740
|
+
bubbles: true,
|
|
1741
|
+
composed: true
|
|
1742
|
+
}));
|
|
1743
|
+
}
|
|
1744
|
+
_findFirstVisibleCol(scrollLeft) {
|
|
1745
|
+
let lo = 0;
|
|
1746
|
+
let hi = this._colOffsets.length - 1;
|
|
1747
|
+
let ans = 0;
|
|
1748
|
+
while (lo <= hi) {
|
|
1749
|
+
const mid = lo + hi >> 1;
|
|
1750
|
+
if (this._colOffsets[mid] <= scrollLeft) {
|
|
1751
|
+
ans = mid;
|
|
1752
|
+
lo = mid + 1;
|
|
1753
|
+
} else hi = mid - 1;
|
|
1754
|
+
}
|
|
1755
|
+
return ans;
|
|
1756
|
+
}
|
|
1757
|
+
_findLastVisibleCol(right) {
|
|
1758
|
+
for (let i = 0; i < this._colOffsets.length; i++) if (this._colOffsets[i] > right) return i;
|
|
1759
|
+
return this._colOffsets.length;
|
|
1760
|
+
}
|
|
1761
|
+
render() {
|
|
1762
|
+
const rh = this.rowHeight;
|
|
1763
|
+
const { start: startRow, end: endRow } = this.#windowRows(2);
|
|
1764
|
+
const visibleFlat = this._flat.slice(startRow, endRow).map((entry, i) => ({
|
|
1765
|
+
entry,
|
|
1766
|
+
absIdx: startRow + i
|
|
1767
|
+
}));
|
|
1768
|
+
const pinnedKey = this._editing?.rowKey ?? this._editingRow;
|
|
1769
|
+
const pinnedIdx = pinnedKey ? this.#flatIndexByKey.get(pinnedKey) : void 0;
|
|
1770
|
+
if (pinnedIdx !== void 0) {
|
|
1771
|
+
if (pinnedIdx >= startRow && pinnedIdx < endRow) visibleFlat.splice(pinnedIdx - startRow, 1);
|
|
1772
|
+
visibleFlat.unshift({
|
|
1773
|
+
entry: this._flat[pinnedIdx],
|
|
1774
|
+
absIdx: pinnedIdx
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
const showCheckbox = this.interactionMode === `checked`;
|
|
1778
|
+
return html`
|
|
1779
|
+
<div class="grid ${this.columnMenu === `auto` ? `has-actions` : ``}" role="grid" aria-rowcount=${this._flat.length + 1} aria-colcount=${this._visibleColumns.length} aria-multiselectable=${this.selectionMode === `multiple` ? `true` : `false`}>
|
|
1780
|
+
<div class="viewport scroll-fade-y" @scroll=${this._onScroll}>
|
|
1781
|
+
<div class="header-row scroll-fade-x" role="row" aria-rowindex="1">
|
|
1782
|
+
${showCheckbox ? html`<div class="header-cell checkbox-col" role="columnheader"></div>` : nothing}
|
|
1783
|
+
${repeat(this._visibleColumns, (c) => c.key, (col, idx) => html`
|
|
1784
|
+
<div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="columnheader" aria-sort=${this.sortState?.key === col.key ? this.sortState.direction === `asc` ? `ascending` : `descending` : `none`} style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
|
|
1785
|
+
@click=${(e) => this._onHeaderClick(col, e)}
|
|
1786
|
+
@pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
|
|
1787
|
+
${col.reorderable !== false ? html`<span class="reorder-handle" draggable="false"
|
|
1788
|
+
@pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
|
|
1789
|
+
><span class="ixfx-handle" data-handle="dotted" data-shape="vertical" part="reorder-handle"></span></span>` : nothing}
|
|
1790
|
+
<span class="header-label" @pointerup=${(e) => this._onHeaderPointerUp(col, e)}>${col.label}</span>
|
|
1791
|
+
${this.sortState?.key === col.key ? html`<span class="sort-icon"><ixfx-icon name=${this.sortState.direction === `asc` ? `chevron-up` : `chevron-down`} size="12"></ixfx-icon></span>` : nothing}
|
|
1792
|
+
${col.button !== false ? html`<span class="col-button" @click=${(e) => this._onColButtonClick(col, e)}><ixfx-icon name=${col.button && typeof col.button === `object` && col.button.icon ? col.button.icon : this.columnButtonIcon} size="14"></ixfx-icon></span>` : nothing}
|
|
1793
|
+
${col.resizable !== false ? html`<span class="resize-handle" ?data-active=${this._resizingColKey === col.key} @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
|
|
1794
|
+
e.preventDefault();
|
|
1795
|
+
e.stopPropagation();
|
|
1796
|
+
this._autoSizeColumn(col);
|
|
1797
|
+
}}></span>` : nothing}
|
|
1798
|
+
</div>
|
|
1799
|
+
`)}
|
|
1800
|
+
${this._drag?.active && this._drag.gap !== void 0 ? html`<div class="drop-caret" style="left:${(() => {
|
|
1801
|
+
const gap = this._drag.gap;
|
|
1802
|
+
if (gap < this._colOffsets.length) return this._colOffsets[gap];
|
|
1803
|
+
const last = this._colOffsets.length - 1;
|
|
1804
|
+
return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
|
|
1805
|
+
})()}px"></div>` : nothing}
|
|
1806
|
+
</div>
|
|
1807
|
+
<div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
|
|
1808
|
+
${repeat(visibleFlat, (f) => f.entry.key, ({ entry, absIdx }) => {
|
|
1809
|
+
if (entry.type === `group`) {
|
|
1810
|
+
const gKey = entry.key.replace(/^__group__/, ``);
|
|
1811
|
+
const collapsed = this._collapsedGroups.has(gKey);
|
|
1812
|
+
return html`<div class="group-row ${collapsed ? `collapsed` : ``}" role="row" aria-rowindex=${absIdx + 2} style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span role="rowheader" aria-expanded=${collapsed ? `false` : `true`}>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
|
|
1813
|
+
}
|
|
1814
|
+
const row = entry.row;
|
|
1815
|
+
const absIdx2 = entry.idx;
|
|
1816
|
+
const rowKey = entry.key;
|
|
1817
|
+
const selected = this._selectedKeys.has(rowKey);
|
|
1818
|
+
const tickled = this._tickled.isTickled(rowKey);
|
|
1819
|
+
return html`
|
|
1820
|
+
<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` : ``}" role="row" aria-rowindex=${absIdx + 2} aria-selected=${selected ? `true` : `false`} style="top:${absIdx * rh}px; height:${rh}px"
|
|
1821
|
+
data-key=${rowKey}
|
|
1822
|
+
draggable=${this.#nativeDrag.draggableAttr}
|
|
1823
|
+
@click=${(e) => {
|
|
1824
|
+
this._onRowClick(rowKey, null, e);
|
|
1825
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
|
|
1826
|
+
detail: {
|
|
1827
|
+
rowKey,
|
|
1828
|
+
colKey: ``,
|
|
1829
|
+
rowIndex: absIdx2,
|
|
1830
|
+
colIndex: -1
|
|
1831
|
+
},
|
|
1832
|
+
bubbles: true,
|
|
1833
|
+
composed: true
|
|
1834
|
+
}));
|
|
1835
|
+
}}
|
|
1836
|
+
@pointerenter=${() => this._tickled.handlePointerEnter(rowKey)}
|
|
1837
|
+
@pointerleave=${() => this._tickled.clearTickled()}
|
|
1838
|
+
@dblclick=${() => {
|
|
1839
|
+
if (this._visibleColumns[0] && this._isEditable(row, this._visibleColumns[0])) this._startEdit(rowKey, this._visibleColumns[0].key);
|
|
1840
|
+
else this._activateRow(rowKey, row, absIdx2);
|
|
1841
|
+
}}>
|
|
1842
|
+
${showCheckbox ? html`<div class="checkbox-col"><input type="checkbox" .checked=${selected} @click=${(e) => this._onCheckboxClick(rowKey, e)} /></div>` : nothing}
|
|
1843
|
+
${repeat(this._visibleColumns, (c) => c.key, (col) => {
|
|
1844
|
+
const val = row[col.key];
|
|
1845
|
+
if (this._isCellEditing(rowKey, col.key)) {
|
|
1846
|
+
const immediate = this.editability === `immediate` || this._editingRow === rowKey;
|
|
1847
|
+
const curVal = immediate ? val : this._editValue ?? val;
|
|
1848
|
+
const effectiveDataType = col.dataType ?? `text`;
|
|
1849
|
+
if (effectiveDataType === `boolean` && (col.editor === `auto` || col.editor === `select` || !col.editor)) return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px;justify-content:center;align-items:center"><input type="checkbox" .checked=${Boolean(curVal)} @change=${(e) => {
|
|
1850
|
+
const v = e.target.checked;
|
|
1851
|
+
if (immediate) this._commitImmediate(rowKey, col.key, v);
|
|
1852
|
+
else {
|
|
1853
|
+
this._editValue = v;
|
|
1854
|
+
this._commitEdit();
|
|
1855
|
+
}
|
|
1856
|
+
}} /></div>`;
|
|
1857
|
+
if (col.editor === `select`) {
|
|
1858
|
+
const opts = col.editorOptions ?? [];
|
|
1859
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
|
|
1860
|
+
const v = e.target.value;
|
|
1861
|
+
if (immediate) this._commitImmediate(rowKey, col.key, v);
|
|
1862
|
+
else {
|
|
1863
|
+
this._editValue = v;
|
|
1864
|
+
this._commitEdit();
|
|
1865
|
+
}
|
|
1866
|
+
}} @keydown=${(e) => {
|
|
1867
|
+
if (e.key === `Tab`) {
|
|
1868
|
+
e.preventDefault();
|
|
1869
|
+
e.stopPropagation();
|
|
1870
|
+
this._tabToNext(rowKey, col.key, e.shiftKey ? -1 : 1);
|
|
1871
|
+
}
|
|
1872
|
+
if (e.key === `Escape`) {
|
|
1873
|
+
this._editing = null;
|
|
1874
|
+
this._editingRow = null;
|
|
1875
|
+
}
|
|
1876
|
+
}} @blur=${() => {
|
|
1877
|
+
if (this._suppressBlurCommit) return;
|
|
1878
|
+
if (!immediate) this._commitEdit();
|
|
1879
|
+
}}><option value=""></option>${opts.map((o) => {
|
|
1880
|
+
const v = typeof o === `string` ? o : o.value;
|
|
1881
|
+
const l = typeof o === `string` ? o : o.label;
|
|
1882
|
+
return html`<option value=${v} ?selected=${String(curVal) === v}>${l}</option>`;
|
|
1883
|
+
})}</select></div>`;
|
|
1884
|
+
}
|
|
1885
|
+
if (typeof col.editor === `function`) {
|
|
1886
|
+
const factory = col.editor;
|
|
1887
|
+
const ctx = {
|
|
1888
|
+
row,
|
|
1889
|
+
column: col,
|
|
1890
|
+
rowIndex: absIdx2,
|
|
1891
|
+
colIndex: 0,
|
|
1892
|
+
value: curVal,
|
|
1893
|
+
commit: (v) => {
|
|
1894
|
+
if (immediate) this._commitImmediate(rowKey, col.key, v);
|
|
1895
|
+
else {
|
|
1896
|
+
this._editValue = v;
|
|
1897
|
+
this._commitEdit();
|
|
1898
|
+
}
|
|
1899
|
+
},
|
|
1900
|
+
cancel: () => {
|
|
1901
|
+
this._editing = null;
|
|
1902
|
+
this._editingRow = null;
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
|
|
1906
|
+
}
|
|
1907
|
+
const inputType = effectiveDataType === `number` ? `number` : `text`;
|
|
1908
|
+
const inputValue = effectiveDataType === `number` ? String(curVal ?? ``) : String(curVal ?? ``);
|
|
1909
|
+
return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><input type=${inputType} .value=${inputValue} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
|
|
1910
|
+
if (e.key === `Enter`) this._commitEdit();
|
|
1911
|
+
if (e.key === `Escape`) {
|
|
1912
|
+
this._editing = null;
|
|
1913
|
+
this._editingRow = null;
|
|
1914
|
+
}
|
|
1915
|
+
if (e.key === `Tab`) {
|
|
1916
|
+
e.preventDefault();
|
|
1917
|
+
e.stopPropagation();
|
|
1918
|
+
this._tabToNext(rowKey, col.key, e.shiftKey ? -1 : 1);
|
|
1919
|
+
}
|
|
1920
|
+
}} @blur=${() => {
|
|
1921
|
+
if (this._suppressBlurCommit) return;
|
|
1922
|
+
if (!immediate) this._commitEdit();
|
|
1923
|
+
}} autofocus /></div>`;
|
|
1924
|
+
}
|
|
1925
|
+
const displayVal = this._formatValue(val, col, row);
|
|
1926
|
+
const rendered = col.renderer ? col.renderer(row, col, absIdx2) : displayVal;
|
|
1927
|
+
const cellContent = col.renderer ? rendered : displayVal;
|
|
1928
|
+
const textEditable = this._isEditable(row, col) && this._isTextEditor(col);
|
|
1929
|
+
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));` : ``;
|
|
1930
|
+
const canPan = this.cellOverflow === `pan-text` && !col.renderer && (selected || tickled);
|
|
1931
|
+
const look = this._cellLook(row, col.key);
|
|
1932
|
+
const cellStyle = look.textColor === void 0 ? `` : `color:${look.textColor};`;
|
|
1933
|
+
return html`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent} ${cellStyle}"
|
|
1934
|
+
@click=${(e) => {
|
|
1935
|
+
this._lastClickedCell = {
|
|
1936
|
+
rowKey,
|
|
1937
|
+
colKey: col.key
|
|
1938
|
+
};
|
|
1939
|
+
if (col.selectable !== false) {
|
|
1940
|
+
e.stopPropagation();
|
|
1941
|
+
this._onRowClick(rowKey, col, e);
|
|
1942
|
+
this._handleCellSecondClick(rowKey, col);
|
|
1943
|
+
}
|
|
1944
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
|
|
1945
|
+
detail: {
|
|
1946
|
+
rowKey,
|
|
1947
|
+
colKey: col.key,
|
|
1948
|
+
rowIndex: absIdx2,
|
|
1949
|
+
colIndex: this.columns.indexOf(col)
|
|
1950
|
+
},
|
|
1951
|
+
bubbles: true,
|
|
1952
|
+
composed: true
|
|
1953
|
+
}));
|
|
1954
|
+
}}
|
|
1955
|
+
>${look.icon === void 0 ? nothing : html`<span class="cell-icon" part="icon" style=${look.iconColor === void 0 ? nothing : `color:${look.iconColor}`}>${look.icon}</span>`}${canPan ? html`<ixfx-pan-text mode="loop">${cellContent}</ixfx-pan-text>` : cellContent}${look.badge === void 0 ? nothing : html`<span class="cell-badge" part="badge">${look.badge}</span>`}</div>`;
|
|
1956
|
+
})}
|
|
1957
|
+
</div>
|
|
1958
|
+
`;
|
|
1959
|
+
})}
|
|
1960
|
+
</div>
|
|
1961
|
+
</div>
|
|
1962
|
+
${this.#renderColumnMenu()}
|
|
1963
|
+
${this.search.renderField()}
|
|
1964
|
+
</div>
|
|
1965
|
+
`;
|
|
1966
|
+
}
|
|
1967
|
+
/** Built-in column/group menu, pinned over the header row's end. Only for `columnMenu: auto`. */
|
|
1968
|
+
#renderColumnMenu() {
|
|
1969
|
+
if (this.columnMenu !== `auto`) return nothing;
|
|
1970
|
+
return html`
|
|
1971
|
+
<div class="header-actions">
|
|
1972
|
+
<ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
|
|
1973
|
+
const cmd = e.detail.command;
|
|
1974
|
+
const checked = e.detail.checked;
|
|
1975
|
+
const value = e.detail.value;
|
|
1976
|
+
if (cmd?.startsWith(`col:`)) {
|
|
1977
|
+
const key = cmd.slice(4);
|
|
1978
|
+
const col = this.columns.find((c) => c.key === key);
|
|
1979
|
+
if (col) this._onVisibilityToggle(col, !!checked);
|
|
1980
|
+
e.stopPropagation();
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
if (cmd === `groupBy`) {
|
|
1984
|
+
const v = value ?? ``;
|
|
1985
|
+
this.groupBy = v || null;
|
|
1986
|
+
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
1987
|
+
this.dispatchEvent(new CustomEvent(`datagrid-group-change`, {
|
|
1988
|
+
detail: { groupBy: this.groupBy },
|
|
1989
|
+
bubbles: true,
|
|
1990
|
+
composed: true
|
|
1991
|
+
}));
|
|
1992
|
+
e.stopPropagation();
|
|
1993
|
+
}
|
|
1994
|
+
}}>
|
|
1995
|
+
<ixfx-menu-container slot="menu">
|
|
1996
|
+
<ixfx-menu-header label="Columns"></ixfx-menu-header>
|
|
1997
|
+
${repeat(this.columns, (c) => c.key, (col) => html`
|
|
1998
|
+
<ixfx-menu-check-item command="col:${col.key}" label=${col.label} ?checked=${col.visible !== false}></ixfx-menu-check-item>
|
|
1999
|
+
`)}
|
|
2000
|
+
${this.allowGrouping ? html`
|
|
2001
|
+
<ixfx-menu-separator></ixfx-menu-separator>
|
|
2002
|
+
<ixfx-menu-header label="Group by"></ixfx-menu-header>
|
|
2003
|
+
<ixfx-menu-radio-group name="groupBy" value=${typeof this.groupBy === `string` ? this.groupBy : ``}>
|
|
2004
|
+
<ixfx-menu-radio-item value="" label="None"></ixfx-menu-radio-item>
|
|
2005
|
+
${this.columns.filter((c) => c.groupable !== false && (!this.groupableColumns || this.groupableColumns.includes(c.key))).map((col) => html`
|
|
2006
|
+
<ixfx-menu-radio-item value=${col.key} label=${col.label}></ixfx-menu-radio-item>
|
|
2007
|
+
`)}
|
|
2008
|
+
</ixfx-menu-radio-group>
|
|
2009
|
+
` : nothing}
|
|
2010
|
+
</ixfx-menu-container>
|
|
2011
|
+
</ixfx-menu-trigger>
|
|
2012
|
+
</div>`;
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
__decorate([property({ type: Array })], DataGridElement.prototype, "columns", void 0);
|
|
2016
|
+
__decorate([property({ type: Array })], DataGridElement.prototype, "rows", void 0);
|
|
2017
|
+
__decorate([property({
|
|
2018
|
+
type: String,
|
|
2019
|
+
attribute: `selection-mode`
|
|
2020
|
+
})], DataGridElement.prototype, "selectionMode", void 0);
|
|
2021
|
+
__decorate([property({
|
|
2022
|
+
type: String,
|
|
2023
|
+
attribute: `interaction-mode`
|
|
2024
|
+
})], DataGridElement.prototype, "interactionMode", void 0);
|
|
2025
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "sortState", void 0);
|
|
2026
|
+
__decorate([property({
|
|
2027
|
+
type: Number,
|
|
2028
|
+
attribute: `row-height`
|
|
2029
|
+
})], DataGridElement.prototype, "rowHeight", void 0);
|
|
2030
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "getRowKey", void 0);
|
|
2031
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "checkboxRenderer", void 0);
|
|
2032
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "editable", void 0);
|
|
2033
|
+
__decorate([property({
|
|
2034
|
+
type: String,
|
|
2035
|
+
reflect: true
|
|
2036
|
+
})], DataGridElement.prototype, "editability", void 0);
|
|
2037
|
+
__decorate([property({
|
|
2038
|
+
type: String,
|
|
2039
|
+
attribute: `cell-overflow`
|
|
2040
|
+
})], DataGridElement.prototype, "cellOverflow", void 0);
|
|
2041
|
+
__decorate([property({
|
|
2042
|
+
type: Boolean,
|
|
2043
|
+
attribute: `zebra-stripes`,
|
|
2044
|
+
reflect: true
|
|
2045
|
+
})], DataGridElement.prototype, "zebraStripes", void 0);
|
|
2046
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "groupBy", void 0);
|
|
2047
|
+
__decorate([property({
|
|
2048
|
+
type: Boolean,
|
|
2049
|
+
attribute: `allow-grouping`
|
|
2050
|
+
})], DataGridElement.prototype, "allowGrouping", void 0);
|
|
2051
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "groupLabel", void 0);
|
|
2052
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "groupableColumns", void 0);
|
|
2053
|
+
__decorate([property({
|
|
2054
|
+
type: Boolean,
|
|
2055
|
+
attribute: `cross-group-selection`,
|
|
2056
|
+
reflect: true
|
|
2057
|
+
})], DataGridElement.prototype, "crossGroupSelection", void 0);
|
|
2058
|
+
__decorate([property({
|
|
2059
|
+
type: String,
|
|
2060
|
+
attribute: `column-menu`
|
|
2061
|
+
})], DataGridElement.prototype, "columnMenu", void 0);
|
|
2062
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
|
|
2063
|
+
__decorate([property({
|
|
2064
|
+
type: String,
|
|
2065
|
+
attribute: `text-color-column`
|
|
2066
|
+
})], DataGridElement.prototype, "textColorColumn", void 0);
|
|
2067
|
+
__decorate([property({
|
|
2068
|
+
type: String,
|
|
2069
|
+
attribute: `icon-column`
|
|
2070
|
+
})], DataGridElement.prototype, "iconColumn", void 0);
|
|
2071
|
+
__decorate([property({
|
|
2072
|
+
type: String,
|
|
2073
|
+
attribute: `badge-column`
|
|
2074
|
+
})], DataGridElement.prototype, "badgeColumn", void 0);
|
|
2075
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "filterPredicate", void 0);
|
|
2076
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "rowSearchText", void 0);
|
|
2077
|
+
__decorate([property({
|
|
2078
|
+
type: String,
|
|
2079
|
+
attribute: `search-modes`,
|
|
2080
|
+
reflect: true
|
|
2081
|
+
})], DataGridElement.prototype, "searchModes", void 0);
|
|
2082
|
+
__decorate([property({
|
|
2083
|
+
type: Boolean,
|
|
2084
|
+
attribute: `items-draggable`,
|
|
2085
|
+
reflect: true
|
|
2086
|
+
})], DataGridElement.prototype, "itemsDraggable", void 0);
|
|
2087
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "dragPreviewRenderer", void 0);
|
|
2088
|
+
__decorate([property({ attribute: false })], DataGridElement.prototype, "dragPreviewSource", void 0);
|
|
2089
|
+
__decorate([property({
|
|
2090
|
+
type: String,
|
|
2091
|
+
attribute: `column-button-icon`
|
|
2092
|
+
})], DataGridElement.prototype, "columnButtonIcon", void 0);
|
|
2093
|
+
__decorate([state()], DataGridElement.prototype, "_resizingColKey", void 0);
|
|
2094
|
+
__decorate([state()], DataGridElement.prototype, "_animatingColKeys", void 0);
|
|
2095
|
+
__decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
|
|
2096
|
+
__decorate([state()], DataGridElement.prototype, "_viewportH", void 0);
|
|
2097
|
+
__decorate([state()], DataGridElement.prototype, "_viewportW", void 0);
|
|
2098
|
+
__decorate([state()], DataGridElement.prototype, "_collapsedGroups", void 0);
|
|
2099
|
+
__decorate([state()], DataGridElement.prototype, "_editing", void 0);
|
|
2100
|
+
__decorate([state()], DataGridElement.prototype, "_editingRow", void 0);
|
|
2101
|
+
__decorate([state()], DataGridElement.prototype, "_lastClick", void 0);
|
|
2102
|
+
__decorate([state()], DataGridElement.prototype, "_lastClickedCell", void 0);
|
|
2103
|
+
__decorate([state()], DataGridElement.prototype, "_editValue", void 0);
|
|
2104
|
+
__decorate([query(`.viewport`)], DataGridElement.prototype, "_viewportEl", void 0);
|
|
2105
|
+
__decorate([query(`.header-row`)], DataGridElement.prototype, "_headerRowEl", void 0);
|
|
2106
|
+
DataGridElement = __decorate([safeCustomElement(`ixfx-data-grid`)], DataGridElement);
|
|
2107
|
+
//#endregion
|
|
2108
|
+
export { DataGridElement as t };
|