argus-table 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -2
- package/dist/_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.js +8 -0
- package/dist/argus-table.js +492 -0
- package/dist/argus-table.js.map +1 -0
- package/dist/cdn/argus-table.js +4743 -0
- package/dist/cdn/argus-table.js.map +1 -0
- package/dist/controllers/resize-drag.js +44 -0
- package/dist/controllers/resize-drag.js.map +1 -0
- package/dist/controllers/table-controller.js +24 -0
- package/dist/controllers/table-controller.js.map +1 -0
- package/dist/controllers/virtual-controller.js +83 -0
- package/dist/controllers/virtual-controller.js.map +1 -0
- package/dist/core/src/columns/resolve-columns.js +62 -0
- package/dist/core/src/columns/resolve-columns.js.map +1 -0
- package/dist/core/src/state/controllable.js +31 -0
- package/dist/core/src/state/controllable.js.map +1 -0
- package/dist/core/src/table/create-table.js +185 -0
- package/dist/core/src/table/create-table.js.map +1 -0
- package/dist/core/src/virtual/row-pool.js +64 -0
- package/dist/core/src/virtual/row-pool.js.map +1 -0
- package/dist/custom-elements.json +1131 -0
- package/dist/events.js +22 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +3 -0
- package/dist/render/body.js +49 -0
- package/dist/render/body.js.map +1 -0
- package/dist/render/cells.js +27 -0
- package/dist/render/cells.js.map +1 -0
- package/dist/render/content.js +35 -0
- package/dist/render/content.js.map +1 -0
- package/dist/render/header.js +62 -0
- package/dist/render/header.js.map +1 -0
- package/dist/styles/base.js +235 -0
- package/dist/styles/base.js.map +1 -0
- package/package.json +61 -3
package/dist/events.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
/** Fired after an update when the visible row range changed. */
|
|
3
|
+
var RANGE_CHANGE = "argus-range-change";
|
|
4
|
+
/** Fired after the sort order changed (uncontrolled), or with the proposed order (controlled). */
|
|
5
|
+
var SORT_CHANGE = "argus-sort-change";
|
|
6
|
+
/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */
|
|
7
|
+
var COLUMN_RESIZE = "argus-column-resize";
|
|
8
|
+
/** Fired when a body row was clicked, after `argus-cell-click`. */
|
|
9
|
+
var ROW_CLICK = "argus-row-click";
|
|
10
|
+
/** Fired when a body cell was clicked, before `argus-row-click`. */
|
|
11
|
+
var CELL_CLICK = "argus-cell-click";
|
|
12
|
+
/** Dispatches a bubbling, composed `CustomEvent`, the shape of every public event. */
|
|
13
|
+
function fire(host, name, detail) {
|
|
14
|
+
return host.dispatchEvent(new CustomEvent(name, {
|
|
15
|
+
detail,
|
|
16
|
+
bubbles: true,
|
|
17
|
+
composed: true
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export { CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE, fire };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["import type {\n CellClickDetail,\n ColumnResizeDetail,\n RangeChangeDetail,\n RowClickDetail,\n SortChangeDetail,\n} from '@argus-table/core';\n\n/** Fired after an update when the visible row range changed. */\nexport const RANGE_CHANGE = 'argus-range-change';\n\n/** Fired after the sort order changed (uncontrolled), or with the proposed order (controlled). */\nexport const SORT_CHANGE = 'argus-sort-change';\n\n/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */\nexport const COLUMN_RESIZE = 'argus-column-resize';\n\n/** Fired when a body row was clicked, after `argus-cell-click`. */\nexport const ROW_CLICK = 'argus-row-click';\n\n/** Fired when a body cell was clicked, before `argus-row-click`. */\nexport const CELL_CLICK = 'argus-cell-click';\n\ndeclare global {\n interface HTMLElementEventMap {\n 'argus-range-change': CustomEvent<RangeChangeDetail>;\n 'argus-sort-change': CustomEvent<SortChangeDetail>;\n 'argus-column-resize': CustomEvent<ColumnResizeDetail>;\n 'argus-row-click': CustomEvent<RowClickDetail<unknown>>;\n 'argus-cell-click': CustomEvent<CellClickDetail<unknown>>;\n }\n}\n\n/** Dispatches a bubbling, composed `CustomEvent`, the shape of every public event. */\nexport function fire(host: HTMLElement, name: string, detail: unknown): boolean {\n return host.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));\n}\n"],"mappings":";;AASA,IAAa,eAAe;;AAG5B,IAAa,cAAc;;AAG3B,IAAa,gBAAgB;;AAG7B,IAAa,YAAY;;AAGzB,IAAa,aAAa;;AAa1B,SAAgB,KAAK,MAAmB,MAAc,QAA0B;CAC9E,OAAO,KAAK,cAAc,IAAI,YAAY,MAAM;EAAE;EAAQ,SAAS;EAAM,UAAU;CAAK,CAAC,CAAC;AAC5F"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { TemplateResult, LitElement, CSSResultGroup, PropertyValues } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public types shared by `@argus-table/core` and `argus-table`. `argus-table`
|
|
5
|
+
* re-exports them with `TContent` fixed to its own `CellContent`.
|
|
6
|
+
*
|
|
7
|
+
* No TanStack type appears here: TanStack stays an implementation detail.
|
|
8
|
+
*/
|
|
9
|
+
/** Logical side of a pinned column: `'start'` is the left in LTR and the right in RTL. */
|
|
10
|
+
type ColumnPin = 'start' | 'end';
|
|
11
|
+
/** Text alignment inside a cell, in logical terms. */
|
|
12
|
+
type ColumnAlign = 'start' | 'center' | 'end';
|
|
13
|
+
/**
|
|
14
|
+
* Column width: a fixed pixel width, or a share of the free space (`{ flex: 1 }`).
|
|
15
|
+
* `'auto'` (fit to content) arrives in Phase 2.
|
|
16
|
+
*/
|
|
17
|
+
type ColumnWidth = number | {
|
|
18
|
+
flex: number;
|
|
19
|
+
};
|
|
20
|
+
/** What a `cell` function receives besides the row itself. */
|
|
21
|
+
interface CellContext<TData> {
|
|
22
|
+
/** The row object from `data`. */
|
|
23
|
+
row: TData;
|
|
24
|
+
/** The id from `getRowId`, or the row's index as a string. */
|
|
25
|
+
rowId: string;
|
|
26
|
+
/** Index of the row in the current (sorted) row model. */
|
|
27
|
+
index: number;
|
|
28
|
+
/** The column's id. */
|
|
29
|
+
columnId: string;
|
|
30
|
+
/** The value read by the column's `accessor`, or `undefined` without one. */
|
|
31
|
+
value: unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One column of the table.
|
|
35
|
+
*
|
|
36
|
+
* @typeParam TData - the row type.
|
|
37
|
+
* @typeParam TContent - what `header` and `cell` may return. `argus-table`
|
|
38
|
+
* fixes it to `CellContent` (a Lit template, a DOM node, a string or a number).
|
|
39
|
+
*/
|
|
40
|
+
interface ColumnDef$1<TData, TContent = unknown> {
|
|
41
|
+
/** Stable key, used in state objects, events and persistence. Must be unique. */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Header content, or a function that returns it. */
|
|
44
|
+
header?: TContent | (() => TContent);
|
|
45
|
+
/**
|
|
46
|
+
* How to read the cell value: a property path such as `'customer.name'`, or a
|
|
47
|
+
* function of the row. Without an accessor the value is `undefined`; the column
|
|
48
|
+
* then needs a `cell` function to show anything.
|
|
49
|
+
*/
|
|
50
|
+
accessor?: string | ((row: TData, index: number) => unknown);
|
|
51
|
+
/** Custom cell content. Without it, the cell shows the accessor's value as text. */
|
|
52
|
+
cell?: (row: TData, context: CellContext<TData>) => TContent;
|
|
53
|
+
/**
|
|
54
|
+
* Fixed width in px, or `{ flex }` for a share of the free space (at least `minWidth`).
|
|
55
|
+
* Pinned columns ignore `flex`. Default: 150 px.
|
|
56
|
+
*/
|
|
57
|
+
width?: ColumnWidth;
|
|
58
|
+
/** Lower bound for resizing and for flex columns, in px. Default: 40. */
|
|
59
|
+
minWidth?: number;
|
|
60
|
+
/** Upper bound for resizing, in px. Default: no limit. */
|
|
61
|
+
maxWidth?: number;
|
|
62
|
+
/** Pins the column to the start or end side. */
|
|
63
|
+
pin?: ColumnPin;
|
|
64
|
+
/** Whether the header can be clicked to sort. Default: `false`. */
|
|
65
|
+
sortable?: boolean;
|
|
66
|
+
/** Whether the column can be resized. Default: `true`. */
|
|
67
|
+
resizable?: boolean;
|
|
68
|
+
/** Text alignment. Default: `'start'`. */
|
|
69
|
+
align?: ColumnAlign;
|
|
70
|
+
}
|
|
71
|
+
/** One sorted column. */
|
|
72
|
+
interface SortItem {
|
|
73
|
+
/** The column's id. */
|
|
74
|
+
id: string;
|
|
75
|
+
/** `true` for descending order. */
|
|
76
|
+
desc: boolean;
|
|
77
|
+
}
|
|
78
|
+
/** The sort order: an empty array when unsorted. v0.1 sorts by one column at a time. */
|
|
79
|
+
type SortState = SortItem[];
|
|
80
|
+
/** Column widths in px set by resizing, keyed by column id. */
|
|
81
|
+
type ColumnSizingState = Record<string, number>;
|
|
82
|
+
/** `detail` of `argus-sort-change`. */
|
|
83
|
+
interface SortChangeDetail {
|
|
84
|
+
sorting: SortState;
|
|
85
|
+
}
|
|
86
|
+
/** `detail` of `argus-column-resize`. */
|
|
87
|
+
interface ColumnResizeDetail {
|
|
88
|
+
columnId: string;
|
|
89
|
+
width: number;
|
|
90
|
+
sizing: ColumnSizingState;
|
|
91
|
+
}
|
|
92
|
+
/** `detail` of `argus-row-click`. */
|
|
93
|
+
interface RowClickDetail<TData> {
|
|
94
|
+
row: TData;
|
|
95
|
+
rowId: string;
|
|
96
|
+
index: number;
|
|
97
|
+
}
|
|
98
|
+
/** `detail` of `argus-cell-click`. */
|
|
99
|
+
interface CellClickDetail<TData> {
|
|
100
|
+
row: TData;
|
|
101
|
+
columnId: string;
|
|
102
|
+
value: unknown;
|
|
103
|
+
}
|
|
104
|
+
/** `detail` of `argus-range-change`: the first and last rendered row index (inclusive). */
|
|
105
|
+
interface RangeChangeDetail {
|
|
106
|
+
start: number;
|
|
107
|
+
end: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* What a column's `header` or `cell` may produce: a Lit template, a DOM node, or
|
|
112
|
+
* text. The content is rendered into the table's light DOM and slotted into the
|
|
113
|
+
* cell, so page CSS and the page's own components work inside it.
|
|
114
|
+
*/
|
|
115
|
+
type CellContent = TemplateResult | Node | string | number;
|
|
116
|
+
/** One column of `<argus-table>`: what it shows, how wide it is, and how it behaves. */
|
|
117
|
+
type ColumnDef<T = unknown> = ColumnDef$1<T, CellContent>;
|
|
118
|
+
/** Row density. Each one sets defaults for `--argus-row-height` and `--argus-cell-padding-inline`. */
|
|
119
|
+
type Density = 'compact' | 'comfortable' | 'spacious';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A virtualized data table.
|
|
123
|
+
*
|
|
124
|
+
* Set `columns` and `data` as properties. Header and cell content is rendered
|
|
125
|
+
* into the element's light DOM and slotted into the cells, so page CSS and the
|
|
126
|
+
* page's own components work inside cells.
|
|
127
|
+
*
|
|
128
|
+
* @summary Data table with pinned rows and columns and infinite loading.
|
|
129
|
+
*
|
|
130
|
+
* @fires argus-sort-change - A sortable header was clicked, or activated with Enter or Space. `detail: { sorting }`: the new order, or the proposed one when `sorting` is controlled.
|
|
131
|
+
* @fires argus-column-resize - A column was resized by dragging its handle or with Alt+Arrow on its header. `detail: { columnId, width, sizing }`: the new widths, or the proposed ones when `columnSizing` is controlled. Fires on every frame of a drag.
|
|
132
|
+
* @fires argus-cell-click - A body cell was clicked, or Enter was pressed on it. `detail: { row, columnId, value }`. Fires before `argus-row-click`.
|
|
133
|
+
* @fires argus-row-click - A body row was clicked, or Enter was pressed on one of its cells. `detail: { row, rowId, index }`.
|
|
134
|
+
* @fires argus-range-change - The visible rows changed. `detail: { start, end }` (row indices, inclusive).
|
|
135
|
+
*
|
|
136
|
+
* @slot empty - Shown instead of the body when there are no rows. Default: "No rows".
|
|
137
|
+
*
|
|
138
|
+
* @csspart table - The scroll container that holds header and body.
|
|
139
|
+
* @csspart header - The sticky header row group.
|
|
140
|
+
* @csspart header-row - The header row.
|
|
141
|
+
* @csspart header-cell - Every header cell.
|
|
142
|
+
* @csspart header-cell-sorted - The header of the sorted column. The direction is in `aria-sort`.
|
|
143
|
+
* @csspart resize-handle - The drag handle on the inline-end edge of a resizable column's header.
|
|
144
|
+
* @csspart body - The row group holding the rendered body rows.
|
|
145
|
+
* @csspart row - Every body row.
|
|
146
|
+
* @csspart row-even - Body rows with an even index (0-based).
|
|
147
|
+
* @csspart row-odd - Body rows with an odd index.
|
|
148
|
+
* @csspart cell - Every body cell.
|
|
149
|
+
* @csspart cell-sorted - Body cells in the sorted column.
|
|
150
|
+
* @csspart cell-pinned - Body cells in a pinned column, on either side.
|
|
151
|
+
* @csspart cell-pinned-edge - The pinned body cell next to the scrolling columns (one per pinned side).
|
|
152
|
+
* @csspart empty - The empty-state container.
|
|
153
|
+
*
|
|
154
|
+
* @cssprop [--argus-bg=Canvas] - Background of the table and rows.
|
|
155
|
+
* @cssprop [--argus-fg=CanvasText] - Text colour.
|
|
156
|
+
* @cssprop [--argus-font=inherit] - Font shorthand for the table.
|
|
157
|
+
* @cssprop [--argus-header-bg] - Header background.
|
|
158
|
+
* @cssprop [--argus-header-fg=inherit] - Header text colour.
|
|
159
|
+
* @cssprop [--argus-border-color] - Colour of the row borders.
|
|
160
|
+
* @cssprop [--argus-border-width=1px] - Width of the row borders.
|
|
161
|
+
* @cssprop [--argus-row-height] - Height of a row. Default per density: 32px, 40px, 52px.
|
|
162
|
+
* @cssprop [--argus-cell-padding-inline] - Inline padding of cells. Default per density: 8px, 12px, 16px.
|
|
163
|
+
* @cssprop [--argus-row-stripe-bg] - Background of odd rows. Default: no stripes.
|
|
164
|
+
* @cssprop [--argus-pinned-shadow] - Shadow on the pinned edge, as a `box-shadow` falling toward the end side (`4px 0 6px -4px …`). Mirrored for the end edge and in RTL; `none` removes it.
|
|
165
|
+
* @cssprop [--argus-resize-handle-color] - Colour of the resize handle's line, shown on hover, focus and drag.
|
|
166
|
+
* @cssprop [--argus-focus-ring=2px solid Highlight] - Outline of the focused header or cell.
|
|
167
|
+
* @cssprop [--argus-row-hover-bg] - Background of the hovered row. Default: no change.
|
|
168
|
+
* @cssprop [--argus-row-selected-bg] - Reserved for row selection, which comes after v0.1. The table doesn't read it yet.
|
|
169
|
+
*/
|
|
170
|
+
declare class ArgusTable<T = unknown> extends LitElement {
|
|
171
|
+
#private;
|
|
172
|
+
static styles: CSSResultGroup;
|
|
173
|
+
/** Column definitions. Property only: functions can't be attributes. */
|
|
174
|
+
columns: readonly ColumnDef<T>[];
|
|
175
|
+
/** The rows to show. Replace the array to update; changes inside it aren't detected. */
|
|
176
|
+
data: readonly T[];
|
|
177
|
+
/** Returns a stable id for a row. Default: the row's index in `data`. */
|
|
178
|
+
getRowId?: (row: T, index: number) => string;
|
|
179
|
+
/** Row height in px. Unset: the density (or the `--argus-row-height` token) decides. */
|
|
180
|
+
rowHeight?: number;
|
|
181
|
+
/** Row density: `compact`, `comfortable` or `spacious`. */
|
|
182
|
+
density: Density;
|
|
183
|
+
/** Rows rendered above and below the visible ones. */
|
|
184
|
+
overscan: number;
|
|
185
|
+
/**
|
|
186
|
+
* The sort order, e.g. `[{ id: 'total', desc: true }]`; `[]` when unsorted. Unset,
|
|
187
|
+
* the table keeps it itself and header clicks change it. Once set, it is
|
|
188
|
+
* controlled: a click only fires `argus-sort-change` with the proposed order,
|
|
189
|
+
* and nothing changes until you set the property. Set `undefined` to hand it back.
|
|
190
|
+
*/
|
|
191
|
+
get sorting(): SortState;
|
|
192
|
+
set sorting(value: SortState | undefined);
|
|
193
|
+
/**
|
|
194
|
+
* Resized widths in px by column id, e.g. `{ name: 240 }`. A resized flex column
|
|
195
|
+
* becomes fixed. Uncontrolled until set, like `sorting`: once set, a resize only
|
|
196
|
+
* fires `argus-column-resize` with the proposed widths. `undefined` hands it back.
|
|
197
|
+
*/
|
|
198
|
+
get columnSizing(): ColumnSizingState;
|
|
199
|
+
set columnSizing(value: ColumnSizingState | undefined);
|
|
200
|
+
connectedCallback(): void;
|
|
201
|
+
disconnectedCallback(): void;
|
|
202
|
+
/** Scrolls so that the row at `index` (in the current row order) is at the top. */
|
|
203
|
+
scrollToRow(index: number): Promise<void>;
|
|
204
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
205
|
+
protected render(): TemplateResult;
|
|
206
|
+
protected firstUpdated(): void;
|
|
207
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
208
|
+
}
|
|
209
|
+
declare global {
|
|
210
|
+
interface HTMLElementTagNameMap {
|
|
211
|
+
'argus-table': ArgusTable;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Fired after an update when the visible row range changed. */
|
|
216
|
+
declare const RANGE_CHANGE = "argus-range-change";
|
|
217
|
+
/** Fired after the sort order changed (uncontrolled), or with the proposed order (controlled). */
|
|
218
|
+
declare const SORT_CHANGE = "argus-sort-change";
|
|
219
|
+
/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */
|
|
220
|
+
declare const COLUMN_RESIZE = "argus-column-resize";
|
|
221
|
+
/** Fired when a body row was clicked, after `argus-cell-click`. */
|
|
222
|
+
declare const ROW_CLICK = "argus-row-click";
|
|
223
|
+
/** Fired when a body cell was clicked, before `argus-row-click`. */
|
|
224
|
+
declare const CELL_CLICK = "argus-cell-click";
|
|
225
|
+
declare global {
|
|
226
|
+
interface HTMLElementEventMap {
|
|
227
|
+
'argus-range-change': CustomEvent<RangeChangeDetail>;
|
|
228
|
+
'argus-sort-change': CustomEvent<SortChangeDetail>;
|
|
229
|
+
'argus-column-resize': CustomEvent<ColumnResizeDetail>;
|
|
230
|
+
'argus-row-click': CustomEvent<RowClickDetail<unknown>>;
|
|
231
|
+
'argus-cell-click': CustomEvent<CellClickDetail<unknown>>;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export { ArgusTable, CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE };
|
|
236
|
+
export type { CellClickDetail, CellContent, CellContext, ColumnAlign, ColumnDef, ColumnPin, ColumnResizeDetail, ColumnSizingState, ColumnWidth, Density, RangeChangeDetail, RowClickDetail, SortChangeDetail, SortItem, SortState };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cellParts, cellSlot, pinStyle } from "./content.js";
|
|
2
|
+
import { html, nothing } from "lit";
|
|
3
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
4
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
5
|
+
// src/render/body.ts
|
|
6
|
+
/**
|
|
7
|
+
* The virtualized body. Rows are keyed by pool slot, so a row entering the window
|
|
8
|
+
* reuses the DOM of one that left (ADR 0001), and they stay in pool order: screen
|
|
9
|
+
* readers get the position from `aria-rowindex` (the header row is 1).
|
|
10
|
+
*/
|
|
11
|
+
function renderBody(rows, columns, totalSize, active) {
|
|
12
|
+
return html`<div
|
|
13
|
+
part="body"
|
|
14
|
+
role="rowgroup"
|
|
15
|
+
style=${styleMap({ blockSize: `${String(totalSize)}px` })}
|
|
16
|
+
>
|
|
17
|
+
${repeat(rows, (r) => r.slot, (r) => renderRow(r, columns, r.index === active?.index ? active.col : void 0))}
|
|
18
|
+
</div>`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One body row. Every cell is focusable (`tabindex=-1`) so clicks focus it; the
|
|
22
|
+
* active one is the grid's single tab stop (roving tabindex).
|
|
23
|
+
*/
|
|
24
|
+
function renderRow(row, columns, activeCol) {
|
|
25
|
+
return html`<div
|
|
26
|
+
part=${row.index % 2 === 0 ? "row row-even" : "row row-odd"}
|
|
27
|
+
class="row"
|
|
28
|
+
role="row"
|
|
29
|
+
aria-rowindex=${row.index + 2}
|
|
30
|
+
style=${styleMap({ transform: `translateY(${String(row.start)}px)` })}
|
|
31
|
+
>
|
|
32
|
+
${repeat(columns, (c) => c.id, (c) => html`<div
|
|
33
|
+
part=${cellParts(c)}
|
|
34
|
+
role="gridcell"
|
|
35
|
+
aria-colindex=${c.colIndex}
|
|
36
|
+
tabindex=${c.id === activeCol ? 0 : -1}
|
|
37
|
+
data-col=${c.id}
|
|
38
|
+
data-align=${c.align}
|
|
39
|
+
data-pin=${c.pin ?? nothing}
|
|
40
|
+
?data-pin-edge=${c.pinEdge}
|
|
41
|
+
style=${pinStyle(c) ?? nothing}
|
|
42
|
+
>
|
|
43
|
+
<slot name=${cellSlot(row.slot, c.colIndex)}></slot>
|
|
44
|
+
</div>`)}
|
|
45
|
+
</div>`;
|
|
46
|
+
}
|
|
47
|
+
export { renderBody };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.js","names":[],"sources":["../../src/render/body.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport type { ColumnView } from '@argus-table/core';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport type { CellContent } from '../types.js';\nimport { cellParts, cellSlot, pinStyle } from './content.js';\n\ntype Columns<T> = readonly ColumnView<T, CellContent>[];\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/**\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` (the header row is 1).\n */\nexport function renderBody<T>(\n rows: readonly WindowRow[],\n columns: Columns<T>,\n totalSize: number,\n active: ActiveCell | undefined,\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) => renderRow(r, columns, r.index === active?.index ? active.col : undefined),\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).\n */\nfunction renderRow<T>(row: WindowRow, columns: Columns<T>, activeCol: string | undefined) {\n return html`<div\n part=${row.index % 2 === 0 ? 'row row-even' : 'row row-odd'}\n class=\"row\"\n role=\"row\"\n aria-rowindex=${row.index + 2}\n style=${styleMap({ transform: `translateY(${String(row.start)}px)` })}\n >\n ${repeat(\n columns,\n (c) => c.id,\n (c) =>\n html`<div\n part=${cellParts(c)}\n role=\"gridcell\"\n aria-colindex=${c.colIndex}\n tabindex=${c.id === activeCol ? 0 : -1}\n data-col=${c.id}\n data-align=${c.align}\n data-pin=${c.pin ?? nothing}\n ?data-pin-edge=${c.pinEdge}\n style=${pinStyle(c) ?? nothing}\n >\n <slot name=${cellSlot(row.slot, c.colIndex)}></slot>\n </div>`,\n )}\n </div>`;\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,WACd,MACA,SACA,WACA,QACA;CACA,OAAO,IAAI;;;YAGD,SAAS,EAAE,WAAW,GAAG,OAAO,SAAS,EAAE,IAAI,CAAC,EAAE;;MAExD,OACA,OACC,MAAM,EAAE,OACR,MAAM,UAAU,GAAG,SAAS,EAAE,UAAU,QAAQ,QAAQ,OAAO,MAAM,KAAA,CAAS,CACjF,EAAE;;AAEN;;;;;AAMA,SAAS,UAAa,KAAgB,SAAqB,WAA+B;CACxF,OAAO,IAAI;WACF,IAAI,QAAQ,MAAM,IAAI,iBAAiB,cAAc;;;oBAG5C,IAAI,QAAQ,EAAE;YACtB,SAAS,EAAE,WAAW,cAAc,OAAO,IAAI,KAAK,EAAE,KAAK,CAAC,EAAE;;MAEpE,OACA,UACC,MAAM,EAAE,KACR,MACC,IAAI;iBACK,UAAU,CAAC,EAAE;;0BAEJ,EAAE,SAAS;qBAChB,EAAE,OAAO,YAAY,IAAI,GAAG;qBAC5B,EAAE,GAAG;uBACH,EAAE,MAAM;qBACV,EAAE,OAAO,QAAQ;2BACX,EAAE,QAAQ;kBACnB,SAAS,CAAC,KAAK,QAAQ;;uBAElB,SAAS,IAAI,MAAM,EAAE,QAAQ,EAAE;eAElD,EAAE;;AAEN"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cellSlot, formatValue, headerSlot, toChild } from "./content.js";
|
|
2
|
+
import { html, render } from "lit";
|
|
3
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
4
|
+
// src/render/cells.ts
|
|
5
|
+
/**
|
|
6
|
+
* Renders header and cell content into the host's light DOM, as direct children
|
|
7
|
+
* (a slot only takes the host's own children). Keyed and ordered by pool slot,
|
|
8
|
+
* so recycled content updates in place instead of moving (ADR 0001).
|
|
9
|
+
*/
|
|
10
|
+
function renderLightDom(host, model, rows) {
|
|
11
|
+
const columns = model.columns;
|
|
12
|
+
render(html`${repeat(columns, (c) => c.id, (c) => html`<div slot=${headerSlot(c.colIndex)}>${headerContent(c)}</div>`)}${repeat(rows, (r) => r.slot, (r) => repeat(columns, (c) => c.id, (c) => html`<div slot=${cellSlot(r.slot, c.colIndex)}>${cellContent(model, r.index, c)}</div>`))}`, host, { host });
|
|
13
|
+
}
|
|
14
|
+
function headerContent(column) {
|
|
15
|
+
const { header } = column;
|
|
16
|
+
return toChild(typeof header === "function" ? header() : header);
|
|
17
|
+
}
|
|
18
|
+
function cellContent(model, index, column) {
|
|
19
|
+
if (column.cell) {
|
|
20
|
+
const context = model.cellContext(index, column.id);
|
|
21
|
+
return context ? toChild(column.cell(context.row, context)) : "";
|
|
22
|
+
}
|
|
23
|
+
return column.hasAccessor ? formatValue(model.valueAt(index, column.id)) : "";
|
|
24
|
+
}
|
|
25
|
+
export { renderLightDom };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=cells.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cells.js","names":[],"sources":["../../src/render/cells.ts"],"sourcesContent":["import { html, render } from 'lit';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { ColumnView, TableModel } from '@argus-table/core';\nimport type { WindowRow } from '../controllers/virtual-controller.js';\nimport type { CellContent } from '../types.js';\nimport { cellSlot, formatValue, headerSlot, toChild } from './content.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).\n */\nexport function renderLightDom<T>(\n host: HTMLElement,\n model: TableModel<T, CellContent>,\n rows: readonly WindowRow[],\n): void {\n const columns = model.columns;\n render(\n html`${repeat(\n columns,\n (c) => c.id,\n (c) => html`<div slot=${headerSlot(c.colIndex)}>${headerContent(c)}</div>`,\n )}${repeat(\n rows,\n (r) => r.slot,\n (r) =>\n repeat(\n columns,\n (c) => c.id,\n (c) =>\n html`<div slot=${cellSlot(r.slot, c.colIndex)}>${cellContent(model, r.index, c)}</div>`,\n ),\n )}`,\n host,\n { host },\n );\n}\n\nfunction headerContent<T>(column: Column<T>): unknown {\n const { header } = column;\n return toChild(typeof header === 'function' ? header() : header);\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 ? toChild(column.cell(context.row, context)) : '';\n }\n return column.hasAccessor ? formatValue(model.valueAt(index, column.id)) : '';\n}\n"],"mappings":";;;;;;;;;AAcA,SAAgB,eACd,MACA,OACA,MACM;CACN,MAAM,UAAU,MAAM;CACtB,OACE,IAAI,GAAG,OACL,UACC,MAAM,EAAE,KACR,MAAM,IAAI,aAAa,WAAW,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OACrE,IAAI,OACF,OACC,MAAM,EAAE,OACR,MACC,OACE,UACC,MAAM,EAAE,KACR,MACC,IAAI,aAAa,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,YAAY,OAAO,EAAE,OAAO,CAAC,EAAE,OACpF,CACJ,KACA,MACA,EAAE,KAAK,CACT;AACF;AAEA,SAAS,cAAiB,QAA4B;CACpD,MAAM,EAAE,WAAW;CACnB,OAAO,QAAQ,OAAO,WAAW,aAAa,OAAO,IAAI,MAAM;AACjE;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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { nothing } from "lit";
|
|
2
|
+
// src/render/content.ts
|
|
3
|
+
/** Turns header or cell content into a Lit child. */
|
|
4
|
+
function toChild(content) {
|
|
5
|
+
if (content === null || content === void 0) return nothing;
|
|
6
|
+
return typeof content === "number" ? String(content) : content;
|
|
7
|
+
}
|
|
8
|
+
/** Default cell text for an accessor value. */
|
|
9
|
+
function formatValue(value) {
|
|
10
|
+
if (value === null || value === void 0) return "";
|
|
11
|
+
if (value instanceof Date) return value.toLocaleString();
|
|
12
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
13
|
+
}
|
|
14
|
+
/** Private slot names (ADR 0001): cells by pool slot and column, headers by column. */
|
|
15
|
+
var cellSlot = (slot, colIndex) => `c-${String(slot)}-${String(colIndex)}`;
|
|
16
|
+
var headerSlot = (colIndex) => `h-${String(colIndex)}`;
|
|
17
|
+
/** Part list of a body cell: state is in the part name (`::part()` can't take attribute selectors). */
|
|
18
|
+
function cellParts(column) {
|
|
19
|
+
let parts = "cell";
|
|
20
|
+
if (column.pin) parts += column.pinEdge ? " cell-pinned cell-pinned-edge" : " cell-pinned";
|
|
21
|
+
if (column.sorted) parts += " cell-sorted";
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
/** `aria-sort` for a header: only the sorted column carries it (WAI-ARIA APG). */
|
|
25
|
+
function ariaSort(sorted) {
|
|
26
|
+
if (!sorted) return void 0;
|
|
27
|
+
return sorted === "asc" ? "ascending" : "descending";
|
|
28
|
+
}
|
|
29
|
+
/** Inline style of a pinned cell: its sticky inset, from the start or the end edge. */
|
|
30
|
+
function pinStyle(column) {
|
|
31
|
+
return column.pin ? `--_pin-offset: ${String(column.offset)}px` : void 0;
|
|
32
|
+
}
|
|
33
|
+
export { ariaSort, cellParts, cellSlot, formatValue, headerSlot, pinStyle, toChild };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +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/** Private slot names (ADR 0001): cells by pool slot and column, headers by column. */\nexport const cellSlot = (slot: number, colIndex: number) => `c-${String(slot)}-${String(colIndex)}`;\nexport const headerSlot = (colIndex: number) => `h-${String(colIndex)}`;\n\ninterface Pinnable {\n pin: 'start' | 'end' | undefined;\n pinEdge: boolean;\n}\n\n/** Part list of a body cell: state is in the part name (`::part()` can't take attribute selectors). */\nexport function cellParts(column: Pinnable & { sorted: 'asc' | 'desc' | false }): string {\n let parts = 'cell';\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/** `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/** Inline style of a pinned cell: its sticky inset, from the start or the end edge. */\nexport function pinStyle(column: Pinnable & { offset: number }): string | undefined {\n return column.pin ? `--_pin-offset: ${String(column.offset)}px` : undefined;\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;;AAGA,IAAa,YAAY,MAAc,aAAqB,KAAK,OAAO,IAAI,EAAE,GAAG,OAAO,QAAQ;AAChG,IAAa,cAAc,aAAqB,KAAK,OAAO,QAAQ;;AAQpE,SAAgB,UAAU,QAA+D;CACvF,IAAI,QAAQ;CACZ,IAAI,OAAO,KAAK,SAAS,OAAO,UAAU,kCAAkC;CAC5E,IAAI,OAAO,QAAQ,SAAS;CAC5B,OAAO;AACT;;AAGA,SAAgB,SAAS,QAAwE;CAC/F,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO,WAAW,QAAQ,cAAc;AAC1C;;AAGA,SAAgB,SAAS,QAA2D;CAClF,OAAO,OAAO,MAAM,kBAAkB,OAAO,OAAO,MAAM,EAAE,MAAM,KAAA;AACpE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ariaSort, headerSlot, pinStyle } from "./content.js";
|
|
2
|
+
import { html, nothing } from "lit";
|
|
3
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
4
|
+
// src/render/header.ts
|
|
5
|
+
/**
|
|
6
|
+
* The sticky header row. Header content is slotted from the light DOM. Pinned
|
|
7
|
+
* header cells use the same private `data-pin` hooks as body cells, but no parts.
|
|
8
|
+
* Every header carries `data-col` (its column id) and sortable ones also
|
|
9
|
+
* `data-sort-col`; the element's delegated handlers read them. Headers take part
|
|
10
|
+
* in the grid's roving tabindex: the active one is the tab stop (`activeCol`).
|
|
11
|
+
* Resizable columns get a `resize-handle` on the header's inline-end edge. It is
|
|
12
|
+
* not focusable: the focused header resizes with Alt+Arrow.
|
|
13
|
+
*/
|
|
14
|
+
function renderHeader(columns, handlers, activeCol) {
|
|
15
|
+
return html`<div part="header" role="rowgroup">
|
|
16
|
+
<div
|
|
17
|
+
part="header-row"
|
|
18
|
+
class="row"
|
|
19
|
+
role="row"
|
|
20
|
+
aria-rowindex="1"
|
|
21
|
+
@click=${handlers.click}
|
|
22
|
+
@pointerdown=${handlers.pointerdown}
|
|
23
|
+
>
|
|
24
|
+
${repeat(columns, (c) => c.id, (c) => html`<div
|
|
25
|
+
part=${c.sorted ? "header-cell header-cell-sorted" : "header-cell"}
|
|
26
|
+
role="columnheader"
|
|
27
|
+
aria-colindex=${c.colIndex}
|
|
28
|
+
aria-sort=${ariaSort(c.sorted) ?? nothing}
|
|
29
|
+
data-col=${c.id}
|
|
30
|
+
data-sort-col=${c.sortable ? c.id : nothing}
|
|
31
|
+
tabindex=${c.id === activeCol ? 0 : -1}
|
|
32
|
+
data-align=${c.align}
|
|
33
|
+
data-pin=${c.pin ?? nothing}
|
|
34
|
+
?data-pin-edge=${c.pinEdge}
|
|
35
|
+
style=${pinStyle(c) ?? nothing}
|
|
36
|
+
>
|
|
37
|
+
<slot name=${headerSlot(c.colIndex)}></slot>
|
|
38
|
+
${c.sortable ? html`<span class="sort-indicator" aria-hidden="true"></span>` : nothing}
|
|
39
|
+
${c.resizable ? resizeHandle(c) : nothing}
|
|
40
|
+
</div>`)}
|
|
41
|
+
</div>
|
|
42
|
+
</div>`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A vertical separator with the column's width as its value. A flex column has no
|
|
46
|
+
* fixed width until it is resized, so it reports none.
|
|
47
|
+
*/
|
|
48
|
+
function resizeHandle(c) {
|
|
49
|
+
return html`<div
|
|
50
|
+
part="resize-handle"
|
|
51
|
+
role="separator"
|
|
52
|
+
aria-orientation="vertical"
|
|
53
|
+
aria-label="Resize column"
|
|
54
|
+
aria-valuenow=${c.flex === void 0 ? Math.round(c.width) : nothing}
|
|
55
|
+
aria-valuemin=${c.minWidth}
|
|
56
|
+
aria-valuemax=${Number.isFinite(c.maxWidth) ? c.maxWidth : nothing}
|
|
57
|
+
data-resize-col=${c.id}
|
|
58
|
+
></div>`;
|
|
59
|
+
}
|
|
60
|
+
export { renderHeader };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.js","names":[],"sources":["../../src/render/header.ts"],"sourcesContent":["import { html, nothing } from 'lit';\nimport { repeat } from 'lit/directives/repeat.js';\nimport type { ColumnView } from '@argus-table/core';\nimport type { CellContent } from '../types.js';\nimport { ariaSort, headerSlot, pinStyle } from './content.js';\n\nexport interface HeaderHandlers {\n click: (event: MouseEvent) => void;\n pointerdown: (event: PointerEvent) => void;\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. It is\n * not focusable: the focused header resizes with Alt+Arrow.\n */\nexport function renderHeader<T>(\n columns: readonly ColumnView<T, CellContent>[],\n handlers: HeaderHandlers,\n activeCol: string | undefined,\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 >\n ${repeat(\n columns,\n (c) => c.id,\n (c) =>\n html`<div\n part=${c.sorted ? 'header-cell header-cell-sorted' : 'header-cell'}\n role=\"columnheader\"\n aria-colindex=${c.colIndex}\n aria-sort=${ariaSort(c.sorted) ?? nothing}\n data-col=${c.id}\n data-sort-col=${c.sortable ? c.id : nothing}\n tabindex=${c.id === activeCol ? 0 : -1}\n data-align=${c.align}\n data-pin=${c.pin ?? nothing}\n ?data-pin-edge=${c.pinEdge}\n style=${pinStyle(c) ?? nothing}\n >\n <slot name=${headerSlot(c.colIndex)}></slot>\n ${c.sortable ? html`<span class=\"sort-indicator\" aria-hidden=\"true\"></span>` : nothing}\n ${c.resizable ? resizeHandle(c) : nothing}\n </div>`,\n )}\n </div>\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: ColumnView<T, CellContent>) {\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":";;;;;;;;;;;;;AAoBA,SAAgB,aACd,SACA,UACA,WACA;CACA,OAAO,IAAI;;;;;;eAME,SAAS,MAAM;qBACT,SAAS,YAAY;;QAElC,OACA,UACC,MAAM,EAAE,KACR,MACC,IAAI;mBACK,EAAE,SAAS,mCAAmC,cAAc;;4BAEnD,EAAE,SAAS;wBACf,SAAS,EAAE,MAAM,KAAK,QAAQ;uBAC/B,EAAE,GAAG;4BACA,EAAE,WAAW,EAAE,KAAK,QAAQ;uBACjC,EAAE,OAAO,YAAY,IAAI,GAAG;yBAC1B,EAAE,MAAM;uBACV,EAAE,OAAO,QAAQ;6BACX,EAAE,QAAQ;oBACnB,SAAS,CAAC,KAAK,QAAQ;;yBAElB,WAAW,EAAE,QAAQ,EAAE;cAClC,EAAE,WAAW,IAAI,4DAA4D,QAAQ;cACrF,EAAE,YAAY,aAAa,CAAC,IAAI,QAAQ;iBAEhD,EAAE;;;AAGR;;;;;AAMA,SAAS,aAAgB,GAA+B;CACtD,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"}
|