argus-table 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argus-column-group.js +66 -0
- package/dist/argus-column-group.js.map +1 -0
- package/dist/argus-column.js +4 -25
- package/dist/argus-column.js.map +1 -1
- package/dist/argus-table.js +553 -200
- package/dist/argus-table.js.map +1 -1
- package/dist/attributes.js +21 -0
- package/dist/attributes.js.map +1 -0
- package/dist/cdn/argus-table.js +92 -57
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/column-registry.js +23 -0
- package/dist/column-registry.js.map +1 -0
- package/dist/controllers/auto-width.js +6 -5
- package/dist/controllers/auto-width.js.map +1 -1
- package/dist/controllers/column-children.js +32 -9
- package/dist/controllers/column-children.js.map +1 -1
- package/dist/controllers/column-window.js +0 -4
- package/dist/controllers/column-window.js.map +1 -1
- package/dist/controllers/plugins.js +25 -6
- package/dist/controllers/plugins.js.map +1 -1
- package/dist/controllers/reorder-drag.js +12 -9
- package/dist/controllers/reorder-drag.js.map +1 -1
- package/dist/controllers/row-measure.js +2 -1
- package/dist/controllers/row-measure.js.map +1 -1
- package/dist/controllers/virtual-controller.js +25 -32
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-groups.js +129 -0
- package/dist/core/src/columns/column-groups.js.map +1 -0
- package/dist/core/src/columns/column-order.js +125 -31
- package/dist/core/src/columns/column-order.js.map +1 -1
- package/dist/core/src/columns/resolve-columns.js +0 -1
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/columns/spread-width.js +43 -0
- package/dist/core/src/columns/spread-width.js.map +1 -0
- package/dist/core/src/filters/filter.js +6 -8
- package/dist/core/src/filters/filter.js.map +1 -1
- package/dist/core/src/grid/row-layout.js +19 -11
- package/dist/core/src/grid/row-layout.js.map +1 -1
- package/dist/core/src/state/selection.js +2 -1
- package/dist/core/src/state/selection.js.map +1 -1
- package/dist/core/src/table/create-table.js +57 -22
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/sorted-row-model.js.map +1 -1
- package/dist/core/src/virtual/row-sizes.js +0 -4
- package/dist/core/src/virtual/row-sizes.js.map +1 -1
- package/dist/custom-elements.json +489 -245
- package/dist/events.js +16 -16
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +273 -127
- package/dist/index.js +3 -2
- package/dist/render/body.js +2 -2
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +11 -3
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content-directive.js +1 -1
- package/dist/render/content-directive.js.map +1 -1
- package/dist/render/content.js +15 -3
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +82 -19
- package/dist/render/header.js.map +1 -1
- package/dist/styles/base.js +1 -1
- package/dist/styles/base.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArgusColumn } from "./argus-column.js";
|
|
2
|
+
import { ArgusColumnGroup } from "./argus-column-group.js";
|
|
2
3
|
import { ArgusTable } from "./argus-table.js";
|
|
3
4
|
import { content } from "./render/content-directive.js";
|
|
4
|
-
import { CELL_CLICK,
|
|
5
|
-
export { ArgusColumn, ArgusTable, CELL_CLICK,
|
|
5
|
+
import { CELL_CLICK, COLUMN_FILTERS_CHANGE, COLUMN_ORDER_CHANGE, COLUMN_PINNING_CHANGE, COLUMN_SIZING_CHANGE, COLUMN_VISIBILITY_CHANGE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SELECTION_CHANGE, SORTING_CHANGE } from "./events.js";
|
|
6
|
+
export { ArgusColumn, ArgusColumnGroup, ArgusTable, CELL_CLICK, COLUMN_FILTERS_CHANGE, COLUMN_ORDER_CHANGE, COLUMN_PINNING_CHANGE, COLUMN_SIZING_CHANGE, COLUMN_VISIBILITY_CHANGE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SELECTION_CHANGE, SORTING_CHANGE, content };
|
package/dist/render/body.js
CHANGED
|
@@ -32,10 +32,10 @@ function renderBody(rows, columns, totalSize, active, firstRowIndex, loading, se
|
|
|
32
32
|
function renderRow(row, columns, activeCol, firstRowIndex, loading, selected, checkboxes, inset) {
|
|
33
33
|
const state = loading?.stateAt(row.index) ?? "loaded";
|
|
34
34
|
let parts = row.index % 2 === 0 ? "row row-even" : "row row-odd";
|
|
35
|
-
if (state !== "loaded") parts += " row-loading";
|
|
35
|
+
if (state !== "loaded") parts += state === "error" ? " row-loading row-error" : " row-loading";
|
|
36
36
|
if (selected) parts += " row-selected";
|
|
37
37
|
const retry = state === "error" ? loading?.retry.get(row.index) : void 0;
|
|
38
|
-
const retryCol = firstDataColumn(columns);
|
|
38
|
+
const retryCol = retry === void 0 ? void 0 : firstDataColumn(columns);
|
|
39
39
|
return html`<div
|
|
40
40
|
part=${parts}
|
|
41
41
|
class="row"
|
package/dist/render/body.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body.js","names":[],"sources":["../../src/render/body.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport { live } from 'lit/directives/live.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport type { RowState } from '@argus-table/core';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport { cellParts, cellSlot, cellStyle, type CheckboxHandlers } from './content.js';\n\ntype Columns<T> = readonly RenderedColumn<T>[];\n\n/** The cell holding the grid's tab stop: a row index and a column id. */\nexport interface ActiveCell {\n index: number;\n col: string;\n}\n\n/** Row loading state for `dataSource` tables. */\nexport interface Loading {\n /** Whether a row's data is loaded, still loading, or its page failed. */\n stateAt(index: number): RowState;\n /** Rows that show a Retry button (one per failed page in view), with its accessible label. */\n retry: ReadonlyMap<number, string>;\n /** Click handler of the Retry buttons; the button carries the row index in `data-row`. */\n onRetry: (event: Event) => void;\n}\n\n/**\n * Whether the row at an index is selected; `undefined` when it can't be\n * selected (selection is off, or the row hasn't loaded).\n */\nexport type SelectedAt = (index: number) => boolean | undefined;\n\n/**\n * The virtualized body. Rows are keyed by pool slot, so a row entering the window\n * reuses the DOM of one that left (ADR 0001), and they stay in pool order: screen\n * readers get the position from `aria-rowindex`. `firstRowIndex` is the\n * `aria-rowindex` of row 0: after the header and the rows pinned to the top.\n * `inset`: rows of different heights (`rowHeight: 'auto'`) are placed with\n * `inset-block-start` and render at least their measured `size`, which is on\n * the controller's px grid, so they stay flush however far down they are.\n */\nexport function renderBody<T>(\n rows: readonly WindowRow[],\n columns: Columns<T>,\n totalSize: number,\n active: ActiveCell | undefined,\n firstRowIndex: number,\n loading?: Loading,\n selectedAt?: SelectedAt,\n checkboxes?: CheckboxHandlers,\n inset = false,\n) {\n return html`<div\n part=\"body\"\n role=\"rowgroup\"\n style=${styleMap({ blockSize: `${String(totalSize)}px` })}\n >\n ${repeat(\n rows,\n (r) => r.slot,\n (r) =>\n renderRow(\n r,\n columns,\n r.index === active?.index ? active.col : undefined,\n firstRowIndex,\n loading,\n selectedAt?.(r.index),\n checkboxes,\n inset,\n ),\n )}\n </div>`;\n}\n\n/**\n * One body row. Every cell is focusable (`tabindex=-1`) so clicks focus it; the\n * active one is the grid's single tab stop (roving tabindex). A row whose data\n * isn't loaded shows skeleton bars, and a Retry button if its page failed.\n * `selected` is `undefined` for a row that can't be selected: it then has no\n * `aria-selected`, and no checkbox in the selection column.\n */\nfunction renderRow<T>(\n row: WindowRow,\n columns: Columns<T>,\n activeCol: string | undefined,\n firstRowIndex: number,\n loading: Loading | undefined,\n selected: boolean | undefined,\n checkboxes: CheckboxHandlers | undefined,\n inset: boolean,\n) {\n const state = loading?.stateAt(row.index) ?? 'loaded';\n let parts = row.index % 2 === 0 ? 'row row-even' : 'row row-odd';\n if (state !== 'loaded') parts += ' row-loading';\n if (selected) parts += ' row-selected';\n const retry = state === 'error' ? loading?.retry.get(row.index) : undefined;\n // The Retry button goes in the first data column, after a selection column.\n const retryCol = firstDataColumn(columns);\n return html`<div\n part=${parts}\n class=\"row\"\n role=\"row\"\n aria-rowindex=${row.index + firstRowIndex}\n aria-busy=${state === 'loading' ? 'true' : nothing}\n aria-selected=${selected === undefined ? nothing : String(selected)}\n style=${styleMap(\n inset\n ? {\n insetBlockStart: `${String(row.start)}px`,\n minBlockSize: row.size === undefined ? undefined : `${String(row.size)}px`,\n }\n : { transform: `translateY(${String(row.start)}px)` },\n )}\n >\n ${repeat(\n columns,\n (c) => c.key,\n (c) =>\n renderCell(\n c,\n c.id === activeCol,\n cellSlot(row.slot, c.slot),\n false,\n c.kind === 'selection'\n ? rowCheckbox(row.index, selected, checkboxes)\n : placeholder(state, c.id === retryCol ? retry : undefined, row.index, loading),\n ),\n )}\n </div>`;\n}\n\n/**\n * A body or pinned-row cell. Every cell is focusable (`tabindex=-1`) so clicks\n * focus it; `tabStop` marks the grid's single tab stop. `before` goes ahead of\n * the slot (a loading row's skeleton bar or Retry button).\n */\nexport function renderCell<T>(\n c: RenderedColumn<T>,\n tabStop: boolean,\n slot: string,\n pinnedRow: boolean,\n before: unknown = nothing,\n) {\n return html`<div\n part=${cellParts(c, pinnedRow)}\n role=\"gridcell\"\n aria-colindex=${c.colIndex}\n tabindex=${tabStop ? 0 : -1}\n data-col=${c.id}\n data-align=${c.align}\n data-wrap=${c.wrap ? '' : nothing}\n data-pin=${c.pin ?? nothing}\n style=${cellStyle(c) ?? nothing}\n >\n ${before}<slot name=${slot}></slot>\n </div>`;\n}\n\n/**\n * The id of the leftmost rendered data column (in reading order). Rendered\n * columns are in pool order with `virtualizeColumns`.\n */\nfunction firstDataColumn<T>(columns: Columns<T>): string | undefined {\n let first: RenderedColumn<T> | undefined;\n for (const c of columns) {\n if (c.kind === 'data' && (!first || c.colIndex < first.colIndex)) first = c;\n }\n return first?.id;\n}\n\n/**\n * A row's checkbox in the selection column, for a row that can be selected.\n * It isn't focusable: the cell holds focus. `live()` keeps it showing the\n * selection when a controlled host turns a change down.\n */\nfunction rowCheckbox(\n index: number,\n selected: boolean | undefined,\n checkboxes: CheckboxHandlers | undefined,\n) {\n if (selected === undefined || !checkboxes) return nothing;\n return html`<input\n type=\"checkbox\"\n part=\"selection-checkbox\"\n tabindex=\"-1\"\n aria-label=\"Select row\"\n data-row=${index}\n .checked=${live(selected)}\n @click=${checkboxes.row}\n @mousedown=${checkboxes.press}\n />`;\n}\n\n/** What an unloaded cell shows: a skeleton bar, or the Retry button in a failed page's first cell. */\nfunction placeholder(\n state: RowState,\n retryLabel: string | undefined,\n index: number,\n loading: Loading | undefined,\n) {\n if (state === 'loaded') return nothing;\n if (retryLabel !== undefined && loading) {\n return html`<button\n part=\"retry\"\n type=\"button\"\n tabindex=\"-1\"\n data-row=${index}\n aria-label=${retryLabel}\n @click=${loading.onRetry}\n >\n Retry\n </button>`;\n }\n return html`<div\n part=${state === 'error' ? 'skeleton skeleton-error' : 'skeleton'}\n aria-hidden=\"true\"\n ></div>`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0CA,SAAgB,WACd,MACA,SACA,WACA,QACA,eACA,SACA,YACA,YACA,QAAQ,OACR;CACA,OAAO,IAAI;;;YAGD,SAAS,EAAE,WAAW,GAAG,OAAO,SAAS,EAAE,IAAI,CAAC,EAAE;;MAExD,OACA,OACC,MAAM,EAAE,OACR,MACC,UACE,GACA,SACA,EAAE,UAAU,QAAQ,QAAQ,OAAO,MAAM,KAAA,GACzC,eACA,SACA,aAAa,EAAE,KAAK,GACpB,YACA,KACF,CACJ,EAAE;;AAEN;;;;;;;;AASA,SAAS,UACP,KACA,SACA,WACA,eACA,SACA,UACA,YACA,OACA;CACA,MAAM,QAAQ,SAAS,QAAQ,IAAI,KAAK,KAAK;CAC7C,IAAI,QAAQ,IAAI,QAAQ,MAAM,IAAI,iBAAiB;CACnD,IAAI,UAAU,UAAU,SAAS;
|
|
1
|
+
{"version":3,"file":"body.js","names":[],"sources":["../../src/render/body.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport { live } from 'lit/directives/live.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport type { RowState } from '@argus-table/core';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport { cellParts, cellSlot, cellStyle, type CheckboxHandlers } from './content.js';\n\ntype Columns<T> = readonly RenderedColumn<T>[];\n\n/** The cell holding the grid's tab stop: a row index and a column id. */\nexport interface ActiveCell {\n index: number;\n col: string;\n}\n\n/** Row loading state for `dataSource` tables. */\nexport interface Loading {\n /** Whether a row's data is loaded, still loading, or its page failed. */\n stateAt(index: number): RowState;\n /** Rows that show a Retry button (one per failed page in view), with its accessible label. */\n retry: ReadonlyMap<number, string>;\n /** Click handler of the Retry buttons; the button carries the row index in `data-row`. */\n onRetry: (event: Event) => void;\n}\n\n/**\n * Whether the row at an index is selected; `undefined` when it can't be\n * selected (selection is off, or the row hasn't loaded).\n */\nexport type SelectedAt = (index: number) => boolean | undefined;\n\n/**\n * The virtualized body. Rows are keyed by pool slot, so a row entering the window\n * reuses the DOM of one that left (ADR 0001), and they stay in pool order: screen\n * readers get the position from `aria-rowindex`. `firstRowIndex` is the\n * `aria-rowindex` of row 0: after the header and the rows pinned to the top.\n * `inset`: rows of different heights (`rowHeight: 'auto'`) are placed with\n * `inset-block-start` and render at least their measured `size`, which is on\n * the controller's px grid, so they stay flush however far down they are.\n */\nexport function renderBody<T>(\n rows: readonly WindowRow[],\n columns: Columns<T>,\n totalSize: number,\n active: ActiveCell | undefined,\n firstRowIndex: number,\n loading?: Loading,\n selectedAt?: SelectedAt,\n checkboxes?: CheckboxHandlers,\n inset = false,\n) {\n return html`<div\n part=\"body\"\n role=\"rowgroup\"\n style=${styleMap({ blockSize: `${String(totalSize)}px` })}\n >\n ${repeat(\n rows,\n (r) => r.slot,\n (r) =>\n renderRow(\n r,\n columns,\n r.index === active?.index ? active.col : undefined,\n firstRowIndex,\n loading,\n selectedAt?.(r.index),\n checkboxes,\n inset,\n ),\n )}\n </div>`;\n}\n\n/**\n * One body row. Every cell is focusable (`tabindex=-1`) so clicks focus it; the\n * active one is the grid's single tab stop (roving tabindex). A row whose data\n * isn't loaded shows skeleton bars, and a Retry button if its page failed.\n * `selected` is `undefined` for a row that can't be selected: it then has no\n * `aria-selected`, and no checkbox in the selection column.\n */\nfunction renderRow<T>(\n row: WindowRow,\n columns: Columns<T>,\n activeCol: string | undefined,\n firstRowIndex: number,\n loading: Loading | undefined,\n selected: boolean | undefined,\n checkboxes: CheckboxHandlers | undefined,\n inset: boolean,\n) {\n const state = loading?.stateAt(row.index) ?? 'loaded';\n let parts = row.index % 2 === 0 ? 'row row-even' : 'row row-odd';\n if (state !== 'loaded') parts += state === 'error' ? ' row-loading row-error' : ' row-loading';\n if (selected) parts += ' row-selected';\n const retry = state === 'error' ? loading?.retry.get(row.index) : undefined;\n // The Retry button goes in the first data column, after a selection column.\n const retryCol = retry === undefined ? undefined : firstDataColumn(columns);\n return html`<div\n part=${parts}\n class=\"row\"\n role=\"row\"\n aria-rowindex=${row.index + firstRowIndex}\n aria-busy=${state === 'loading' ? 'true' : nothing}\n aria-selected=${selected === undefined ? nothing : String(selected)}\n style=${styleMap(\n inset\n ? {\n insetBlockStart: `${String(row.start)}px`,\n minBlockSize: row.size === undefined ? undefined : `${String(row.size)}px`,\n }\n : { transform: `translateY(${String(row.start)}px)` },\n )}\n >\n ${repeat(\n columns,\n (c) => c.key,\n (c) =>\n renderCell(\n c,\n c.id === activeCol,\n cellSlot(row.slot, c.slot),\n false,\n c.kind === 'selection'\n ? rowCheckbox(row.index, selected, checkboxes)\n : placeholder(state, c.id === retryCol ? retry : undefined, row.index, loading),\n ),\n )}\n </div>`;\n}\n\n/**\n * A body or pinned-row cell. Every cell is focusable (`tabindex=-1`) so clicks\n * focus it; `tabStop` marks the grid's single tab stop. `before` goes ahead of\n * the slot (a loading row's skeleton bar or Retry button).\n */\nexport function renderCell<T>(\n c: RenderedColumn<T>,\n tabStop: boolean,\n slot: string,\n pinnedRow: boolean,\n before: unknown = nothing,\n) {\n return html`<div\n part=${cellParts(c, pinnedRow)}\n role=\"gridcell\"\n aria-colindex=${c.colIndex}\n tabindex=${tabStop ? 0 : -1}\n data-col=${c.id}\n data-align=${c.align}\n data-wrap=${c.wrap ? '' : nothing}\n data-pin=${c.pin ?? nothing}\n style=${cellStyle(c) ?? nothing}\n >\n ${before}<slot name=${slot}></slot>\n </div>`;\n}\n\n/**\n * The id of the leftmost rendered data column (in reading order). Rendered\n * columns are in pool order with `virtualizeColumns`.\n */\nfunction firstDataColumn<T>(columns: Columns<T>): string | undefined {\n let first: RenderedColumn<T> | undefined;\n for (const c of columns) {\n if (c.kind === 'data' && (!first || c.colIndex < first.colIndex)) first = c;\n }\n return first?.id;\n}\n\n/**\n * A row's checkbox in the selection column, for a row that can be selected.\n * It isn't focusable: the cell holds focus. `live()` keeps it showing the\n * selection when a controlled host turns a change down.\n */\nfunction rowCheckbox(\n index: number,\n selected: boolean | undefined,\n checkboxes: CheckboxHandlers | undefined,\n) {\n if (selected === undefined || !checkboxes) return nothing;\n return html`<input\n type=\"checkbox\"\n part=\"selection-checkbox\"\n tabindex=\"-1\"\n aria-label=\"Select row\"\n data-row=${index}\n .checked=${live(selected)}\n @click=${checkboxes.row}\n @mousedown=${checkboxes.press}\n />`;\n}\n\n/** What an unloaded cell shows: a skeleton bar, or the Retry button in a failed page's first cell. */\nfunction placeholder(\n state: RowState,\n retryLabel: string | undefined,\n index: number,\n loading: Loading | undefined,\n) {\n if (state === 'loaded') return nothing;\n if (retryLabel !== undefined && loading) {\n return html`<button\n part=\"retry\"\n type=\"button\"\n tabindex=\"-1\"\n data-row=${index}\n aria-label=${retryLabel}\n @click=${loading.onRetry}\n >\n Retry\n </button>`;\n }\n return html`<div\n part=${state === 'error' ? 'skeleton skeleton-error' : 'skeleton'}\n aria-hidden=\"true\"\n ></div>`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0CA,SAAgB,WACd,MACA,SACA,WACA,QACA,eACA,SACA,YACA,YACA,QAAQ,OACR;CACA,OAAO,IAAI;;;YAGD,SAAS,EAAE,WAAW,GAAG,OAAO,SAAS,EAAE,IAAI,CAAC,EAAE;;MAExD,OACA,OACC,MAAM,EAAE,OACR,MACC,UACE,GACA,SACA,EAAE,UAAU,QAAQ,QAAQ,OAAO,MAAM,KAAA,GACzC,eACA,SACA,aAAa,EAAE,KAAK,GACpB,YACA,KACF,CACJ,EAAE;;AAEN;;;;;;;;AASA,SAAS,UACP,KACA,SACA,WACA,eACA,SACA,UACA,YACA,OACA;CACA,MAAM,QAAQ,SAAS,QAAQ,IAAI,KAAK,KAAK;CAC7C,IAAI,QAAQ,IAAI,QAAQ,MAAM,IAAI,iBAAiB;CACnD,IAAI,UAAU,UAAU,SAAS,UAAU,UAAU,2BAA2B;CAChF,IAAI,UAAU,SAAS;CACvB,MAAM,QAAQ,UAAU,UAAU,SAAS,MAAM,IAAI,IAAI,KAAK,IAAI,KAAA;CAElE,MAAM,WAAW,UAAU,KAAA,IAAY,KAAA,IAAY,gBAAgB,OAAO;CAC1E,OAAO,IAAI;WACF,MAAM;;;oBAGG,IAAI,QAAQ,cAAc;gBAC9B,UAAU,YAAY,SAAS,QAAQ;oBACnC,aAAa,KAAA,IAAY,UAAU,OAAO,QAAQ,EAAE;YAC5D,SACN,QACI;EACE,iBAAiB,GAAG,OAAO,IAAI,KAAK,EAAE;EACtC,cAAc,IAAI,SAAS,KAAA,IAAY,KAAA,IAAY,GAAG,OAAO,IAAI,IAAI,EAAE;CACzE,IACA,EAAE,WAAW,cAAc,OAAO,IAAI,KAAK,EAAE,KAAK,CACxD,EAAE;;MAEA,OACA,UACC,MAAM,EAAE,MACR,MACC,WACE,GACA,EAAE,OAAO,WACT,SAAS,IAAI,MAAM,EAAE,IAAI,GACzB,OACA,EAAE,SAAS,cACP,YAAY,IAAI,OAAO,UAAU,UAAU,IAC3C,YAAY,OAAO,EAAE,OAAO,WAAW,QAAQ,KAAA,GAAW,IAAI,OAAO,OAAO,CAClF,CACJ,EAAE;;AAEN;;;;;;AAOA,SAAgB,WACd,GACA,SACA,MACA,WACA,SAAkB,SAClB;CACA,OAAO,IAAI;WACF,UAAU,GAAG,SAAS,EAAE;;oBAEf,EAAE,SAAS;eAChB,UAAU,IAAI,GAAG;eACjB,EAAE,GAAG;iBACH,EAAE,MAAM;gBACT,EAAE,OAAO,KAAK,QAAQ;eACvB,EAAE,OAAO,QAAQ;YACpB,UAAU,CAAC,KAAK,QAAQ;;MAE9B,OAAO,aAAa,KAAK;;AAE/B;;;;;AAMA,SAAS,gBAAmB,SAAyC;CACnE,IAAI;CACJ,KAAK,MAAM,KAAK,SACd,IAAI,EAAE,SAAS,WAAW,CAAC,SAAS,EAAE,WAAW,MAAM,WAAW,QAAQ;CAE5E,OAAO,OAAO;AAChB;;;;;;AAOA,SAAS,YACP,OACA,UACA,YACA;CACA,IAAI,aAAa,KAAA,KAAa,CAAC,YAAY,OAAO;CAClD,OAAO,IAAI;;;;;eAKE,MAAM;eACN,KAAK,QAAQ,EAAE;aACjB,WAAW,IAAI;iBACX,WAAW,MAAM;;AAElC;;AAGA,SAAS,YACP,OACA,YACA,OACA,SACA;CACA,IAAI,UAAU,UAAU,OAAO;CAC/B,IAAI,eAAe,KAAA,KAAa,SAC9B,OAAO,IAAI;;;;iBAIE,MAAM;mBACJ,WAAW;eACf,QAAQ,QAAQ;;;;CAK7B,OAAO,IAAI;WACF,UAAU,UAAU,4BAA4B,WAAW;;;AAGtE"}
|
package/dist/render/cells.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cellSlot, formatValue, headerSlot, pinnedSlot } from "./content.js";
|
|
1
|
+
import { cellSlot, formatValue, groupSlot, headerSlot, pinnedSlot } from "./content.js";
|
|
2
2
|
import { content } from "./content-directive.js";
|
|
3
3
|
import { html, render } from "lit";
|
|
4
4
|
import { repeat } from "lit/directives/repeat.js";
|
|
@@ -11,15 +11,23 @@ import { repeat } from "lit/directives/repeat.js";
|
|
|
11
11
|
* outside it, wherever they are added. `rendered` are the columns the shadow
|
|
12
12
|
* root renders, with their slots and keys. What `header` and `cell` return goes
|
|
13
13
|
* through `content()`, so foreign content reaches the table's `contentRenderer`.
|
|
14
|
+
* Group headers come first, one per group and pin side (`groups`, the header's
|
|
15
|
+
* group rows).
|
|
14
16
|
* Returns the part, which the table disconnects when it is disconnected.
|
|
15
17
|
*/
|
|
16
|
-
function renderLightDom(host, model, rows, rendered, renderBefore) {
|
|
18
|
+
function renderLightDom(host, model, rows, rendered, groups, renderBefore) {
|
|
17
19
|
const columns = rendered.filter((c) => c.kind === "data");
|
|
18
|
-
|
|
20
|
+
const groupCells = groups.flat().filter((g) => g.kind === "group");
|
|
21
|
+
return render(html`${repeat(groupCells, (g) => g.key, (g) => html`<div slot=${groupSlot(g.slot)}>${groupContent(g)}</div>`)}${repeat(columns, (c) => c.key, (c) => html`<div slot=${headerSlot(c.slot)}>${headerContent(c)}</div>`)}${pinnedContent(model, columns, "top")}${repeat(rows, (r) => r.slot, (r) => repeat(columns, (c) => c.key, (c) => html`<div slot=${cellSlot(r.slot, c.slot)}>${cellContent(model, r.index, c)}</div>`))}${pinnedContent(model, columns, "bottom")}`, host, {
|
|
19
22
|
host,
|
|
20
23
|
renderBefore
|
|
21
24
|
});
|
|
22
25
|
}
|
|
26
|
+
/** A group's header: its own content, else its id. */
|
|
27
|
+
function groupContent(group) {
|
|
28
|
+
const { header } = group;
|
|
29
|
+
return content((typeof header === "function" ? header() : header) ?? group.groupId);
|
|
30
|
+
}
|
|
23
31
|
function headerContent(column) {
|
|
24
32
|
const { header } = column;
|
|
25
33
|
return content(typeof header === "function" ? header() : header);
|
package/dist/render/cells.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cells.js","names":[],"sources":["../../src/render/cells.ts"],"sourcesContent":["import { html, render, type RootPart } from 'lit';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { ColumnView, RowPin, TableModel } from '@argus-table/core';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport type { CellContent } from '../types.js';\nimport { cellSlot, formatValue, headerSlot, pinnedSlot } from './content.js';\nimport { content } from './content-directive.js';\n\ntype Column<T> = ColumnView<T, CellContent>;\n\n/**\n * Renders header and cell content into the host's light DOM, as direct children\n * (a slot only takes the host's own children). Keyed and ordered by pool slot,\n * so recycled content updates in place instead of moving (ADR 0001). The part\n * ends at `renderBefore`, so the host's other children (`<argus-column>`) stay\n * outside it, wherever they are added. `rendered` are the columns the shadow\n * root renders, with their slots and keys. What `header` and `cell` return goes\n * through `content()`, so foreign content reaches the table's `contentRenderer`.\n * Returns the part, which the table disconnects when it is disconnected.\n */\nexport function renderLightDom<T>(\n host: HTMLElement,\n model: TableModel<T, CellContent>,\n rows: readonly WindowRow[],\n rendered: readonly RenderedColumn<T>[],\n renderBefore: ChildNode,\n): RootPart {\n // The selection column renders its checkboxes in the shadow root: no content here.\n const columns = rendered.filter((c) => c.kind === 'data');\n return render(\n html`${repeat(\n columns,\n (c) => c.key,\n (c) => html`<div slot=${headerSlot(c.slot)}>${headerContent(c)}</div>`,\n )}${pinnedContent(model, columns, 'top')}${repeat(\n rows,\n (r) => r.slot,\n (r) =>\n repeat(\n columns,\n (c) => c.key,\n (c) =>\n html`<div slot=${cellSlot(r.slot, c.slot)}>${cellContent(model, r.index, c)}</div>`,\n ),\n )}${pinnedContent(model, columns, 'bottom')}`,\n host,\n { host, renderBefore },\n );\n}\n\nfunction headerContent<T>(column: Column<T>): unknown {\n const { header } = column;\n return content(typeof header === 'function' ? header() : header);\n}\n\n/** Content of the rows pinned to one side, slotted by side, index and column. */\nfunction pinnedContent<T>(\n model: TableModel<T, CellContent>,\n columns: readonly RenderedColumn<T>[],\n side: RowPin,\n) {\n const rows = Array.from({ length: model.pinnedCount(side) }, (_, i) => i);\n return rows.map((i) =>\n repeat(\n columns,\n (c) => c.key,\n (c) => html`<div slot=${pinnedSlot(side, i, c.slot)}>${pinnedCell(model, side, i, c)}</div>`,\n ),\n );\n}\n\nfunction pinnedCell<T>(\n model: TableModel<T, CellContent>,\n side: RowPin,\n index: number,\n column: Column<T>,\n) {\n if (column.cell) {\n const context = model.pinnedCellContext(side, index, column.id);\n return context ? content(column.cell(context.row, context)) : '';\n }\n return column.hasAccessor ? formatValue(model.pinnedValueAt(side, index, column.id)) : '';\n}\n\nfunction cellContent<T>(model: TableModel<T, CellContent>, index: number, column: Column<T>) {\n if (column.cell) {\n const context = model.cellContext(index, column.id);\n return context ? content(column.cell(context.row, context)) : '';\n }\n return column.hasAccessor ? formatValue(model.valueAt(index, column.id)) : '';\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"cells.js","names":[],"sources":["../../src/render/cells.ts"],"sourcesContent":["import { html, render, type RootPart } from 'lit';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { ColumnView, HeaderGroupCell, RowPin, TableModel } from '@argus-table/core';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport type { CellContent } from '../types.js';\nimport { cellSlot, formatValue, groupSlot, headerSlot, pinnedSlot } from './content.js';\nimport { content } from './content-directive.js';\n\ntype Column<T> = ColumnView<T, CellContent>;\n\n/**\n * Renders header and cell content into the host's light DOM, as direct children\n * (a slot only takes the host's own children). Keyed and ordered by pool slot,\n * so recycled content updates in place instead of moving (ADR 0001). The part\n * ends at `renderBefore`, so the host's other children (`<argus-column>`) stay\n * outside it, wherever they are added. `rendered` are the columns the shadow\n * root renders, with their slots and keys. What `header` and `cell` return goes\n * through `content()`, so foreign content reaches the table's `contentRenderer`.\n * Group headers come first, one per group and pin side (`groups`, the header's\n * group rows).\n * Returns the part, which the table disconnects when it is disconnected.\n */\nexport function renderLightDom<T>(\n host: HTMLElement,\n model: TableModel<T, CellContent>,\n rows: readonly WindowRow[],\n rendered: readonly RenderedColumn<T>[],\n groups: readonly (readonly HeaderGroupCell<CellContent>[])[],\n renderBefore: ChildNode,\n): RootPart {\n // The selection column renders its checkboxes in the shadow root: no content here.\n const columns = rendered.filter((c) => c.kind === 'data');\n const groupCells = groups.flat().filter((g) => g.kind === 'group');\n return render(\n html`${repeat(\n groupCells,\n (g) => g.key,\n (g) => html`<div slot=${groupSlot(g.slot)}>${groupContent(g)}</div>`,\n )}${repeat(\n columns,\n (c) => c.key,\n (c) => html`<div slot=${headerSlot(c.slot)}>${headerContent(c)}</div>`,\n )}${pinnedContent(model, columns, 'top')}${repeat(\n rows,\n (r) => r.slot,\n (r) =>\n repeat(\n columns,\n (c) => c.key,\n (c) =>\n html`<div slot=${cellSlot(r.slot, c.slot)}>${cellContent(model, r.index, c)}</div>`,\n ),\n )}${pinnedContent(model, columns, 'bottom')}`,\n host,\n { host, renderBefore },\n );\n}\n\n/** A group's header: its own content, else its id. */\nfunction groupContent(group: HeaderGroupCell<CellContent>): unknown {\n const { header } = group;\n return content((typeof header === 'function' ? header() : header) ?? group.groupId);\n}\n\nfunction headerContent<T>(column: Column<T>): unknown {\n const { header } = column;\n return content(typeof header === 'function' ? header() : header);\n}\n\n/** Content of the rows pinned to one side, slotted by side, index and column. */\nfunction pinnedContent<T>(\n model: TableModel<T, CellContent>,\n columns: readonly RenderedColumn<T>[],\n side: RowPin,\n) {\n const rows = Array.from({ length: model.pinnedCount(side) }, (_, i) => i);\n return rows.map((i) =>\n repeat(\n columns,\n (c) => c.key,\n (c) => html`<div slot=${pinnedSlot(side, i, c.slot)}>${pinnedCell(model, side, i, c)}</div>`,\n ),\n );\n}\n\nfunction pinnedCell<T>(\n model: TableModel<T, CellContent>,\n side: RowPin,\n index: number,\n column: Column<T>,\n) {\n if (column.cell) {\n const context = model.pinnedCellContext(side, index, column.id);\n return context ? content(column.cell(context.row, context)) : '';\n }\n return column.hasAccessor ? formatValue(model.pinnedValueAt(side, index, column.id)) : '';\n}\n\nfunction cellContent<T>(model: TableModel<T, CellContent>, index: number, column: Column<T>) {\n if (column.cell) {\n const context = model.cellContext(index, column.id);\n return context ? content(column.cell(context.row, context)) : '';\n }\n return column.hasAccessor ? formatValue(model.valueAt(index, column.id)) : '';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,SAAgB,eACd,MACA,OACA,MACA,UACA,QACA,cACU;CAEV,MAAM,UAAU,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM;CACxD,MAAM,aAAa,OAAO,KAAK,CAAC,CAAC,QAAQ,MAAM,EAAE,SAAS,OAAO;CACjE,OAAO,OACL,IAAI,GAAG,OACL,aACC,MAAM,EAAE,MACR,MAAM,IAAI,aAAa,UAAU,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,EAAE,OAC/D,IAAI,OACF,UACC,MAAM,EAAE,MACR,MAAM,IAAI,aAAa,WAAW,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC,EAAE,OACjE,IAAI,cAAc,OAAO,SAAS,KAAK,IAAI,OACzC,OACC,MAAM,EAAE,OACR,MACC,OACE,UACC,MAAM,EAAE,MACR,MACC,IAAI,aAAa,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,OAAO,EAAE,OAAO,CAAC,EAAE,OAChF,CACJ,IAAI,cAAc,OAAO,SAAS,QAAQ,KAC1C,MACA;EAAE;EAAM;CAAa,CACvB;AACF;;AAGA,SAAS,aAAa,OAA8C;CAClE,MAAM,EAAE,WAAW;CACnB,OAAO,SAAS,OAAO,WAAW,aAAa,OAAO,IAAI,WAAW,MAAM,OAAO;AACpF;AAEA,SAAS,cAAiB,QAA4B;CACpD,MAAM,EAAE,WAAW;CACnB,OAAO,QAAQ,OAAO,WAAW,aAAa,OAAO,IAAI,MAAM;AACjE;;AAGA,SAAS,cACP,OACA,SACA,MACA;CAEA,OADa,MAAM,KAAK,EAAE,QAAQ,MAAM,YAAY,IAAI,EAAE,IAAI,GAAG,MAAM,CAChE,CAAA,CAAK,KAAK,MACf,OACE,UACC,MAAM,EAAE,MACR,MAAM,IAAI,aAAa,WAAW,MAAM,GAAG,EAAE,IAAI,EAAE,GAAG,WAAW,OAAO,MAAM,GAAG,CAAC,EAAE,OACvF,CACF;AACF;AAEA,SAAS,WACP,OACA,MACA,OACA,QACA;CACA,IAAI,OAAO,MAAM;EACf,MAAM,UAAU,MAAM,kBAAkB,MAAM,OAAO,OAAO,EAAE;EAC9D,OAAO,UAAU,QAAQ,OAAO,KAAK,QAAQ,KAAK,OAAO,CAAC,IAAI;CAChE;CACA,OAAO,OAAO,cAAc,YAAY,MAAM,cAAc,MAAM,OAAO,OAAO,EAAE,CAAC,IAAI;AACzF;AAEA,SAAS,YAAe,OAAmC,OAAe,QAAmB;CAC3F,IAAI,OAAO,MAAM;EACf,MAAM,UAAU,MAAM,YAAY,OAAO,OAAO,EAAE;EAClD,OAAO,UAAU,QAAQ,OAAO,KAAK,QAAQ,KAAK,OAAO,CAAC,IAAI;CAChE;CACA,OAAO,OAAO,cAAc,YAAY,MAAM,QAAQ,OAAO,OAAO,EAAE,CAAC,IAAI;AAC7E"}
|
|
@@ -52,7 +52,7 @@ var ContentDirective = class extends AsyncDirective {
|
|
|
52
52
|
if (!isForeign(value)) {
|
|
53
53
|
this.#release();
|
|
54
54
|
this.#content = void 0;
|
|
55
|
-
return
|
|
55
|
+
return toChild(value);
|
|
56
56
|
}
|
|
57
57
|
const host = part.options?.host;
|
|
58
58
|
const renderer = host?.contentRenderer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-directive.js","names":[],"sources":["../../src/render/content-directive.ts"],"sourcesContent":["import { nothing, type ChildPart } from 'lit';\nimport { AsyncDirective, directive, PartType, type PartInfo } from 'lit/async-directive.js';\nimport { toChild } from './content.js';\nimport type { CellContent } from '../types.js';\n\n/**\n * Renders content the table doesn't render itself, such as React elements, Vue\n * VNodes or Angular templates, into a target element the table keeps. Set it as the\n * table's `contentRenderer`; the framework wrappers do.\n */\nexport interface ContentRenderer {\n /**\n * Renders `content` into `target`, or updates what is there. Called on every update\n * for each header and cell showing foreign content. A target is reused: when the\n * table scrolls, the same target shows other rows.\n */\n render(target: HTMLElement, content: unknown): void;\n /**\n * `target` shows no foreign content any more: its cell scrolled away and wasn't\n * reused, its row isn't loaded, its column left, or the table was disconnected.\n * Unmount what `render` put there.\n */\n release(target: HTMLElement): void;\n /**\n * Called once after each update in which `render` or `release` ran, before the\n * table measures content (`width: 'auto'`). Commit batched work here, synchronously.\n */\n flush?(): void;\n}\n\n/** What the directive reads from the table it renders into. */\ninterface ContentHost {\n readonly contentRenderer?: ContentRenderer | undefined;\n}\n\n/** Renderers that rendered or released since the last flush, per table. */\nconst dirty = new WeakMap<object, Set<ContentRenderer>>();\nlet warned = false;\n\nfunction markDirty(host: object, renderer: ContentRenderer): void {\n let set = dirty.get(host);\n if (!set) dirty.set(host, (set = new Set()));\n set.add(renderer);\n}\n\n/** Calls `flush()` on the renderers that rendered or released for `host` since the last call. */\nexport function flushContent(host: object): void {\n const set = dirty.get(host);\n if (!set?.size) return;\n const renderers = [...set];\n set.clear();\n for (const renderer of renderers) renderer.flush?.();\n}\n\n/**\n * Whether the table renders `value` itself: strings, numbers, Lit templates and\n * directive results, DOM nodes, `nothing` and empty values, and arrays of those.\n * Anything else is foreign: it goes to the `contentRenderer`.\n */\nexport function isForeign(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n if (Array.isArray(value)) return value.some(isForeign);\n if (value instanceof Node) return false;\n return !('_$litType$' in value) && !('_$litDirective$' in value);\n}\n\n/**\n * Header and cell content. Foreign content renders through the table's\n * `contentRenderer` into a target element kept while this part lives, so a pooled\n * cell updates its framework content in place as it shows other rows.\n */\nclass ContentDirective extends AsyncDirective {\n #target: HTMLElement | undefined;\n /** The renderer that rendered into the target, and the table it belongs to. */\n #renderer: ContentRenderer | undefined;\n #host: object | undefined;\n /** The last foreign content, rendered again on reconnect. */\n #content: unknown;\n\n constructor(part: PartInfo) {\n super(part);\n if (part.type !== PartType.CHILD) {\n throw new Error('argus-table: content() only works in child positions.');\n }\n }\n\n render(value: unknown): unknown {\n return isForeign(value) ? nothing : toChild(value as CellContent | null | undefined);\n }\n\n override update(part: ChildPart, [value]: [unknown]): unknown {\n if (!isForeign(value)) {\n this.#release();\n this.#content = undefined;\n return
|
|
1
|
+
{"version":3,"file":"content-directive.js","names":[],"sources":["../../src/render/content-directive.ts"],"sourcesContent":["import { nothing, type ChildPart } from 'lit';\nimport { AsyncDirective, directive, PartType, type PartInfo } from 'lit/async-directive.js';\nimport { toChild } from './content.js';\nimport type { CellContent } from '../types.js';\n\n/**\n * Renders content the table doesn't render itself, such as React elements, Vue\n * VNodes or Angular templates, into a target element the table keeps. Set it as the\n * table's `contentRenderer`; the framework wrappers do.\n */\nexport interface ContentRenderer {\n /**\n * Renders `content` into `target`, or updates what is there. Called on every update\n * for each header and cell showing foreign content. A target is reused: when the\n * table scrolls, the same target shows other rows.\n */\n render(target: HTMLElement, content: unknown): void;\n /**\n * `target` shows no foreign content any more: its cell scrolled away and wasn't\n * reused, its row isn't loaded, its column left, or the table was disconnected.\n * Unmount what `render` put there.\n */\n release(target: HTMLElement): void;\n /**\n * Called once after each update in which `render` or `release` ran, before the\n * table measures content (`width: 'auto'`). Commit batched work here, synchronously.\n */\n flush?(): void;\n}\n\n/** What the directive reads from the table it renders into. */\ninterface ContentHost {\n readonly contentRenderer?: ContentRenderer | undefined;\n}\n\n/** Renderers that rendered or released since the last flush, per table. */\nconst dirty = new WeakMap<object, Set<ContentRenderer>>();\nlet warned = false;\n\nfunction markDirty(host: object, renderer: ContentRenderer): void {\n let set = dirty.get(host);\n if (!set) dirty.set(host, (set = new Set()));\n set.add(renderer);\n}\n\n/** Calls `flush()` on the renderers that rendered or released for `host` since the last call. */\nexport function flushContent(host: object): void {\n const set = dirty.get(host);\n if (!set?.size) return;\n const renderers = [...set];\n set.clear();\n for (const renderer of renderers) renderer.flush?.();\n}\n\n/**\n * Whether the table renders `value` itself: strings, numbers, Lit templates and\n * directive results, DOM nodes, `nothing` and empty values, and arrays of those.\n * Anything else is foreign: it goes to the `contentRenderer`.\n */\nexport function isForeign(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n if (Array.isArray(value)) return value.some(isForeign);\n if (value instanceof Node) return false;\n return !('_$litType$' in value) && !('_$litDirective$' in value);\n}\n\n/**\n * Header and cell content. Foreign content renders through the table's\n * `contentRenderer` into a target element kept while this part lives, so a pooled\n * cell updates its framework content in place as it shows other rows.\n */\nclass ContentDirective extends AsyncDirective {\n #target: HTMLElement | undefined;\n /** The renderer that rendered into the target, and the table it belongs to. */\n #renderer: ContentRenderer | undefined;\n #host: object | undefined;\n /** The last foreign content, rendered again on reconnect. */\n #content: unknown;\n\n constructor(part: PartInfo) {\n super(part);\n if (part.type !== PartType.CHILD) {\n throw new Error('argus-table: content() only works in child positions.');\n }\n }\n\n render(value: unknown): unknown {\n return isForeign(value) ? nothing : toChild(value as CellContent | null | undefined);\n }\n\n override update(part: ChildPart, [value]: [unknown]): unknown {\n if (!isForeign(value)) {\n this.#release();\n this.#content = undefined;\n return toChild(value as CellContent | null | undefined);\n }\n const host = part.options?.host;\n const renderer = (host as ContentHost | undefined)?.contentRenderer;\n if (!host || !renderer) {\n this.#release();\n if (!warned) {\n warned = true;\n console.warn(\n 'argus-table: a header or cell returned content the table can’t render (set contentRenderer, or use a framework wrapper).',\n );\n }\n return nothing;\n }\n if (renderer !== this.#renderer) this.#release();\n const target = (this.#target ??= targetElement());\n this.#renderer = renderer;\n this.#host = host;\n this.#content = value;\n renderer.render(target, value);\n markDirty(host, renderer);\n return target;\n }\n\n protected override disconnected(): void {\n const content = this.#content;\n this.#release();\n // Kept for reconnected(): the part is the same, and so is its content.\n this.#content = content;\n }\n\n protected override reconnected(): void {\n const host = this.#host;\n const renderer = (host as ContentHost | undefined)?.contentRenderer;\n if (!host || !renderer || this.#content === undefined || !this.#target) return;\n this.#renderer = renderer;\n renderer.render(this.#target, this.#content);\n markDirty(host, renderer);\n }\n\n #release(): void {\n const renderer = this.#renderer;\n const host = this.#host;\n if (!renderer || !host || !this.#target) return;\n this.#renderer = undefined;\n renderer.release(this.#target);\n markDirty(host, renderer);\n }\n}\n\n/** A target: invisible to layout, so the content lays out as the cell's own. */\nfunction targetElement(): HTMLElement {\n const target = document.createElement('div');\n target.className = 'argus-content';\n target.style.display = 'contents';\n return target;\n}\n\n/**\n * Header or cell content for a template of your own. The table renders what\n * `header` and `cell` return through it already; a plugin that nests a column's\n * content in its own template (`html\\`<b>${content(own)}</b>\\``) wraps it, so\n * framework content (React elements, Vue VNodes) still reaches the\n * `contentRenderer`. Only in templates the table renders.\n */\nexport const content = directive(ContentDirective);\n"],"mappings":";;;;;AAoCA,IAAM,wBAAQ,IAAI,QAAsC;AACxD,IAAI,SAAS;AAEb,SAAS,UAAU,MAAc,UAAiC;CAChE,IAAI,MAAM,MAAM,IAAI,IAAI;CACxB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAO,sBAAM,IAAI,IAAI,CAAE;CAC3C,IAAI,IAAI,QAAQ;AAClB;;AAGA,SAAgB,aAAa,MAAoB;CAC/C,MAAM,MAAM,MAAM,IAAI,IAAI;CAC1B,IAAI,CAAC,KAAK,MAAM;CAChB,MAAM,YAAY,CAAC,GAAG,GAAG;CACzB,IAAI,MAAM;CACV,KAAK,MAAM,YAAY,WAAW,SAAS,QAAQ;AACrD;;;;;;AAOA,SAAgB,UAAU,OAAyB;CACjD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,SAAS;CACrD,IAAI,iBAAiB,MAAM,OAAO;CAClC,OAAO,EAAE,gBAAgB,UAAU,EAAE,qBAAqB;AAC5D;;;;;;AAOA,IAAM,mBAAN,cAA+B,eAAe;CAC5C;;CAEA;CACA;;CAEA;CAEA,YAAY,MAAgB;EAC1B,MAAM,IAAI;EACV,IAAI,KAAK,SAAS,SAAS,OACzB,MAAM,IAAI,MAAM,uDAAuD;CAE3E;CAEA,OAAO,OAAyB;EAC9B,OAAO,UAAU,KAAK,IAAI,UAAU,QAAQ,KAAuC;CACrF;CAEA,OAAgB,MAAiB,CAAC,QAA4B;EAC5D,IAAI,CAAC,UAAU,KAAK,GAAG;GACrB,KAAK,SAAS;GACd,KAAK,WAAW,KAAA;GAChB,OAAO,QAAQ,KAAuC;EACxD;EACA,MAAM,OAAO,KAAK,SAAS;EAC3B,MAAM,WAAY,MAAkC;EACpD,IAAI,CAAC,QAAQ,CAAC,UAAU;GACtB,KAAK,SAAS;GACd,IAAI,CAAC,QAAQ;IACX,SAAS;IACT,QAAQ,KACN,0HACF;GACF;GACA,OAAO;EACT;EACA,IAAI,aAAa,KAAK,WAAW,KAAK,SAAS;EAC/C,MAAM,SAAU,KAAK,YAAY,cAAc;EAC/C,KAAK,YAAY;EACjB,KAAK,QAAQ;EACb,KAAK,WAAW;EAChB,SAAS,OAAO,QAAQ,KAAK;EAC7B,UAAU,MAAM,QAAQ;EACxB,OAAO;CACT;CAEA,eAAwC;EACtC,MAAM,UAAU,KAAK;EACrB,KAAK,SAAS;EAEd,KAAK,WAAW;CAClB;CAEA,cAAuC;EACrC,MAAM,OAAO,KAAK;EAClB,MAAM,WAAY,MAAkC;EACpD,IAAI,CAAC,QAAQ,CAAC,YAAY,KAAK,aAAa,KAAA,KAAa,CAAC,KAAK,SAAS;EACxE,KAAK,YAAY;EACjB,SAAS,OAAO,KAAK,SAAS,KAAK,QAAQ;EAC3C,UAAU,MAAM,QAAQ;CAC1B;CAEA,WAAiB;EACf,MAAM,WAAW,KAAK;EACtB,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,SAAS;EACzC,KAAK,YAAY,KAAA;EACjB,SAAS,QAAQ,KAAK,OAAO;EAC7B,UAAU,MAAM,QAAQ;CAC1B;AACF;;AAGA,SAAS,gBAA6B;CACpC,MAAM,SAAS,SAAS,cAAc,KAAK;CAC3C,OAAO,YAAY;CACnB,OAAO,MAAM,UAAU;CACvB,OAAO;AACT;;;;;;;;AASA,IAAa,UAAU,UAAU,gBAAgB"}
|
package/dist/render/content.js
CHANGED
|
@@ -13,18 +13,21 @@ function formatValue(value) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Private slot names (ADR 0001): cells by row pool slot and column slot,
|
|
16
|
-
* headers by column slot,
|
|
16
|
+
* headers by column slot, group headers by group and pin side, pinned rows by side,
|
|
17
|
+
* index and column slot. A
|
|
17
18
|
* column's slot is its `colIndex`, or its pool slot with `virtualizeColumns`.
|
|
18
19
|
*/
|
|
19
20
|
var cellSlot = (slot, colSlot) => `c-${String(slot)}-${String(colSlot)}`;
|
|
20
21
|
var headerSlot = (colSlot) => `h-${String(colSlot)}`;
|
|
22
|
+
/** A column group's header, by the group's slot number (`HeaderGroupCell.slot`). */
|
|
23
|
+
var groupSlot = (slot) => `g-${String(slot)}`;
|
|
21
24
|
var pinnedSlot = (side, index, colSlot) => `${side === "top" ? "t" : "b"}-${String(index)}-${String(colSlot)}`;
|
|
22
25
|
/**
|
|
23
26
|
* Part list of a body or pinned-row cell: state is in the part name (`::part()`
|
|
24
27
|
* can't take attribute selectors).
|
|
25
28
|
*/
|
|
26
29
|
function cellParts(column, pinnedRow = false) {
|
|
27
|
-
let parts = pinnedRow ? "cell cell-pinned-row" : "cell";
|
|
30
|
+
let parts = pinnedRow ? "cell cell-in-pinned-row" : "cell";
|
|
28
31
|
if (column.kind === "selection") parts += " cell-selection";
|
|
29
32
|
if (column.pin) parts += column.pinEdge ? " cell-pinned cell-pinned-edge" : " cell-pinned";
|
|
30
33
|
if (column.sorted) parts += " cell-sorted";
|
|
@@ -48,6 +51,15 @@ function cellStyle(column) {
|
|
|
48
51
|
const place = `grid-area: 1 / ${String(column.place)}`;
|
|
49
52
|
return pin ? `${pin}; ${place}` : place;
|
|
50
53
|
}
|
|
51
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Inline style of a group header row's cell: the columns it spans, placed
|
|
56
|
+
* explicitly (the rows are grids on every column's track), and a pinned one's
|
|
57
|
+
* sticky inset.
|
|
58
|
+
*/
|
|
59
|
+
function groupStyle(cell) {
|
|
60
|
+
const place = `grid-column: ${String(cell.colIndex)} / span ${String(cell.span)}`;
|
|
61
|
+
return cell.pin ? `--_pin-offset: ${String(cell.offset)}px; ${place}` : place;
|
|
62
|
+
}
|
|
63
|
+
export { ariaSort, cellParts, cellSlot, cellStyle, formatValue, groupSlot, groupStyle, headerSlot, pinnedSlot, toChild };
|
|
52
64
|
|
|
53
65
|
//# sourceMappingURL=content.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","names":[],"sources":["../../src/render/content.ts"],"sourcesContent":["import { nothing } from 'lit';\nimport type { CellContent } from '../types.js';\n\n/** Turns header or cell content into a Lit child. */\nexport function toChild(content: CellContent | null | undefined): unknown {\n if (content === null || content === undefined) return nothing;\n return typeof content === 'number' ? String(content) : content;\n}\n\n/** Default cell text for an accessor value. */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (value instanceof Date) return value.toLocaleString();\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- objects are handled above\n return typeof value === 'object' ? JSON.stringify(value) : String(value);\n}\n\n/**\n * Private slot names (ADR 0001): cells by row pool slot and column slot,\n * headers by column slot, pinned rows by side
|
|
1
|
+
{"version":3,"file":"content.js","names":[],"sources":["../../src/render/content.ts"],"sourcesContent":["import { nothing } from 'lit';\nimport type { CellContent } from '../types.js';\n\n/** Turns header or cell content into a Lit child. */\nexport function toChild(content: CellContent | null | undefined): unknown {\n if (content === null || content === undefined) return nothing;\n return typeof content === 'number' ? String(content) : content;\n}\n\n/** Default cell text for an accessor value. */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (value instanceof Date) return value.toLocaleString();\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- objects are handled above\n return typeof value === 'object' ? JSON.stringify(value) : String(value);\n}\n\n/**\n * Private slot names (ADR 0001): cells by row pool slot and column slot,\n * headers by column slot, group headers by group and pin side, pinned rows by side,\n * index and column slot. A\n * column's slot is its `colIndex`, or its pool slot with `virtualizeColumns`.\n */\nexport const cellSlot = (slot: number, colSlot: number) => `c-${String(slot)}-${String(colSlot)}`;\nexport const headerSlot = (colSlot: number) => `h-${String(colSlot)}`;\n/** A column group's header, by the group's slot number (`HeaderGroupCell.slot`). */\nexport const groupSlot = (slot: number) => `g-${String(slot)}`;\nexport const pinnedSlot = (side: 'top' | 'bottom', index: number, colSlot: number) =>\n `${side === 'top' ? 't' : 'b'}-${String(index)}-${String(colSlot)}`;\n\ninterface Pinnable {\n pin: 'start' | 'end' | undefined;\n pinEdge: boolean;\n}\n\n/**\n * Part list of a body or pinned-row cell: state is in the part name (`::part()`\n * can't take attribute selectors).\n */\nexport function cellParts(\n column: Pinnable & { sorted: 'asc' | 'desc' | false; kind: 'data' | 'selection' },\n pinnedRow = false,\n): string {\n let parts = pinnedRow ? 'cell cell-in-pinned-row' : 'cell';\n if (column.kind === 'selection') parts += ' cell-selection';\n if (column.pin) parts += column.pinEdge ? ' cell-pinned cell-pinned-edge' : ' cell-pinned';\n if (column.sorted) parts += ' cell-sorted';\n return parts;\n}\n\n/** Handlers of the selection checkboxes (`selectionCheckboxes`). */\nexport interface CheckboxHandlers {\n /** A row's checkbox; it carries the row index in `data-row`. */\n row: (event: MouseEvent) => void;\n /** The header's select-all checkbox. */\n all: (event: MouseEvent) => void;\n /** A press on any of them: keeps focus on its cell, not the checkbox. */\n press: (event: MouseEvent) => void;\n}\n\n/** `aria-sort` for a header: only the sorted column carries it (WAI-ARIA APG). */\nexport function ariaSort(sorted: 'asc' | 'desc' | false): 'ascending' | 'descending' | undefined {\n if (!sorted) return undefined;\n return sorted === 'asc' ? 'ascending' : 'descending';\n}\n\n/**\n * Inline style of a cell or header: a pinned one's sticky inset, from the\n * start or the end edge, and with `virtualizeColumns` the grid cell it is\n * placed in. Cells are in pool order there, not in column order, so the row is\n * explicit too: auto-placement would wrap a cell that comes after one of a\n * later column into a new grid row.\n */\nexport function cellStyle(\n column: Pinnable & { offset: number; place: number | undefined },\n): string | undefined {\n const pin = column.pin ? `--_pin-offset: ${String(column.offset)}px` : undefined;\n if (column.place === undefined) return pin;\n const place = `grid-area: 1 / ${String(column.place)}`;\n return pin ? `${pin}; ${place}` : place;\n}\n\n/**\n * Inline style of a group header row's cell: the columns it spans, placed\n * explicitly (the rows are grids on every column's track), and a pinned one's\n * sticky inset.\n */\nexport function groupStyle(\n cell: Pinnable & { offset: number; colIndex: number; span: number },\n): string {\n const place = `grid-column: ${String(cell.colIndex)} / span ${String(cell.span)}`;\n return cell.pin ? `--_pin-offset: ${String(cell.offset)}px; ${place}` : place;\n}\n"],"mappings":";;;AAIA,SAAgB,QAAQ,SAAkD;CACxE,IAAI,YAAY,QAAQ,YAAY,KAAA,GAAW,OAAO;CACtD,OAAO,OAAO,YAAY,WAAW,OAAO,OAAO,IAAI;AACzD;;AAGA,SAAgB,YAAY,OAAwB;CAClD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,iBAAiB,MAAM,OAAO,MAAM,eAAe;CAEvD,OAAO,OAAO,UAAU,WAAW,KAAK,UAAU,KAAK,IAAI,OAAO,KAAK;AACzE;;;;;;;AAQA,IAAa,YAAY,MAAc,YAAoB,KAAK,OAAO,IAAI,EAAE,GAAG,OAAO,OAAO;AAC9F,IAAa,cAAc,YAAoB,KAAK,OAAO,OAAO;;AAElE,IAAa,aAAa,SAAiB,KAAK,OAAO,IAAI;AAC3D,IAAa,cAAc,MAAwB,OAAe,YAChE,GAAG,SAAS,QAAQ,MAAM,IAAI,GAAG,OAAO,KAAK,EAAE,GAAG,OAAO,OAAO;;;;;AAWlE,SAAgB,UACd,QACA,YAAY,OACJ;CACR,IAAI,QAAQ,YAAY,4BAA4B;CACpD,IAAI,OAAO,SAAS,aAAa,SAAS;CAC1C,IAAI,OAAO,KAAK,SAAS,OAAO,UAAU,kCAAkC;CAC5E,IAAI,OAAO,QAAQ,SAAS;CAC5B,OAAO;AACT;;AAaA,SAAgB,SAAS,QAAwE;CAC/F,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO,WAAW,QAAQ,cAAc;AAC1C;;;;;;;;AASA,SAAgB,UACd,QACoB;CACpB,MAAM,MAAM,OAAO,MAAM,kBAAkB,OAAO,OAAO,MAAM,EAAE,MAAM,KAAA;CACvE,IAAI,OAAO,UAAU,KAAA,GAAW,OAAO;CACvC,MAAM,QAAQ,kBAAkB,OAAO,OAAO,KAAK;CACnD,OAAO,MAAM,GAAG,IAAI,IAAI,UAAU;AACpC;;;;;;AAOA,SAAgB,WACd,MACQ;CACR,MAAM,QAAQ,gBAAgB,OAAO,KAAK,QAAQ,EAAE,UAAU,OAAO,KAAK,IAAI;CAC9E,OAAO,KAAK,MAAM,kBAAkB,OAAO,KAAK,MAAM,EAAE,MAAM,UAAU;AAC1E"}
|
package/dist/render/header.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { ariaSort, cellStyle, headerSlot } from "./content.js";
|
|
1
|
+
import { ariaSort, cellStyle, groupSlot, groupStyle, headerSlot } from "./content.js";
|
|
2
2
|
import { html, nothing } from "lit";
|
|
3
3
|
import { live } from "lit/directives/live.js";
|
|
4
4
|
import { repeat } from "lit/directives/repeat.js";
|
|
5
5
|
// src/render/header.ts
|
|
6
|
+
/** The drop slot key of a column (`c:` and its id) or a group cell (its `key`). */
|
|
7
|
+
var columnKey = (columnId) => `c:${columnId}`;
|
|
6
8
|
/**
|
|
7
|
-
* The sticky header row
|
|
8
|
-
*
|
|
9
|
+
* The sticky header: a row per level of column groups, then the row of column
|
|
10
|
+
* headers. Header content is slotted from the light DOM. Pinned
|
|
11
|
+
* header cells use the same private `data-pin` hooks as body cells, and the parts
|
|
12
|
+
* `header-cell-pinned` and `header-cell-pinned-edge`.
|
|
9
13
|
* Every header carries `data-col` (its column id) and sortable ones also
|
|
10
14
|
* `data-sort-col`; the element's delegated handlers read them. Headers take part
|
|
11
15
|
* in the grid's roving tabindex: the active one is the tab stop (`activeCol`).
|
|
@@ -17,17 +21,18 @@ import { repeat } from "lit/directives/repeat.js";
|
|
|
17
21
|
* column. While one is dragged (`drag`), its header adds `header-cell-dragging`
|
|
18
22
|
* and the target header shows a `drop-indicator` on the edge it would land at.
|
|
19
23
|
*/
|
|
20
|
-
function renderHeader(columns, handlers,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
function renderHeader({ columns, groups, handlers, active, drag, checkAll }) {
|
|
25
|
+
const leaf = groups.length;
|
|
26
|
+
const activeCol = active?.index === leaf ? active.col : void 0;
|
|
27
|
+
return html`<div
|
|
28
|
+
part="header"
|
|
29
|
+
role="rowgroup"
|
|
30
|
+
@click=${handlers.click}
|
|
31
|
+
@pointerdown=${handlers.pointerdown}
|
|
32
|
+
@dblclick=${handlers.dblclick}
|
|
33
|
+
>
|
|
34
|
+
${groups.map((cells, level) => groupRow(cells, level, active, drag))}
|
|
35
|
+
<div part="header-row" class="row" role="row" aria-rowindex=${leaf + 1}>
|
|
31
36
|
${repeat(columns, (c) => c.key, (c) => html`<div
|
|
32
37
|
part=${headerParts(c, drag)}
|
|
33
38
|
role="columnheader"
|
|
@@ -43,10 +48,14 @@ function renderHeader(columns, handlers, activeCol, drag, checkAll) {
|
|
|
43
48
|
style=${cellStyle(c) ?? nothing}
|
|
44
49
|
>
|
|
45
50
|
${c.kind === "selection" ? selectAllCheckbox(checkAll) : html`<slot name=${headerSlot(c.slot)}></slot>`}
|
|
46
|
-
${c.sortable ? html`<span
|
|
51
|
+
${c.sortable ? html`<span
|
|
52
|
+
part="sort-indicator"
|
|
53
|
+
class="sort-indicator"
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
></span>` : nothing}
|
|
47
56
|
${c.filtered ? html`<span part="filter-indicator" aria-hidden="true"></span>` : nothing}
|
|
48
57
|
${c.resizable ? resizeHandle(c) : nothing}
|
|
49
|
-
${drag?.target?.
|
|
58
|
+
${drag?.target?.id === columnKey(c.id) ? html`<span
|
|
50
59
|
part="drop-indicator"
|
|
51
60
|
data-edge=${drag.target.edge}
|
|
52
61
|
aria-hidden="true"
|
|
@@ -56,12 +65,53 @@ function renderHeader(columns, handlers, activeCol, drag, checkAll) {
|
|
|
56
65
|
</div>`;
|
|
57
66
|
}
|
|
58
67
|
function headerParts(c, drag) {
|
|
59
|
-
let parts = c.sorted ?
|
|
68
|
+
let parts = c.sorted ? `header-cell header-cell-sorted header-cell-sorted-${c.sorted}` : "header-cell";
|
|
69
|
+
if (c.pin) parts += c.pinEdge ? " header-cell-pinned header-cell-pinned-edge" : " header-cell-pinned";
|
|
60
70
|
if (c.filtered) parts += " header-cell-filtered";
|
|
61
71
|
if (c.kind === "selection") parts += " header-cell-selection";
|
|
62
|
-
if (drag?.
|
|
72
|
+
if (drag?.columns.has(c.id)) parts += " header-cell-dragging";
|
|
63
73
|
return parts;
|
|
64
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A row of group headers. Every group cell renders, also with `virtualizeColumns`
|
|
77
|
+
* (there are few, and their slots stay put). A group's cell is a `columnheader`
|
|
78
|
+
* spanning its columns (`aria-colspan`), focusable through the roving tabindex,
|
|
79
|
+
* with `data-group-key`; columns without a group at this level sit under an empty
|
|
80
|
+
* filler `gridcell`, so every row covers every column. Neither carries `data-col`:
|
|
81
|
+
* they are no column's header. A group with a resizable column has a
|
|
82
|
+
* `resize-handle` too (`data-resize-group`).
|
|
83
|
+
*/
|
|
84
|
+
function groupRow(cells, level, active, drag) {
|
|
85
|
+
const activeCol = active?.index === level ? active.col : void 0;
|
|
86
|
+
return html`<div part="header-group-row" class="row" role="row" aria-rowindex=${level + 1}>
|
|
87
|
+
${repeat(cells, (c) => c.key, (c) => c.kind === "filler" ? html`<div
|
|
88
|
+
part="header-group-filler"
|
|
89
|
+
role="gridcell"
|
|
90
|
+
aria-colindex=${c.colIndex}
|
|
91
|
+
aria-colspan=${c.span}
|
|
92
|
+
data-pin=${c.pin ?? nothing}
|
|
93
|
+
style=${groupStyle(c)}
|
|
94
|
+
></div>` : html`<div
|
|
95
|
+
part=${drag?.key === c.key ? "header-group-cell header-group-cell-dragging" : "header-group-cell"}
|
|
96
|
+
role="columnheader"
|
|
97
|
+
aria-colindex=${c.colIndex}
|
|
98
|
+
aria-colspan=${c.span}
|
|
99
|
+
data-group-key=${c.key}
|
|
100
|
+
data-move-group=${c.movable ? c.key : nothing}
|
|
101
|
+
tabindex=${activeCol !== void 0 && c.columns.includes(activeCol) ? 0 : -1}
|
|
102
|
+
data-pin=${c.pin ?? nothing}
|
|
103
|
+
style=${groupStyle(c)}
|
|
104
|
+
>
|
|
105
|
+
<span class="group-label"><slot name=${groupSlot(c.slot)}></slot></span>
|
|
106
|
+
${c.resizable ? groupResizeHandle(c.key) : nothing}
|
|
107
|
+
${drag?.target?.id === c.key ? html`<span
|
|
108
|
+
part="drop-indicator"
|
|
109
|
+
data-edge=${drag.target.edge}
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
></span>` : nothing}
|
|
112
|
+
</div>`)}
|
|
113
|
+
</div>`;
|
|
114
|
+
}
|
|
65
115
|
/** The header's select-all checkbox: not focusable, like the rows' (the header cell holds focus). */
|
|
66
116
|
function selectAllCheckbox(checkAll) {
|
|
67
117
|
if (!checkAll) return nothing;
|
|
@@ -77,6 +127,19 @@ function selectAllCheckbox(checkAll) {
|
|
|
77
127
|
/>`;
|
|
78
128
|
}
|
|
79
129
|
/**
|
|
130
|
+
* A group's resize handle: dragging it resizes the group's resizable columns,
|
|
131
|
+
* each in proportion to its width; a double-click fits them to their content.
|
|
132
|
+
*/
|
|
133
|
+
function groupResizeHandle(key) {
|
|
134
|
+
return html`<div
|
|
135
|
+
part="resize-handle"
|
|
136
|
+
role="separator"
|
|
137
|
+
aria-orientation="vertical"
|
|
138
|
+
aria-label="Resize group"
|
|
139
|
+
data-resize-group=${key}
|
|
140
|
+
></div>`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
80
143
|
* A vertical separator with the column's width as its value. A flex column has no
|
|
81
144
|
* fixed width until it is resized, so it reports none.
|
|
82
145
|
*/
|
|
@@ -92,6 +155,6 @@ function resizeHandle(c) {
|
|
|
92
155
|
data-resize-col=${c.id}
|
|
93
156
|
></div>`;
|
|
94
157
|
}
|
|
95
|
-
export { renderHeader };
|
|
158
|
+
export { columnKey, renderHeader };
|
|
96
159
|
|
|
97
160
|
//# sourceMappingURL=header.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.js","names":[],"sources":["../../src/render/header.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport { live } from 'lit/directives/live.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { DropTarget } from '../controllers/reorder-drag.js';\nimport { ariaSort, cellStyle, headerSlot, type CheckboxHandlers } from './content.js';\n\n/** A column header being dragged, and where it would land (`undefined`: where it is). */\nexport interface HeaderDrag {\n columnId: string;\n target: DropTarget | undefined;\n}\n\nexport interface HeaderHandlers {\n click: (event: MouseEvent) => void;\n pointerdown: (event: PointerEvent) => void;\n dblclick: (event: MouseEvent) => void;\n}\n\n/**\n * The select-all checkbox in the selection column's header, with\n * `selectionMode` `'multi'`: checked when every selectable row is, mixed when\n * some are.\n */\nexport interface CheckAll {\n state: 'all' | 'some' | 'none';\n handlers: CheckboxHandlers;\n}\n\n/**\n * The sticky header row. Header content is slotted from the light DOM. Pinned\n * header cells use the same private `data-pin` hooks as body cells, but no parts.\n * Every header carries `data-col` (its column id) and sortable ones also\n * `data-sort-col`; the element's delegated handlers read them. Headers take part\n * in the grid's roving tabindex: the active one is the tab stop (`activeCol`).\n * Resizable columns get a `resize-handle` on the header's inline-end edge; a\n * double-click on it fits the column to its content. It is not focusable: the\n * focused header resizes with Alt+Arrow.\n *\n * Reorderable columns carry `data-move-col`: a drag on the header moves the\n * column. While one is dragged (`drag`), its header adds `header-cell-dragging`\n * and the target header shows a `drop-indicator` on the edge it would land at.\n */\nexport function renderHeader<T>(\n columns: readonly RenderedColumn<T>[],\n handlers: HeaderHandlers,\n activeCol: string | undefined,\n drag?: HeaderDrag,\n checkAll?: CheckAll,\n) {\n return html`<div part=\"header\" role=\"rowgroup\">\n <div\n part=\"header-row\"\n class=\"row\"\n role=\"row\"\n aria-rowindex=\"1\"\n @click=${handlers.click}\n @pointerdown=${handlers.pointerdown}\n @dblclick=${handlers.dblclick}\n >\n ${repeat(\n columns,\n (c) => c.key,\n (c) =>\n html`<div\n part=${headerParts(c, drag)}\n role=\"columnheader\"\n aria-colindex=${c.colIndex}\n aria-sort=${ariaSort(c.sorted) ?? nothing}\n aria-description=${c.filtered ? 'Filtered' : nothing}\n data-col=${c.id}\n data-sort-col=${c.sortable ? c.id : nothing}\n data-move-col=${c.reorderable ? c.id : nothing}\n tabindex=${c.id === activeCol ? 0 : -1}\n data-align=${c.align}\n data-pin=${c.pin ?? nothing}\n style=${cellStyle(c) ?? nothing}\n >\n ${\n c.kind === 'selection'\n ? selectAllCheckbox(checkAll)\n : html`<slot name=${headerSlot(c.slot)}></slot>`\n }\n ${c.sortable ? html`<span class=\"sort-indicator\" aria-hidden=\"true\"></span>` : nothing}\n ${c.filtered ? html`<span part=\"filter-indicator\" aria-hidden=\"true\"></span>` : nothing}\n ${c.resizable ? resizeHandle(c) : nothing}\n ${\n drag?.target?.columnId === c.id\n ? html`<span\n part=\"drop-indicator\"\n data-edge=${drag.target.edge}\n aria-hidden=\"true\"\n ></span>`\n : nothing\n }\n </div>`,\n )}\n </div>\n </div>`;\n}\n\nfunction headerParts<T>(c: RenderedColumn<T>, drag: HeaderDrag | undefined): string {\n let parts = c.sorted ? 'header-cell header-cell-sorted' : 'header-cell';\n if (c.filtered) parts += ' header-cell-filtered';\n if (c.kind === 'selection') parts += ' header-cell-selection';\n if (drag?.columnId === c.id) parts += ' header-cell-dragging';\n return parts;\n}\n\n/** The header's select-all checkbox: not focusable, like the rows' (the header cell holds focus). */\nfunction selectAllCheckbox(checkAll: CheckAll | undefined) {\n if (!checkAll) return nothing;\n return html`<input\n type=\"checkbox\"\n part=\"selection-checkbox\"\n tabindex=\"-1\"\n aria-label=\"Select all rows\"\n .checked=${live(checkAll.state === 'all')}\n .indeterminate=${live(checkAll.state === 'some')}\n @click=${checkAll.handlers.all}\n @mousedown=${checkAll.handlers.press}\n />`;\n}\n\n/**\n * A vertical separator with the column's width as its value. A flex column has no\n * fixed width until it is resized, so it reports none.\n */\nfunction resizeHandle<T>(c: RenderedColumn<T>) {\n return html`<div\n part=\"resize-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize column\"\n aria-valuenow=${c.flex === undefined ? Math.round(c.width) : nothing}\n aria-valuemin=${c.minWidth}\n aria-valuemax=${Number.isFinite(c.maxWidth) ? c.maxWidth : nothing}\n data-resize-col=${c.id}\n ></div>`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,aACd,SACA,UACA,WACA,MACA,UACA;CACA,OAAO,IAAI;;;;;;eAME,SAAS,MAAM;qBACT,SAAS,YAAY;kBACxB,SAAS,SAAS;;QAE5B,OACA,UACC,MAAM,EAAE,MACR,MACC,IAAI;mBACK,YAAY,GAAG,IAAI,EAAE;;4BAEZ,EAAE,SAAS;wBACf,SAAS,EAAE,MAAM,KAAK,QAAQ;+BACvB,EAAE,WAAW,aAAa,QAAQ;uBAC1C,EAAE,GAAG;4BACA,EAAE,WAAW,EAAE,KAAK,QAAQ;4BAC5B,EAAE,cAAc,EAAE,KAAK,QAAQ;uBACpC,EAAE,OAAO,YAAY,IAAI,GAAG;yBAC1B,EAAE,MAAM;uBACV,EAAE,OAAO,QAAQ;oBACpB,UAAU,CAAC,KAAK,QAAQ;;cAG9B,EAAE,SAAS,cACP,kBAAkB,QAAQ,IAC1B,IAAI,cAAc,WAAW,EAAE,IAAI,EAAE,UAC1C;cACC,EAAE,WAAW,IAAI,4DAA4D,QAAQ;cACrF,EAAE,WAAW,IAAI,6DAA6D,QAAQ;cACtF,EAAE,YAAY,aAAa,CAAC,IAAI,QAAQ;cAExC,MAAM,QAAQ,aAAa,EAAE,KACzB,IAAI;;gCAEU,KAAK,OAAO,KAAK;;8BAG/B,QACL;iBAEP,EAAE;;;AAGR;AAEA,SAAS,YAAe,GAAsB,MAAsC;CAClF,IAAI,QAAQ,EAAE,SAAS,mCAAmC;CAC1D,IAAI,EAAE,UAAU,SAAS;CACzB,IAAI,EAAE,SAAS,aAAa,SAAS;CACrC,IAAI,MAAM,aAAa,EAAE,IAAI,SAAS;CACtC,OAAO;AACT;;AAGA,SAAS,kBAAkB,UAAgC;CACzD,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,IAAI;;;;;eAKE,KAAK,SAAS,UAAU,KAAK,EAAE;qBACzB,KAAK,SAAS,UAAU,MAAM,EAAE;aACxC,SAAS,SAAS,IAAI;iBAClB,SAAS,SAAS,MAAM;;AAEzC;;;;;AAMA,SAAS,aAAgB,GAAsB;CAC7C,OAAO,IAAI;;;;;oBAKO,EAAE,SAAS,KAAA,IAAY,KAAK,MAAM,EAAE,KAAK,IAAI,QAAQ;oBACrD,EAAE,SAAS;oBACX,OAAO,SAAS,EAAE,QAAQ,IAAI,EAAE,WAAW,QAAQ;sBACjD,EAAE,GAAG;;AAE3B"}
|
|
1
|
+
{"version":3,"file":"header.js","names":[],"sources":["../../src/render/header.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport type { HeaderGroupCell } from '@argus-table/core';\nimport { live } from 'lit/directives/live.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { RenderedColumn } from '../controllers/column-window.js';\nimport type { DropTarget } from '../controllers/reorder-drag.js';\nimport type { CellContent } from '../types.js';\nimport {\n ariaSort,\n cellStyle,\n groupSlot,\n groupStyle,\n headerSlot,\n type CheckboxHandlers,\n} from './content.js';\n\n/**\n * A column or group header being dragged, and where it would land (`undefined`:\n * where it is). `key` is the drop slot key of what moves (see `unitKey`).\n */\nexport interface HeaderDrag {\n key: string;\n /** The columns that move: the column, or the group's columns on its side. */\n columns: ReadonlySet<string>;\n target: DropTarget | undefined;\n}\n\n/** The drop slot key of a column (`c:` and its id) or a group cell (its `key`). */\nexport const columnKey = (columnId: string) => `c:${columnId}`;\n\nexport interface HeaderOptions<T> {\n columns: readonly RenderedColumn<T>[];\n /** The group header rows above the column headers, top first. */\n groups: readonly (readonly HeaderGroupCell<CellContent>[])[];\n handlers: HeaderHandlers;\n /** The active cell when it is in the header: its header row and column. */\n active: { index: number; col: string } | undefined;\n drag?: HeaderDrag | undefined;\n checkAll?: CheckAll | undefined;\n}\n\nexport interface HeaderHandlers {\n click: (event: MouseEvent) => void;\n pointerdown: (event: PointerEvent) => void;\n dblclick: (event: MouseEvent) => void;\n}\n\n/**\n * The select-all checkbox in the selection column's header, with\n * `selectionMode` `'multi'`: checked when every selectable row is, mixed when\n * some are.\n */\nexport interface CheckAll {\n state: 'all' | 'some' | 'none';\n handlers: CheckboxHandlers;\n}\n\n/**\n * The sticky header: a row per level of column groups, then the row of column\n * headers. Header content is slotted from the light DOM. Pinned\n * header cells use the same private `data-pin` hooks as body cells, and the parts\n * `header-cell-pinned` and `header-cell-pinned-edge`.\n * Every header carries `data-col` (its column id) and sortable ones also\n * `data-sort-col`; the element's delegated handlers read them. Headers take part\n * in the grid's roving tabindex: the active one is the tab stop (`activeCol`).\n * Resizable columns get a `resize-handle` on the header's inline-end edge; a\n * double-click on it fits the column to its content. It is not focusable: the\n * focused header resizes with Alt+Arrow.\n *\n * Reorderable columns carry `data-move-col`: a drag on the header moves the\n * column. While one is dragged (`drag`), its header adds `header-cell-dragging`\n * and the target header shows a `drop-indicator` on the edge it would land at.\n */\nexport function renderHeader<T>({\n columns,\n groups,\n handlers,\n active,\n drag,\n checkAll,\n}: HeaderOptions<T>) {\n const leaf = groups.length;\n const activeCol = active?.index === leaf ? active.col : undefined;\n return html`<div\n part=\"header\"\n role=\"rowgroup\"\n @click=${handlers.click}\n @pointerdown=${handlers.pointerdown}\n @dblclick=${handlers.dblclick}\n >\n ${groups.map((cells, level) => groupRow(cells, level, active, drag))}\n <div part=\"header-row\" class=\"row\" role=\"row\" aria-rowindex=${leaf + 1}>\n ${repeat(\n columns,\n (c) => c.key,\n (c) =>\n html`<div\n part=${headerParts(c, drag)}\n role=\"columnheader\"\n aria-colindex=${c.colIndex}\n aria-sort=${ariaSort(c.sorted) ?? nothing}\n aria-description=${c.filtered ? 'Filtered' : nothing}\n data-col=${c.id}\n data-sort-col=${c.sortable ? c.id : nothing}\n data-move-col=${c.reorderable ? c.id : nothing}\n tabindex=${c.id === activeCol ? 0 : -1}\n data-align=${c.align}\n data-pin=${c.pin ?? nothing}\n style=${cellStyle(c) ?? nothing}\n >\n ${\n c.kind === 'selection'\n ? selectAllCheckbox(checkAll)\n : html`<slot name=${headerSlot(c.slot)}></slot>`\n }\n ${\n c.sortable\n ? html`<span\n part=\"sort-indicator\"\n class=\"sort-indicator\"\n aria-hidden=\"true\"\n ></span>`\n : nothing\n }\n ${c.filtered ? html`<span part=\"filter-indicator\" aria-hidden=\"true\"></span>` : nothing}\n ${c.resizable ? resizeHandle(c) : nothing}\n ${\n drag?.target?.id === columnKey(c.id)\n ? html`<span\n part=\"drop-indicator\"\n data-edge=${drag.target.edge}\n aria-hidden=\"true\"\n ></span>`\n : nothing\n }\n </div>`,\n )}\n </div>\n </div>`;\n}\n\nfunction headerParts<T>(c: RenderedColumn<T>, drag: HeaderDrag | undefined): string {\n let parts = c.sorted\n ? `header-cell header-cell-sorted header-cell-sorted-${c.sorted}`\n : 'header-cell';\n if (c.pin)\n parts += c.pinEdge ? ' header-cell-pinned header-cell-pinned-edge' : ' header-cell-pinned';\n if (c.filtered) parts += ' header-cell-filtered';\n if (c.kind === 'selection') parts += ' header-cell-selection';\n if (drag?.columns.has(c.id)) parts += ' header-cell-dragging';\n return parts;\n}\n\n/**\n * A row of group headers. Every group cell renders, also with `virtualizeColumns`\n * (there are few, and their slots stay put). A group's cell is a `columnheader`\n * spanning its columns (`aria-colspan`), focusable through the roving tabindex,\n * with `data-group-key`; columns without a group at this level sit under an empty\n * filler `gridcell`, so every row covers every column. Neither carries `data-col`:\n * they are no column's header. A group with a resizable column has a\n * `resize-handle` too (`data-resize-group`).\n */\nfunction groupRow(\n cells: readonly HeaderGroupCell<CellContent>[],\n level: number,\n active: { index: number; col: string } | undefined,\n drag: HeaderDrag | undefined,\n) {\n const activeCol = active?.index === level ? active.col : undefined;\n return html`<div part=\"header-group-row\" class=\"row\" role=\"row\" aria-rowindex=${level + 1}>\n ${repeat(\n cells,\n (c) => c.key,\n (c) =>\n c.kind === 'filler'\n ? html`<div\n part=\"header-group-filler\"\n role=\"gridcell\"\n aria-colindex=${c.colIndex}\n aria-colspan=${c.span}\n data-pin=${c.pin ?? nothing}\n style=${groupStyle(c)}\n ></div>`\n : html`<div\n part=${\n drag?.key === c.key\n ? 'header-group-cell header-group-cell-dragging'\n : 'header-group-cell'\n }\n role=\"columnheader\"\n aria-colindex=${c.colIndex}\n aria-colspan=${c.span}\n data-group-key=${c.key}\n data-move-group=${c.movable ? c.key : nothing}\n tabindex=${activeCol !== undefined && c.columns.includes(activeCol) ? 0 : -1}\n data-pin=${c.pin ?? nothing}\n style=${groupStyle(c)}\n >\n <span class=\"group-label\"><slot name=${groupSlot(c.slot)}></slot></span>\n ${c.resizable ? groupResizeHandle(c.key) : nothing}\n ${\n drag?.target?.id === c.key\n ? html`<span\n part=\"drop-indicator\"\n data-edge=${drag.target.edge}\n aria-hidden=\"true\"\n ></span>`\n : nothing\n }\n </div>`,\n )}\n </div>`;\n}\n\n/** The header's select-all checkbox: not focusable, like the rows' (the header cell holds focus). */\nfunction selectAllCheckbox(checkAll: CheckAll | undefined) {\n if (!checkAll) return nothing;\n return html`<input\n type=\"checkbox\"\n part=\"selection-checkbox\"\n tabindex=\"-1\"\n aria-label=\"Select all rows\"\n .checked=${live(checkAll.state === 'all')}\n .indeterminate=${live(checkAll.state === 'some')}\n @click=${checkAll.handlers.all}\n @mousedown=${checkAll.handlers.press}\n />`;\n}\n\n/**\n * A group's resize handle: dragging it resizes the group's resizable columns,\n * each in proportion to its width; a double-click fits them to their content.\n */\nfunction groupResizeHandle(key: string) {\n return html`<div\n part=\"resize-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize group\"\n data-resize-group=${key}\n ></div>`;\n}\n\n/**\n * A vertical separator with the column's width as its value. A flex column has no\n * fixed width until it is resized, so it reports none.\n */\nfunction resizeHandle<T>(c: RenderedColumn<T>) {\n return html`<div\n part=\"resize-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize column\"\n aria-valuenow=${c.flex === undefined ? Math.round(c.width) : nothing}\n aria-valuemin=${c.minWidth}\n aria-valuemax=${Number.isFinite(c.maxWidth) ? c.maxWidth : nothing}\n data-resize-col=${c.id}\n ></div>`;\n}\n"],"mappings":";;;;;;AA4BA,IAAa,aAAa,aAAqB,KAAK;;;;;;;;;;;;;;;;;AA6CpD,SAAgB,aAAgB,EAC9B,SACA,QACA,UACA,QACA,MACA,YACmB;CACnB,MAAM,OAAO,OAAO;CACpB,MAAM,YAAY,QAAQ,UAAU,OAAO,OAAO,MAAM,KAAA;CACxD,OAAO,IAAI;;;aAGA,SAAS,MAAM;mBACT,SAAS,YAAY;gBACxB,SAAS,SAAS;;MAE5B,OAAO,KAAK,OAAO,UAAU,SAAS,OAAO,OAAO,QAAQ,IAAI,CAAC,EAAE;kEACP,OAAO,EAAE;QACnE,OACA,UACC,MAAM,EAAE,MACR,MACC,IAAI;mBACK,YAAY,GAAG,IAAI,EAAE;;4BAEZ,EAAE,SAAS;wBACf,SAAS,EAAE,MAAM,KAAK,QAAQ;+BACvB,EAAE,WAAW,aAAa,QAAQ;uBAC1C,EAAE,GAAG;4BACA,EAAE,WAAW,EAAE,KAAK,QAAQ;4BAC5B,EAAE,cAAc,EAAE,KAAK,QAAQ;uBACpC,EAAE,OAAO,YAAY,IAAI,GAAG;yBAC1B,EAAE,MAAM;uBACV,EAAE,OAAO,QAAQ;oBACpB,UAAU,CAAC,KAAK,QAAQ;;cAG9B,EAAE,SAAS,cACP,kBAAkB,QAAQ,IAC1B,IAAI,cAAc,WAAW,EAAE,IAAI,EAAE,UAC1C;cAEC,EAAE,WACE,IAAI;;;;8BAKJ,QACL;cACC,EAAE,WAAW,IAAI,6DAA6D,QAAQ;cACtF,EAAE,YAAY,aAAa,CAAC,IAAI,QAAQ;cAExC,MAAM,QAAQ,OAAO,UAAU,EAAE,EAAE,IAC/B,IAAI;;gCAEU,KAAK,OAAO,KAAK;;8BAG/B,QACL;iBAEP,EAAE;;;AAGR;AAEA,SAAS,YAAe,GAAsB,MAAsC;CAClF,IAAI,QAAQ,EAAE,SACV,qDAAqD,EAAE,WACvD;CACJ,IAAI,EAAE,KACJ,SAAS,EAAE,UAAU,gDAAgD;CACvE,IAAI,EAAE,UAAU,SAAS;CACzB,IAAI,EAAE,SAAS,aAAa,SAAS;CACrC,IAAI,MAAM,QAAQ,IAAI,EAAE,EAAE,GAAG,SAAS;CACtC,OAAO;AACT;;;;;;;;;;AAWA,SAAS,SACP,OACA,OACA,QACA,MACA;CACA,MAAM,YAAY,QAAQ,UAAU,QAAQ,OAAO,MAAM,KAAA;CACzD,OAAO,IAAI,qEAAqE,QAAQ,EAAE;MACtF,OACA,QACC,MAAM,EAAE,MACR,MACC,EAAE,SAAS,WACP,IAAI;;;8BAGc,EAAE,SAAS;6BACZ,EAAE,KAAK;yBACX,EAAE,OAAO,QAAQ;sBACpB,WAAW,CAAC,EAAE;uBAExB,IAAI;qBAEA,MAAM,QAAQ,EAAE,MACZ,iDACA,oBACL;;8BAEe,EAAE,SAAS;6BACZ,EAAE,KAAK;+BACL,EAAE,IAAI;gCACL,EAAE,UAAU,EAAE,MAAM,QAAQ;yBACnC,cAAc,KAAA,KAAa,EAAE,QAAQ,SAAS,SAAS,IAAI,IAAI,GAAG;yBAClE,EAAE,OAAO,QAAQ;sBACpB,WAAW,CAAC,EAAE;;qDAEiB,UAAU,EAAE,IAAI,EAAE;gBACvD,EAAE,YAAY,kBAAkB,EAAE,GAAG,IAAI,QAAQ;gBAEjD,MAAM,QAAQ,OAAO,EAAE,MACnB,IAAI;;kCAEU,KAAK,OAAO,KAAK;;gCAG/B,QACL;mBAEX,EAAE;;AAEN;;AAGA,SAAS,kBAAkB,UAAgC;CACzD,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,IAAI;;;;;eAKE,KAAK,SAAS,UAAU,KAAK,EAAE;qBACzB,KAAK,SAAS,UAAU,MAAM,EAAE;aACxC,SAAS,SAAS,IAAI;iBAClB,SAAS,SAAS,MAAM;;AAEzC;;;;;AAMA,SAAS,kBAAkB,KAAa;CACtC,OAAO,IAAI;;;;;wBAKW,IAAI;;AAE5B;;;;;AAMA,SAAS,aAAgB,GAAsB;CAC7C,OAAO,IAAI;;;;;oBAKO,EAAE,SAAS,KAAA,IAAY,KAAK,MAAM,EAAE,KAAK,IAAI,QAAQ;oBACrD,EAAE,SAAS;oBACX,OAAO,SAAS,EAAE,QAAQ,IAAI,EAAE,WAAW,QAAQ;sBACjD,EAAE,GAAG;;AAE3B"}
|
package/dist/styles/base.js
CHANGED
|
@@ -5,7 +5,7 @@ import { css } from "lit";
|
|
|
5
5
|
* public token with a neutral fallback, so themes and page CSS always win.
|
|
6
6
|
* `--_*` properties are private.
|
|
7
7
|
*/
|
|
8
|
-
var baseStyles = css`@layer argus-base{:host{box-sizing:border-box;contain:content;color-scheme:light dark;block-size:400px;color:var(--argus-fg,CanvasText);background:var(--argus-bg,Canvas);font:var(--argus-font,inherit);border:var(--argus-
|
|
8
|
+
var baseStyles = css`@layer argus-base{:host{box-sizing:border-box;contain:content;color-scheme:light dark;block-size:400px;color:var(--argus-fg,CanvasText);background:var(--argus-bg,Canvas);font:var(--argus-font,inherit);border:var(--argus-border,none);border-radius:var(--argus-border-radius,0);--_row-height:var(--argus-row-height,40px);--_pad:var(--argus-cell-padding-inline,12px);--_border:var(--argus-border-width,1px) solid var(--argus-border-color,light-dark(#e5e7eb,#374151));--_dir:1;display:block}:host(:dir(rtl)){--_dir:-1}:host([density=compact]){--_row-height:var(--argus-row-height,32px);--_pad:var(--argus-cell-padding-inline,8px)}:host([density=spacious]){--_row-height:var(--argus-row-height,52px);--_pad:var(--argus-cell-padding-inline,16px)}:host([hidden]){display:none}[part~=table]{overscroll-behavior:contain;--_header-h:calc(var(--_header-rows,1) * var(--_row-height));block-size:100%;position:relative;overflow:auto}[part~=body]>.row>[role=gridcell]{scroll-margin-block:calc(var(--_header-h) + var(--_pin-top-rows,0) * var(--_row-height)) calc(var(--_pin-bottom-rows,0) * var(--_row-height))}[role=gridcell]:not([data-pin]),[role=columnheader]:not([data-pin]){scroll-margin-inline:var(--_pin-start,0) var(--_pin-end,0)}.row{grid-template-columns:var(--_cols);inline-size:var(--_width);min-inline-size:100%;block-size:var(--_row-height);box-sizing:border-box;display:grid}[part~=header]{z-index:2;position:sticky;inset-block-start:0}[part~=body]{contain:layout;position:relative}[part~=body]>.row{position:absolute;inset-block-start:0;inset-inline-start:0}[part~=row]{--_row-bg:var(--argus-bg,Canvas);--_row-surface:var(--_row-bg);background:var(--_row-surface)}[part~=row-odd]{--_row-bg:var(--argus-row-odd-bg,var(--argus-bg,Canvas))}[part~=row]:hover{--_row-surface:var(--argus-row-hover-bg,var(--_row-bg))}[part~=row-selected],[part~=row-selected]:hover{--_row-bg:var(--argus-row-selected-bg,color-mix(in srgb, Highlight 22%, var(--argus-bg,Canvas)));--_row-surface:var(--_row-bg)}@media (forced-colors:active){[part~=row-selected]{forced-color-adjust:none;--_row-bg:Highlight;color:highlighttext}}.pinned{z-index:2;position:sticky}.pinned[data-side=top]{inset-block-start:var(--_header-h)}.pinned[data-side=bottom]{inset-block-end:0}[part~=row-pinned]{--_row-bg:var(--argus-row-pinned-bg,var(--argus-bg,Canvas))}[part~=cell],[part~=header-cell]{box-sizing:border-box;min-inline-size:0;padding-inline:var(--_pad);white-space:nowrap;border-block-end:var(--_border);align-items:center;display:flex;overflow:hidden}[part~=cell-selection],[part~=header-cell-selection]{justify-content:center;padding-inline:0}[part~=selection-checkbox]{cursor:pointer;margin:0}[part~=header-cell]{background:var(--argus-header-bg,light-dark(#f9fafb,#111827));color:var(--argus-header-fg,inherit);font-weight:600;position:relative}[part~=header-group-cell],[part~=header-group-filler]{box-sizing:border-box;background:var(--argus-header-bg,light-dark(#f9fafb,#111827));min-inline-size:0;color:var(--argus-header-fg,inherit)}[part~=header-group-cell]{padding-inline:var(--_pad);white-space:nowrap;border-block-end:var(--_border);border-inline-end:var(--_border);align-items:center;font-weight:600;display:flex;position:relative;overflow:clip}.group-label{text-overflow:ellipsis;min-inline-size:0;overflow:hidden}[part~=header-group-cell]:not([data-pin])>.group-label{position:sticky;inset-inline-start:calc(var(--_pin-start,0px) + var(--_pad))}[data-move-group]{user-select:none}[data-dragging] [part~=header-group-cell]{cursor:grabbing}[part~=header-group-cell-dragging]{opacity:.5}[part~=resize-handle]{z-index:1;cursor:col-resize;touch-action:none;inline-size:8px;position:absolute;inset-block:0;inset-inline-end:0}[part~=resize-handle]:after{content:"";background:var(--argus-resize-handle-color,light-dark(#9ca3af,#6b7280));opacity:0;inline-size:2px;position:absolute;inset-block:25%;inset-inline-end:0}[part~=header-cell]:hover [part~=resize-handle]:after,[part~=header-cell]:focus-visible [part~=resize-handle]:after,[part~=header-group-cell]:hover [part~=resize-handle]:after,[part~=header-group-cell]:focus-visible [part~=resize-handle]:after,[part~=resize-handle][data-active]:after{opacity:1}[data-sort-col]{cursor:pointer;user-select:none}[data-move-col]{user-select:none}[data-dragging],[data-dragging] [part~=header-cell]{cursor:grabbing}[part~=header-cell-dragging]{opacity:.5}[part~=drop-indicator]{z-index:2;background:var(--argus-drop-indicator-color,Highlight);pointer-events:none;inline-size:2px;position:absolute;inset-block:0}[part~=drop-indicator][data-edge=start]{inset-inline-start:0}[part~=drop-indicator][data-edge=end]{inset-inline-end:0}[part~=header-cell]:focus-visible,[part~=header-group-cell]:focus-visible,[part~=cell]:focus-visible{outline:var(--argus-focus-ring,2px solid Highlight);outline-offset:-2px}.sort-indicator{visibility:hidden;border-bottom:.12em solid;border-right:.12em solid;flex:none;block-size:.35em;inline-size:.35em;margin-inline-start:.4em}[aria-sort] .sort-indicator{visibility:visible}[aria-sort=ascending] .sort-indicator{transform:translateY(.1em)rotate(-135deg)}[aria-sort=descending] .sort-indicator{transform:translateY(-.1em)rotate(45deg)}[data-align=end] .sort-indicator{order:-1;margin-inline:0 .4em}[part~=filter-indicator]{background:var(--argus-filter-indicator-color,currentColor);clip-path:polygon(0 0,100% 0,62% 48%,62% 100%,38% 82%,38% 48%);flex:none;block-size:.7em;inline-size:.7em;margin-inline-start:.35em}[data-align=end] [part~=filter-indicator]{order:-2;margin-inline:0 .35em}@media (forced-colors:active){[part~=filter-indicator]{forced-color-adjust:none;background:canvastext}}[data-pin]{z-index:1;position:sticky}[data-pin=start]{inset-inline-start:var(--_pin-offset)}[data-pin=end]{inset-inline-end:var(--_pin-offset)}[part~=row]>[data-pin]{position:relative;inset-inline:auto}[part~=row]>[data-pin=start]{transform:translateX(var(--_sx,0px))}[part~=row]>[data-pin=end]{transform:translateX(calc(var(--_sx,0px) - var(--_dir) * var(--_max-x,0px)))}[data-hscroll] [part~=row]>[data-pin]{position:sticky;transform:none}[data-hscroll] [part~=row]>[data-pin=start]{inset-inline-start:var(--_pin-offset)}[data-hscroll] [part~=row]>[data-pin=end]{inset-inline-end:var(--_pin-offset)}[part~=cell-pinned]{background:var(--_row-surface)}.pin-shadow{block-size:min(var(--_view-h,100%), var(--_content-h,100%));z-index:3;pointer-events:none;position:absolute;inset-block-start:0}.pin-shadow[data-side=start]{inset-inline-start:0}.pin-shadow[data-side=end]{inset-inline-end:var(--_sb-x,0px)}.pin-shadow:after{content:"";box-shadow:var(--argus-pinned-shadow,4px 0 6px -4px light-dark(#0003,#0009));transform:scaleX(var(--_dir));position:absolute;inset:0}.pin-shadow[data-side=end]:after{transform:scaleX(calc(-1 * var(--_dir)))}[data-align=center]{text-align:center;justify-content:center}[data-align=end]{text-align:end;justify-content:flex-end}[part~=cell][data-wrap]{white-space:normal;overflow-wrap:anywhere;align-items:safe center}[data-auto-rows]{overflow-anchor:none}[data-auto-rows]>[part~=body]>.row,[data-auto-rows]>.pinned>.row{block-size:auto;min-block-size:var(--_row-height);grid-template-rows:minmax(var(--_row-height), auto)}[data-auto-rows]>[part~=body]>.row[data-measure-row]{min-block-size:var(--_row-height)!important}[data-auto-rows] [part~=cell]{padding-block:var(--argus-cell-padding-block,max(0px, (var(--_row-height) - 1lh - var(--argus-border-width,1px)) / 2));align-items:start}[data-auto-rows] [part~=row-loading]>[part~=cell]{align-items:center;padding-block:0}[data-auto-rows]>[part~=body]>.row>[role=gridcell]{scroll-margin-block:calc(var(--_header-h) + var(--_pin-top-h,0px)) var(--_pin-bottom-h,0px)}[data-auto-rows]>[part~=empty]{min-block-size:calc(100% - var(--_header-h) - var(--_pin-top-h,0px) - var(--_pin-bottom-h,0px))}::slotted(*){text-overflow:ellipsis;min-inline-size:0;overflow:hidden}[data-measure]{inline-size:max-content}[part~=skeleton]{background:var(--argus-skeleton-bg,color-mix(in srgb, currentColor 12%, transparent));border-radius:.3em;flex:none;block-size:.6em;inline-size:min(60%,8em);animation:1.6s ease-in-out infinite argus-skeleton-pulse}[part~=skeleton-error]{opacity:.5;animation:none}@keyframes argus-skeleton-pulse{50%{opacity:.45}}@media (prefers-reduced-motion:reduce){[part~=skeleton]{animation:none}}[part~=retry]{font:inherit;color:inherit;cursor:pointer}[part~=empty]{min-block-size:calc(100% - var(--_header-h) - (var(--_pin-top-rows,0) + var(--_pin-bottom-rows,0)) * var(--_row-height));padding:var(--_pad);box-sizing:border-box;justify-content:center;align-items:center;display:flex}.probe{inline-size:0;block-size:var(--_row-height);visibility:hidden;pointer-events:none;position:absolute;inset-block-start:0}}`;
|
|
9
9
|
export { baseStyles };
|
|
10
10
|
|
|
11
11
|
//# sourceMappingURL=base.js.map
|
package/dist/styles/base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":[],"sources":["../../src/styles/base.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * Base styles. Everything sits in `@layer argus-base`, and every value reads a\n * public token with a neutral fallback, so themes and page CSS always win.\n * `--_*` properties are private.\n */\nexport const baseStyles = css`\n @layer argus-base {\n :host {\n display: block;\n /* A table that virtualizes needs a bounded height; set your own on the element. */\n block-size: 400px;\n box-sizing: border-box;\n contain: content;\n color-scheme: light dark;\n color: var(--argus-fg, CanvasText);\n background: var(--argus-bg, Canvas);\n font: var(--argus-font, inherit);\n /* contain: content clips the header, rows and pinned shadows to the rounded corners. */\n border: var(--argus-outline, none);\n border-radius: var(--argus-radius, 0);\n --_row-height: var(--argus-row-height, 40px);\n --_pad: var(--argus-cell-padding-inline, 12px);\n --_border: var(--argus-border-width, 1px) solid\n var(--argus-border-color, light-dark(#e5e7eb, #374151));\n /* 1 in LTR, -1 in RTL: mirrors the pinned-edge shadow. */\n --_dir: 1;\n }\n :host(:dir(rtl)) {\n --_dir: -1;\n }\n :host([density='compact']) {\n --_row-height: var(--argus-row-height, 32px);\n --_pad: var(--argus-cell-padding-inline, 8px);\n }\n :host([density='spacious']) {\n --_row-height: var(--argus-row-height, 52px);\n --_pad: var(--argus-cell-padding-inline, 16px);\n }\n :host([hidden]) {\n display: none;\n }\n\n [part~='table'] {\n position: relative;\n block-size: 100%;\n overflow: auto;\n overscroll-behavior: contain;\n }\n\n /*\n * A cell focused or moved to by keyboard scrolls clear of the sticky header, the\n * pinned rows and the pinned columns: scroll margins on the cells that scroll.\n * Not scroll padding on the scroller: Chromium and WebKit measure the sticky\n * header and pinned rows against it too, so focusing one of them (a click, Tab,\n * an arrow key) scrolled the body.\n */\n [part~='body'] > .row > [role='gridcell'] {\n scroll-margin-block: calc((1 + var(--_pin-top-rows, 0)) * var(--_row-height))\n calc(var(--_pin-bottom-rows, 0) * var(--_row-height));\n }\n [role='gridcell']:not([data-pin]),\n [role='columnheader']:not([data-pin]) {\n scroll-margin-inline: var(--_pin-start, 0) var(--_pin-end, 0);\n }\n\n .row {\n display: grid;\n grid-template-columns: var(--_cols);\n inline-size: var(--_width);\n min-inline-size: 100%;\n block-size: var(--_row-height);\n box-sizing: border-box;\n }\n\n [part~='header'] {\n position: sticky;\n inset-block-start: 0;\n z-index: 2;\n }\n\n [part~='body'] {\n position: relative;\n contain: layout;\n }\n [part~='body'] > .row {\n position: absolute;\n inset-block-start: 0;\n inset-inline-start: 0;\n }\n [part~='row'] {\n --_row-bg: var(--argus-bg, Canvas);\n --_row-surface: var(--_row-bg);\n background: var(--_row-surface);\n }\n [part~='row-odd'] {\n --_row-bg: var(--argus-row-stripe-bg, var(--argus-bg, Canvas));\n }\n [part~='row']:hover {\n --_row-surface: var(--argus-row-hover-bg, var(--_row-bg));\n }\n /* Selected wins over stripes and hover. */\n [part~='row-selected'],\n [part~='row-selected']:hover {\n --_row-bg: var(\n --argus-row-selected-bg,\n color-mix(in srgb, Highlight 22%, var(--argus-bg, Canvas))\n );\n --_row-surface: var(--_row-bg);\n }\n @media (forced-colors: active) {\n [part~='row-selected'] {\n forced-color-adjust: none;\n --_row-bg: Highlight;\n color: HighlightText;\n }\n }\n\n /*\n * Pinned rows: a sticky row group below the header and one at the bottom of\n * the view. The header is one row tall. They keep their place in the flow,\n * so body row i still starts i rows below the top group.\n */\n .pinned {\n position: sticky;\n z-index: 2;\n }\n .pinned[data-side='top'] {\n inset-block-start: var(--_row-height);\n }\n .pinned[data-side='bottom'] {\n inset-block-end: 0;\n }\n [part~='row-pinned'] {\n --_row-bg: var(--argus-pinned-row-bg, var(--argus-bg, Canvas));\n }\n\n [part~='cell'],\n [part~='header-cell'] {\n display: flex;\n align-items: center;\n min-inline-size: 0;\n box-sizing: border-box;\n padding-inline: var(--_pad);\n overflow: hidden;\n white-space: nowrap;\n border-block-end: var(--_border);\n }\n /* The checkbox column: the checkbox centred, no padding to squeeze it. */\n [part~='cell-selection'],\n [part~='header-cell-selection'] {\n justify-content: center;\n padding-inline: 0;\n }\n [part~='selection-checkbox'] {\n margin: 0;\n cursor: pointer;\n }\n [part~='header-cell'] {\n background: var(--argus-header-bg, light-dark(#f9fafb, #111827));\n color: var(--argus-header-fg, inherit);\n font-weight: 600;\n }\n [part~='header-cell'] {\n position: relative;\n }\n [part~='resize-handle'] {\n position: absolute;\n inset-block: 0;\n inset-inline-end: 0;\n inline-size: 8px;\n z-index: 1;\n cursor: col-resize;\n touch-action: none;\n }\n [part~='resize-handle']::after {\n content: '';\n position: absolute;\n inset-block: 25%;\n inset-inline-end: 0;\n inline-size: 2px;\n background: var(--argus-resize-handle-color, light-dark(#9ca3af, #6b7280));\n opacity: 0;\n }\n [part~='header-cell']:hover [part~='resize-handle']::after,\n [part~='header-cell']:focus-visible [part~='resize-handle']::after,\n [part~='resize-handle'][data-active]::after {\n opacity: 1;\n }\n [data-sort-col] {\n cursor: pointer;\n user-select: none;\n }\n /* A drag on a reorderable header moves the column, so its text isn't selectable. */\n [data-move-col] {\n user-select: none;\n }\n [data-dragging],\n [data-dragging] [part~='header-cell'] {\n cursor: grabbing;\n }\n [part~='header-cell-dragging'] {\n opacity: 0.5;\n }\n /* The line where a dragged column would land, on the target header's logical edge. */\n [part~='drop-indicator'] {\n position: absolute;\n inset-block: 0;\n inline-size: 2px;\n z-index: 2;\n background: var(--argus-drop-indicator-color, Highlight);\n pointer-events: none;\n }\n [part~='drop-indicator'][data-edge='start'] {\n inset-inline-start: 0;\n }\n [part~='drop-indicator'][data-edge='end'] {\n inset-inline-end: 0;\n }\n [part~='header-cell']:focus-visible,\n [part~='cell']:focus-visible {\n outline: var(--argus-focus-ring, 2px solid Highlight);\n outline-offset: -2px;\n }\n /* A small chevron after the header content; only the sorted column shows it. */\n .sort-indicator {\n flex: none;\n inline-size: 0.35em;\n block-size: 0.35em;\n margin-inline-start: 0.4em;\n /* Physical sides on purpose: the chevron is rotated, so it must not mirror in RTL. */\n border-right: 0.12em solid currentColor;\n border-bottom: 0.12em solid currentColor;\n visibility: hidden;\n }\n [aria-sort] .sort-indicator {\n visibility: visible;\n }\n [aria-sort='ascending'] .sort-indicator {\n transform: translateY(0.1em) rotate(-135deg);\n }\n [aria-sort='descending'] .sort-indicator {\n transform: translateY(-0.1em) rotate(45deg);\n }\n [data-align='end'] .sort-indicator {\n order: -1;\n margin-inline: 0 0.4em;\n }\n /* A funnel: the column is filtered. */\n [part~='filter-indicator'] {\n flex: none;\n inline-size: 0.7em;\n block-size: 0.7em;\n margin-inline-start: 0.35em;\n background: var(--argus-filter-indicator-color, currentColor);\n clip-path: polygon(0 0, 100% 0, 62% 48%, 62% 100%, 38% 82%, 38% 48%);\n }\n [data-align='end'] [part~='filter-indicator'] {\n order: -2;\n margin-inline: 0 0.35em;\n }\n @media (forced-colors: active) {\n /* Backgrounds are forced to Canvas there, which would hide the funnel. */\n [part~='filter-indicator'] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n /*\n * Pinned columns: sticky inside the scroll container, with offsets from the\n * table model. Logical insets make RTL work without extra rules.\n */\n [data-pin] {\n position: sticky;\n z-index: 1;\n }\n [data-pin='start'] {\n inset-inline-start: var(--_pin-offset);\n }\n [data-pin='end'] {\n inset-inline-end: var(--_pin-offset);\n }\n /*\n * Pinned body cells are sticky only while the table scrolls sideways\n * (data-hscroll on the scroller). At rest they are plain cells shifted by the\n * horizontal scroll offset, which vertical scrolling never changes: a sticky\n * cell per row is a compositing layer each, and Safari stutters moving them\n * (PinScrollController). Both place the cells at the same spot.\n */\n [part~='row'] > [data-pin] {\n position: relative;\n inset-inline: auto;\n }\n [part~='row'] > [data-pin='start'] {\n transform: translateX(var(--_sx, 0px));\n }\n [part~='row'] > [data-pin='end'] {\n transform: translateX(calc(var(--_sx, 0px) - var(--_dir) * var(--_max-x, 0px)));\n }\n [data-hscroll] [part~='row'] > [data-pin] {\n position: sticky;\n transform: none;\n }\n [data-hscroll] [part~='row'] > [data-pin='start'] {\n inset-inline-start: var(--_pin-offset);\n }\n [data-hscroll] [part~='row'] > [data-pin='end'] {\n inset-inline-end: var(--_pin-offset);\n }\n [part~='cell-pinned'] {\n background: var(--_row-surface);\n }\n /*\n * The edge shadow, once per pinned side (.pin-shadow, beside the scroller).\n * --argus-pinned-shadow is written for a shadow falling toward the end side;\n * the start edge in RTL and the end edge in LTR mirror it.\n */\n .pin-shadow {\n position: absolute;\n inset-block-start: 0;\n block-size: min(var(--_view-h, 100%), var(--_content-h, 100%));\n z-index: 3;\n pointer-events: none;\n }\n .pin-shadow[data-side='start'] {\n inset-inline-start: 0;\n }\n .pin-shadow[data-side='end'] {\n inset-inline-end: var(--_sb-x, 0px);\n }\n .pin-shadow::after {\n content: '';\n position: absolute;\n inset: 0;\n box-shadow: var(\n --argus-pinned-shadow,\n 4px 0 6px -4px light-dark(rgb(0 0 0 / 0.2), rgb(0 0 0 / 0.6))\n );\n transform: scaleX(var(--_dir));\n }\n .pin-shadow[data-side='end']::after {\n transform: scaleX(calc(-1 * var(--_dir)));\n }\n\n [data-align='center'] {\n justify-content: center;\n text-align: center;\n }\n [data-align='end'] {\n justify-content: flex-end;\n text-align: end;\n }\n /*\n * Wrapped text (ColumnDef.wrap). In a fixed-height row, text taller than the row\n * starts at the top (safe) and is clipped below.\n */\n [part~='cell'][data-wrap] {\n white-space: normal;\n overflow-wrap: anywhere;\n align-items: center;\n align-items: safe center;\n }\n\n /*\n * rowHeight: 'auto'. Body and pinned rows fit their content, at least one row\n * tall; the header keeps its height. Content sits at the top of the cell, with\n * the block padding that centres one line in a one-row cell, so one-line rows\n * look as they do with a fixed height. The rows' heights are measured and\n * placed by the table: the browser's scroll anchoring would fight it.\n */\n [data-auto-rows] {\n overflow-anchor: none;\n }\n [data-auto-rows] > [part~='body'] > .row,\n [data-auto-rows] > .pinned > .row {\n block-size: auto;\n min-block-size: var(--_row-height);\n grid-template-rows: minmax(var(--_row-height), auto);\n }\n /* Reading the rows' own heights: without the height each row renders at. */\n [data-auto-rows] > [part~='body'] > .row[data-measure-row] {\n min-block-size: var(--_row-height) !important;\n }\n [data-auto-rows] [part~='cell'] {\n align-items: start;\n padding-block: var(\n --argus-cell-padding-block,\n max(0px, (var(--_row-height) - 1lh - var(--argus-border-width, 1px)) / 2)\n );\n }\n /* Skeleton bars and the Retry button, centred in a one-row row as before. */\n [data-auto-rows] [part~='row-loading'] > [part~='cell'] {\n align-items: center;\n padding-block: 0;\n }\n [data-auto-rows] > [part~='body'] > .row > [role='gridcell'] {\n scroll-margin-block: calc(var(--_row-height) + var(--_pin-top-h, 0px))\n var(--_pin-bottom-h, 0px);\n }\n [data-auto-rows] > [part~='empty'] {\n min-block-size: calc(\n 100% - var(--_row-height) - var(--_pin-top-h, 0px) - var(--_pin-bottom-h, 0px)\n );\n }\n ::slotted(*) {\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n /* Fitting a column to its content: set for one synchronous measurement, never painted. */\n [data-measure] {\n inline-size: max-content;\n }\n\n /* Rows of a data source that haven't loaded: a bar per cell, pulsing gently. */\n [part~='skeleton'] {\n flex: none;\n inline-size: min(60%, 8em);\n block-size: 0.6em;\n border-radius: 0.3em;\n background: var(--argus-skeleton-bg, color-mix(in srgb, currentColor 12%, transparent));\n animation: argus-skeleton-pulse 1.6s ease-in-out infinite;\n }\n [part~='skeleton-error'] {\n animation: none;\n opacity: 0.5;\n }\n @keyframes argus-skeleton-pulse {\n 50% {\n opacity: 0.45;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n [part~='skeleton'] {\n animation: none;\n }\n }\n [part~='retry'] {\n font: inherit;\n color: inherit;\n cursor: pointer;\n }\n\n [part~='empty'] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-block-size: calc(\n 100% - (1 + var(--_pin-top-rows, 0) + var(--_pin-bottom-rows, 0)) * var(--_row-height)\n );\n padding: var(--_pad);\n box-sizing: border-box;\n }\n\n /* Measures the resolved row height in px for the virtualizer. */\n .probe {\n position: absolute;\n inset-block-start: 0;\n inline-size: 0;\n block-size: var(--_row-height);\n visibility: hidden;\n pointer-events: none;\n }\n }\n`;\n"],"mappings":";;;;;;;AAOA,IAAa,aAAa,GAAG"}
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/styles/base.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * Base styles. Everything sits in `@layer argus-base`, and every value reads a\n * public token with a neutral fallback, so themes and page CSS always win.\n * `--_*` properties are private.\n */\nexport const baseStyles = css`\n @layer argus-base {\n :host {\n display: block;\n /* A table that virtualizes needs a bounded height; set your own on the element. */\n block-size: 400px;\n box-sizing: border-box;\n contain: content;\n color-scheme: light dark;\n color: var(--argus-fg, CanvasText);\n background: var(--argus-bg, Canvas);\n font: var(--argus-font, inherit);\n /* contain: content clips the header, rows and pinned shadows to the rounded corners. */\n border: var(--argus-border, none);\n border-radius: var(--argus-border-radius, 0);\n --_row-height: var(--argus-row-height, 40px);\n --_pad: var(--argus-cell-padding-inline, 12px);\n --_border: var(--argus-border-width, 1px) solid\n var(--argus-border-color, light-dark(#e5e7eb, #374151));\n /* 1 in LTR, -1 in RTL: mirrors the pinned-edge shadow. */\n --_dir: 1;\n }\n :host(:dir(rtl)) {\n --_dir: -1;\n }\n :host([density='compact']) {\n --_row-height: var(--argus-row-height, 32px);\n --_pad: var(--argus-cell-padding-inline, 8px);\n }\n :host([density='spacious']) {\n --_row-height: var(--argus-row-height, 52px);\n --_pad: var(--argus-cell-padding-inline, 16px);\n }\n :host([hidden]) {\n display: none;\n }\n\n [part~='table'] {\n position: relative;\n block-size: 100%;\n overflow: auto;\n overscroll-behavior: contain;\n /* The header: one row tall, and a row more per level of column groups. */\n --_header-h: calc(var(--_header-rows, 1) * var(--_row-height));\n }\n\n /*\n * A cell focused or moved to by keyboard scrolls clear of the sticky header, the\n * pinned rows and the pinned columns: scroll margins on the cells that scroll.\n * Not scroll padding on the scroller: Chromium and WebKit measure the sticky\n * header and pinned rows against it too, so focusing one of them (a click, Tab,\n * an arrow key) scrolled the body.\n */\n [part~='body'] > .row > [role='gridcell'] {\n scroll-margin-block: calc(var(--_header-h) + var(--_pin-top-rows, 0) * var(--_row-height))\n calc(var(--_pin-bottom-rows, 0) * var(--_row-height));\n }\n [role='gridcell']:not([data-pin]),\n [role='columnheader']:not([data-pin]) {\n scroll-margin-inline: var(--_pin-start, 0) var(--_pin-end, 0);\n }\n\n .row {\n display: grid;\n grid-template-columns: var(--_cols);\n inline-size: var(--_width);\n min-inline-size: 100%;\n block-size: var(--_row-height);\n box-sizing: border-box;\n }\n\n [part~='header'] {\n position: sticky;\n inset-block-start: 0;\n z-index: 2;\n }\n\n [part~='body'] {\n position: relative;\n contain: layout;\n }\n [part~='body'] > .row {\n position: absolute;\n inset-block-start: 0;\n inset-inline-start: 0;\n }\n [part~='row'] {\n --_row-bg: var(--argus-bg, Canvas);\n --_row-surface: var(--_row-bg);\n background: var(--_row-surface);\n }\n [part~='row-odd'] {\n --_row-bg: var(--argus-row-odd-bg, var(--argus-bg, Canvas));\n }\n [part~='row']:hover {\n --_row-surface: var(--argus-row-hover-bg, var(--_row-bg));\n }\n /* Selected wins over stripes and hover. */\n [part~='row-selected'],\n [part~='row-selected']:hover {\n --_row-bg: var(\n --argus-row-selected-bg,\n color-mix(in srgb, Highlight 22%, var(--argus-bg, Canvas))\n );\n --_row-surface: var(--_row-bg);\n }\n @media (forced-colors: active) {\n [part~='row-selected'] {\n forced-color-adjust: none;\n --_row-bg: Highlight;\n color: HighlightText;\n }\n }\n\n /*\n * Pinned rows: a sticky row group below the header and one at the bottom of\n * the view. The header is --_header-h tall. They keep their place in the flow,\n * so body row i still starts i rows below the top group.\n */\n .pinned {\n position: sticky;\n z-index: 2;\n }\n .pinned[data-side='top'] {\n inset-block-start: var(--_header-h);\n }\n .pinned[data-side='bottom'] {\n inset-block-end: 0;\n }\n [part~='row-pinned'] {\n --_row-bg: var(--argus-row-pinned-bg, var(--argus-bg, Canvas));\n }\n\n [part~='cell'],\n [part~='header-cell'] {\n display: flex;\n align-items: center;\n min-inline-size: 0;\n box-sizing: border-box;\n padding-inline: var(--_pad);\n overflow: hidden;\n white-space: nowrap;\n border-block-end: var(--_border);\n }\n /* The checkbox column: the checkbox centred, no padding to squeeze it. */\n [part~='cell-selection'],\n [part~='header-cell-selection'] {\n justify-content: center;\n padding-inline: 0;\n }\n [part~='selection-checkbox'] {\n margin: 0;\n cursor: pointer;\n }\n [part~='header-cell'] {\n background: var(--argus-header-bg, light-dark(#f9fafb, #111827));\n color: var(--argus-header-fg, inherit);\n font-weight: 600;\n position: relative;\n }\n /*\n * Column groups: a cell per group over its columns, and an empty filler over\n * columns without a group at that level, so the header reads as one block. The\n * label of a group in the scrolling area stays in view while its columns scroll\n * past (sticky inside the cell, which clips rather than scrolls).\n */\n [part~='header-group-cell'],\n [part~='header-group-filler'] {\n box-sizing: border-box;\n min-inline-size: 0;\n background: var(--argus-header-bg, light-dark(#f9fafb, #111827));\n color: var(--argus-header-fg, inherit);\n }\n [part~='header-group-cell'] {\n display: flex;\n align-items: center;\n position: relative;\n padding-inline: var(--_pad);\n overflow: clip;\n white-space: nowrap;\n font-weight: 600;\n border-block-end: var(--_border);\n border-inline-end: var(--_border);\n }\n .group-label {\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n [part~='header-group-cell']:not([data-pin]) > .group-label {\n position: sticky;\n inset-inline-start: calc(var(--_pin-start, 0px) + var(--_pad));\n }\n [data-move-group] {\n user-select: none;\n }\n [data-dragging] [part~='header-group-cell'] {\n cursor: grabbing;\n }\n [part~='header-group-cell-dragging'] {\n opacity: 0.5;\n }\n [part~='resize-handle'] {\n position: absolute;\n inset-block: 0;\n inset-inline-end: 0;\n inline-size: 8px;\n z-index: 1;\n cursor: col-resize;\n touch-action: none;\n }\n [part~='resize-handle']::after {\n content: '';\n position: absolute;\n inset-block: 25%;\n inset-inline-end: 0;\n inline-size: 2px;\n background: var(--argus-resize-handle-color, light-dark(#9ca3af, #6b7280));\n opacity: 0;\n }\n [part~='header-cell']:hover [part~='resize-handle']::after,\n [part~='header-cell']:focus-visible [part~='resize-handle']::after,\n [part~='header-group-cell']:hover [part~='resize-handle']::after,\n [part~='header-group-cell']:focus-visible [part~='resize-handle']::after,\n [part~='resize-handle'][data-active]::after {\n opacity: 1;\n }\n [data-sort-col] {\n cursor: pointer;\n user-select: none;\n }\n /* A drag on a reorderable header moves the column, so its text isn't selectable. */\n [data-move-col] {\n user-select: none;\n }\n [data-dragging],\n [data-dragging] [part~='header-cell'] {\n cursor: grabbing;\n }\n [part~='header-cell-dragging'] {\n opacity: 0.5;\n }\n /* The line where a dragged column would land, on the target header's logical edge. */\n [part~='drop-indicator'] {\n position: absolute;\n inset-block: 0;\n inline-size: 2px;\n z-index: 2;\n background: var(--argus-drop-indicator-color, Highlight);\n pointer-events: none;\n }\n [part~='drop-indicator'][data-edge='start'] {\n inset-inline-start: 0;\n }\n [part~='drop-indicator'][data-edge='end'] {\n inset-inline-end: 0;\n }\n [part~='header-cell']:focus-visible,\n [part~='header-group-cell']:focus-visible,\n [part~='cell']:focus-visible {\n outline: var(--argus-focus-ring, 2px solid Highlight);\n outline-offset: -2px;\n }\n /* A small chevron after the header content; only the sorted column shows it. */\n .sort-indicator {\n flex: none;\n inline-size: 0.35em;\n block-size: 0.35em;\n margin-inline-start: 0.4em;\n /* Physical sides on purpose: the chevron is rotated, so it must not mirror in RTL. */\n border-right: 0.12em solid currentColor;\n border-bottom: 0.12em solid currentColor;\n visibility: hidden;\n }\n [aria-sort] .sort-indicator {\n visibility: visible;\n }\n [aria-sort='ascending'] .sort-indicator {\n transform: translateY(0.1em) rotate(-135deg);\n }\n [aria-sort='descending'] .sort-indicator {\n transform: translateY(-0.1em) rotate(45deg);\n }\n [data-align='end'] .sort-indicator {\n order: -1;\n margin-inline: 0 0.4em;\n }\n /* A funnel: the column is filtered. */\n [part~='filter-indicator'] {\n flex: none;\n inline-size: 0.7em;\n block-size: 0.7em;\n margin-inline-start: 0.35em;\n background: var(--argus-filter-indicator-color, currentColor);\n clip-path: polygon(0 0, 100% 0, 62% 48%, 62% 100%, 38% 82%, 38% 48%);\n }\n [data-align='end'] [part~='filter-indicator'] {\n order: -2;\n margin-inline: 0 0.35em;\n }\n @media (forced-colors: active) {\n /* Backgrounds are forced to Canvas there, which would hide the funnel. */\n [part~='filter-indicator'] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n /*\n * Pinned columns: sticky inside the scroll container, with offsets from the\n * table model. Logical insets make RTL work without extra rules.\n */\n [data-pin] {\n position: sticky;\n z-index: 1;\n }\n [data-pin='start'] {\n inset-inline-start: var(--_pin-offset);\n }\n [data-pin='end'] {\n inset-inline-end: var(--_pin-offset);\n }\n /*\n * Pinned body cells are sticky only while the table scrolls sideways\n * (data-hscroll on the scroller). At rest they are plain cells shifted by the\n * horizontal scroll offset, which vertical scrolling never changes: a sticky\n * cell per row is a compositing layer each, and Safari stutters moving them\n * (PinScrollController). Both place the cells at the same spot.\n */\n [part~='row'] > [data-pin] {\n position: relative;\n inset-inline: auto;\n }\n [part~='row'] > [data-pin='start'] {\n transform: translateX(var(--_sx, 0px));\n }\n [part~='row'] > [data-pin='end'] {\n transform: translateX(calc(var(--_sx, 0px) - var(--_dir) * var(--_max-x, 0px)));\n }\n [data-hscroll] [part~='row'] > [data-pin] {\n position: sticky;\n transform: none;\n }\n [data-hscroll] [part~='row'] > [data-pin='start'] {\n inset-inline-start: var(--_pin-offset);\n }\n [data-hscroll] [part~='row'] > [data-pin='end'] {\n inset-inline-end: var(--_pin-offset);\n }\n [part~='cell-pinned'] {\n background: var(--_row-surface);\n }\n /*\n * The edge shadow, once per pinned side (.pin-shadow, beside the scroller).\n * --argus-pinned-shadow is written for a shadow falling toward the end side;\n * the start edge in RTL and the end edge in LTR mirror it.\n */\n .pin-shadow {\n position: absolute;\n inset-block-start: 0;\n block-size: min(var(--_view-h, 100%), var(--_content-h, 100%));\n z-index: 3;\n pointer-events: none;\n }\n .pin-shadow[data-side='start'] {\n inset-inline-start: 0;\n }\n .pin-shadow[data-side='end'] {\n inset-inline-end: var(--_sb-x, 0px);\n }\n .pin-shadow::after {\n content: '';\n position: absolute;\n inset: 0;\n box-shadow: var(\n --argus-pinned-shadow,\n 4px 0 6px -4px light-dark(rgb(0 0 0 / 0.2), rgb(0 0 0 / 0.6))\n );\n transform: scaleX(var(--_dir));\n }\n .pin-shadow[data-side='end']::after {\n transform: scaleX(calc(-1 * var(--_dir)));\n }\n\n [data-align='center'] {\n justify-content: center;\n text-align: center;\n }\n [data-align='end'] {\n justify-content: flex-end;\n text-align: end;\n }\n /*\n * Wrapped text (ColumnDef.wrap). In a fixed-height row, text taller than the row\n * starts at the top (safe) and is clipped below.\n */\n [part~='cell'][data-wrap] {\n white-space: normal;\n overflow-wrap: anywhere;\n align-items: center;\n align-items: safe center;\n }\n\n /*\n * rowHeight: 'auto'. Body and pinned rows fit their content, at least one row\n * tall; the header keeps its height. Content sits at the top of the cell, with\n * the block padding that centres one line in a one-row cell, so one-line rows\n * look as they do with a fixed height. The rows' heights are measured and\n * placed by the table: the browser's scroll anchoring would fight it.\n */\n [data-auto-rows] {\n overflow-anchor: none;\n }\n [data-auto-rows] > [part~='body'] > .row,\n [data-auto-rows] > .pinned > .row {\n block-size: auto;\n min-block-size: var(--_row-height);\n grid-template-rows: minmax(var(--_row-height), auto);\n }\n /* Reading the rows' own heights: without the height each row renders at. */\n [data-auto-rows] > [part~='body'] > .row[data-measure-row] {\n min-block-size: var(--_row-height) !important;\n }\n [data-auto-rows] [part~='cell'] {\n align-items: start;\n padding-block: var(\n --argus-cell-padding-block,\n max(0px, (var(--_row-height) - 1lh - var(--argus-border-width, 1px)) / 2)\n );\n }\n /* Skeleton bars and the Retry button, centred in a one-row row as before. */\n [data-auto-rows] [part~='row-loading'] > [part~='cell'] {\n align-items: center;\n padding-block: 0;\n }\n [data-auto-rows] > [part~='body'] > .row > [role='gridcell'] {\n scroll-margin-block: calc(var(--_header-h) + var(--_pin-top-h, 0px)) var(--_pin-bottom-h, 0px);\n }\n [data-auto-rows] > [part~='empty'] {\n min-block-size: calc(\n 100% - var(--_header-h) - var(--_pin-top-h, 0px) - var(--_pin-bottom-h, 0px)\n );\n }\n ::slotted(*) {\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n /* Fitting a column to its content: set for one synchronous measurement, never painted. */\n [data-measure] {\n inline-size: max-content;\n }\n\n /* Rows of a data source that haven't loaded: a bar per cell, pulsing gently. */\n [part~='skeleton'] {\n flex: none;\n inline-size: min(60%, 8em);\n block-size: 0.6em;\n border-radius: 0.3em;\n background: var(--argus-skeleton-bg, color-mix(in srgb, currentColor 12%, transparent));\n animation: argus-skeleton-pulse 1.6s ease-in-out infinite;\n }\n [part~='skeleton-error'] {\n animation: none;\n opacity: 0.5;\n }\n @keyframes argus-skeleton-pulse {\n 50% {\n opacity: 0.45;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n [part~='skeleton'] {\n animation: none;\n }\n }\n [part~='retry'] {\n font: inherit;\n color: inherit;\n cursor: pointer;\n }\n\n [part~='empty'] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-block-size: calc(\n 100% - var(--_header-h) - (var(--_pin-top-rows, 0) + var(--_pin-bottom-rows, 0)) *\n var(--_row-height)\n );\n padding: var(--_pad);\n box-sizing: border-box;\n }\n\n /* Measures the resolved row height in px for the virtualizer. */\n .probe {\n position: absolute;\n inset-block-start: 0;\n inline-size: 0;\n block-size: var(--_row-height);\n visibility: hidden;\n pointer-events: none;\n }\n }\n`;\n"],"mappings":";;;;;;;AAOA,IAAa,aAAa,GAAG"}
|