@kouji-ui/components 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/kouji-ui-components.mjs +49 -3
- package/fesm2022/kouji-ui-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/accordion/accordion.css +68 -0
- package/src/action-sheet/action-sheet.css +79 -0
- package/src/alert/alert.css +133 -0
- package/src/avatar/avatar.css +93 -0
- package/src/badge/badge.css +59 -0
- package/src/breadcrumb/breadcrumb.css +145 -0
- package/src/button/button.css +295 -0
- package/src/button-group/button-group.css +114 -0
- package/src/calendar/calendar.css +129 -0
- package/src/card/card.css +120 -0
- package/src/carousel/carousel.css +181 -0
- package/src/cascade-select/cascade-select.css +120 -0
- package/src/chart/chart.css +26 -0
- package/src/chat/chat-ai.css +352 -0
- package/src/chat/chat.css +231 -0
- package/src/checkbox/checkbox.css +74 -0
- package/src/color-picker/color-picker.css +183 -0
- package/src/combobox/combobox.css +101 -0
- package/src/command-palette/command-palette.css +234 -0
- package/src/confirm-popup/confirm-popup.css +47 -0
- package/src/date-picker/date-picker.css +61 -0
- package/src/date-range-presets/date-range-presets.css +49 -0
- package/src/datetime-picker/datetime-picker.css +84 -0
- package/src/dialog/dialog.css +56 -0
- package/src/direction-toggle/direction-toggle.css +55 -0
- package/src/divider/divider.css +93 -0
- package/src/drawer/drawer.css +130 -0
- package/src/dropdown-menu/dropdown-menu.css +108 -0
- package/src/editor/editor.css +140 -0
- package/src/empty-state/empty-state.css +114 -0
- package/src/field/field.css +148 -0
- package/src/file-upload/file-upload.css +207 -0
- package/src/form/form.css +63 -0
- package/src/input/input.css +70 -0
- package/src/input-group/input-group.css +125 -0
- package/src/input-mask/input-mask.css +39 -0
- package/src/input-otp/input-otp.css +87 -0
- package/src/kbd/kbd.css +67 -0
- package/src/link/link.css +104 -0
- package/src/list/list.css +166 -0
- package/src/menubar/menubar.css +62 -0
- package/src/number-input/number-input.css +115 -0
- package/src/overflow/overflow.css +43 -0
- package/src/overlay/overlay.css +45 -0
- package/src/overlay-badge/overlay-badge.css +118 -0
- package/src/pagination/pagination.css +146 -0
- package/src/password-input/password-input.css +155 -0
- package/src/popover/popover.css +71 -0
- package/src/progress-bar/progress-bar.css +113 -0
- package/src/radio/radio.css +58 -0
- package/src/rich-text/rich-text-editor.css +351 -0
- package/src/select/select.css +91 -0
- package/src/sheet/sheet.css +121 -0
- package/src/skeleton/skeleton.css +111 -0
- package/src/skip-link/skip-link.css +58 -0
- package/src/slider/slider.css +172 -0
- package/src/speed-dial/speed-dial.css +137 -0
- package/src/spinner/spinner.css +161 -0
- package/src/stepper/stepper.css +147 -0
- package/src/table/table-filters/filters.css +53 -0
- package/src/table/table.css +509 -0
- package/src/tabs/tabs.css +162 -0
- package/src/tag/tag.css +183 -0
- package/src/textarea/textarea.css +117 -0
- package/src/time-picker/time-picker.css +126 -0
- package/src/toast/toast.css +115 -0
- package/src/toggle/toggle.css +123 -0
- package/src/tooltip/tooltip.css +61 -0
- package/src/tree-select/tree-select.css +220 -0
- package/types/kouji-ui-components.d.ts +19 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Filter-row layout. All sizing flows from `kjSize="xs"` applied to the inner
|
|
3
|
+
* primitives (kj-input, kj-number-input, kj-select, kj-date-picker) — no more
|
|
4
|
+
* CSS-var override fakery here. This file owns the *layout* of the filter
|
|
5
|
+
* shells (flex direction, gap, child width), nothing else.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.kj-text-filter,
|
|
9
|
+
.kj-number-filter,
|
|
10
|
+
.kj-date-filter,
|
|
11
|
+
.kj-select-filter {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--kj-base-space-xs, 0.25rem);
|
|
15
|
+
width: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* The text filter wraps its <kj-input> in a <label>; let the label own the
|
|
19
|
+
row so the visually-hidden caption stays in flow without inflating width. */
|
|
20
|
+
.kj-text-filter__label,
|
|
21
|
+
.kj-select-filter__label {
|
|
22
|
+
display: flex;
|
|
23
|
+
width: 100%;
|
|
24
|
+
align-items: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Number filter: operator picker keeps natural width, each number input
|
|
28
|
+
takes equal share of the rest. */
|
|
29
|
+
.kj-number-filter__operator {
|
|
30
|
+
flex: 0 0 auto;
|
|
31
|
+
min-width: 4.25rem;
|
|
32
|
+
}
|
|
33
|
+
.kj-number-filter__input {
|
|
34
|
+
flex: 1 1 0;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Date filter: both pickers split the row evenly. */
|
|
39
|
+
.kj-date-filter__label {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex: 1 1 0;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
align-items: center;
|
|
44
|
+
}
|
|
45
|
+
.kj-date-filter .kj-date-picker {
|
|
46
|
+
flex: 1 1 0;
|
|
47
|
+
min-width: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Select filter: trigger fills the cell. */
|
|
51
|
+
.kj-select-filter .kj-select {
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/* Density + variant tokens. All spacing via --kj-base-space-*.
|
|
2
|
+
*
|
|
3
|
+
* Sizing contract: the table is container-driven. If a parent gives `<kj-table>`
|
|
4
|
+
* a fixed height (`height: 400px`, a flex/grid track, etc.) the body fills
|
|
5
|
+
* the remaining space inside the wrapper and the row-scroll happens INSIDE
|
|
6
|
+
* `.kj-table-body`. If no parent constraint is set, the table grows to its
|
|
7
|
+
* natural content height — no internal scroll. `min-height: 0` / `min-width: 0`
|
|
8
|
+
* are the magic flex pieces that let the body actually shrink past content
|
|
9
|
+
* to honour an outer constraint. The same applies for width: the wrapper
|
|
10
|
+
* fills the host's width and the body scrolls horizontally when columns
|
|
11
|
+
* overflow.
|
|
12
|
+
*/
|
|
13
|
+
:host {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
min-height: 0;
|
|
17
|
+
min-width: 0;
|
|
18
|
+
font-family: var(--kj-font-sans);
|
|
19
|
+
font-size: var(--kj-text-sm);
|
|
20
|
+
/* Default row height — predictable + virtualization-friendly. Density
|
|
21
|
+
variants below override; consumers can also write the var directly
|
|
22
|
+
(`<kj-table style="--kj-table-row-height: 2.75rem">…</kj-table>`). */
|
|
23
|
+
--kj-table-row-height: var(--kj-row-h);
|
|
24
|
+
}
|
|
25
|
+
:host([data-density="compact"]) { --kj-table-row-height: 1.75rem; }
|
|
26
|
+
:host([data-density="comfortable"]) { --kj-table-row-height: 2.75rem; }
|
|
27
|
+
|
|
28
|
+
.kj-table-wrapper {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
flex: 1 1 auto;
|
|
32
|
+
min-height: 0;
|
|
33
|
+
min-width: 0;
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.kj-table-body {
|
|
38
|
+
position: relative;
|
|
39
|
+
overflow: auto;
|
|
40
|
+
/* Use block layout (not flex) for the body so the `<table>` keeps its
|
|
41
|
+
natural row-height behaviour. With flex, a child `<table>` stretched
|
|
42
|
+
to fill the body — that made a single row balloon to fill the entire
|
|
43
|
+
viewport when filters narrowed the result set. Block layout lets the
|
|
44
|
+
table size to its content; the body's `overflow: auto` still scrolls
|
|
45
|
+
when there are many rows, and the leftover space appears below the
|
|
46
|
+
last row when there are few. */
|
|
47
|
+
display: block;
|
|
48
|
+
flex: 1 1 auto;
|
|
49
|
+
min-height: 0;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Sticky header — the scroll container is `.kj-table-body`, which holds the
|
|
54
|
+
whole <table> including <thead>. Pin the header rows to the top of that
|
|
55
|
+
scrollport so only the rows scroll. Opaque background required (rows pass
|
|
56
|
+
underneath); z-index above pinned data cells (1) so corner cells layer
|
|
57
|
+
correctly, below the resize indicator (3). */
|
|
58
|
+
.kj-table-body thead th {
|
|
59
|
+
position: sticky;
|
|
60
|
+
top: 0;
|
|
61
|
+
z-index: 2;
|
|
62
|
+
/* Themable header band — falls back to the table surface. */
|
|
63
|
+
background: var(--kj-table-header-bg, var(--kj-bg-surface));
|
|
64
|
+
}
|
|
65
|
+
.kj-table-body thead th[data-pin] {
|
|
66
|
+
z-index: 3;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.kj-table-body > table {
|
|
70
|
+
/* Table never flex-grows; row heights stay content-driven (or row-height
|
|
71
|
+
CSS var-driven if the consumer sets `--kj-table-row-height`). */
|
|
72
|
+
min-width: 100%;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:host th,
|
|
76
|
+
:host td {
|
|
77
|
+
height: var(--kj-table-row-height);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:host table {
|
|
81
|
+
width: 100%;
|
|
82
|
+
border-collapse: collapse;
|
|
83
|
+
background: var(--kj-bg-surface);
|
|
84
|
+
color: var(--kj-fg-default);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:host th,
|
|
88
|
+
:host td {
|
|
89
|
+
text-align: left;
|
|
90
|
+
padding: var(--kj-base-space-sm) var(--kj-base-space-md);
|
|
91
|
+
border-bottom: 1px solid var(--kj-border-default);
|
|
92
|
+
position: relative;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:host th {
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
background: var(--kj-bg-elevated);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── Sort indicator ─────────────────────────────────────────────
|
|
101
|
+
The wrapper template renders a `<i kjIcon>` after the header label
|
|
102
|
+
when the column is sortable. CSS here just owns layout + tint:
|
|
103
|
+
- default → muted (unsorted/sortable)
|
|
104
|
+
- data-sort=… → brand primary
|
|
105
|
+
The icon sits inline so it survives the resize handle on the right
|
|
106
|
+
edge of the cell. */
|
|
107
|
+
:host th[aria-sort] {
|
|
108
|
+
user-select: none;
|
|
109
|
+
}
|
|
110
|
+
.kj-table-sort-indicator {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
margin-inline-start: var(--kj-base-space-xs, 0.25rem);
|
|
114
|
+
color: var(--kj-fg-subtle, var(--kj-fg-muted));
|
|
115
|
+
transition: color var(--kj-transition);
|
|
116
|
+
vertical-align: middle;
|
|
117
|
+
}
|
|
118
|
+
:host th[data-sort="asc"] .kj-table-sort-indicator,
|
|
119
|
+
:host th[data-sort="desc"] .kj-table-sort-indicator {
|
|
120
|
+
color: var(--kj-fg-primary);
|
|
121
|
+
}
|
|
122
|
+
:host th[aria-sort]:hover .kj-table-sort-indicator,
|
|
123
|
+
:host th[aria-sort]:focus-visible .kj-table-sort-indicator {
|
|
124
|
+
color: var(--kj-fg-default);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:host([data-density="compact"]) th,
|
|
128
|
+
:host([data-density="compact"]) td {
|
|
129
|
+
padding: var(--kj-base-space-xs) var(--kj-base-space-sm);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:host([data-density="comfortable"]) th,
|
|
133
|
+
:host([data-density="comfortable"]) td {
|
|
134
|
+
padding: var(--kj-base-space-md) var(--kj-base-space-lg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:host([data-variant="striped"]) tbody tr:nth-child(even) td {
|
|
138
|
+
background: var(--kj-bg-elevated);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
:host([data-variant="clean"]) th,
|
|
142
|
+
:host([data-variant="clean"]) td {
|
|
143
|
+
border-bottom-color: transparent;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
:host td:focus-visible,
|
|
147
|
+
:host th:focus-visible {
|
|
148
|
+
outline: 2px solid var(--kj-border-focus);
|
|
149
|
+
outline-offset: -2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Inline cell editors must NOT push the column wider than its existing
|
|
153
|
+
content. A bare `<input>` (and the styled select trigger) has an
|
|
154
|
+
intrinsic min-content width — ~20 chars from `size` for inputs, the
|
|
155
|
+
selected label for triggers — that table auto-layout uses to size the
|
|
156
|
+
column.
|
|
157
|
+
|
|
158
|
+
Trick: render an invisible "ghost" of the cell's value so the cell
|
|
159
|
+
keeps its natural content width, then lift every editor host out of
|
|
160
|
+
the in-flow stacking context with `position: absolute`. Editors use
|
|
161
|
+
`display: contents` by default — we override that while editing so
|
|
162
|
+
they actually get a box that can be positioned. */
|
|
163
|
+
:host td.kj-table-cell--editing {
|
|
164
|
+
position: relative;
|
|
165
|
+
}
|
|
166
|
+
:host td .kj-table-cell-ghost {
|
|
167
|
+
visibility: hidden;
|
|
168
|
+
white-space: pre;
|
|
169
|
+
display: inline-block;
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
}
|
|
172
|
+
/* `::ng-deep` is required because the editor components are created
|
|
173
|
+
dynamically via `ViewContainerRef.createComponent` from inside the
|
|
174
|
+
`kjCellEditorOutlet` directive. Dynamic elements don't inherit the
|
|
175
|
+
parent template's emulated-encapsulation attributes, so a scoped
|
|
176
|
+
selector wouldn't match. */
|
|
177
|
+
/* `!important` is needed to override the editor / input components'
|
|
178
|
+
inline `style="display: contents"` host bindings (those win over
|
|
179
|
+
stylesheets). `::ng-deep` reaches the dynamically-created editor
|
|
180
|
+
components, which don't inherit the parent template's emulated
|
|
181
|
+
encapsulation attributes. */
|
|
182
|
+
:host td.kj-table-cell--editing ::ng-deep kj-text-editor,
|
|
183
|
+
:host td.kj-table-cell--editing ::ng-deep kj-number-editor,
|
|
184
|
+
:host td.kj-table-cell--editing ::ng-deep kj-select-editor,
|
|
185
|
+
:host td.kj-table-cell--editing ::ng-deep kj-date-editor,
|
|
186
|
+
:host td.kj-table-cell--editing ::ng-deep kj-boolean-editor {
|
|
187
|
+
position: absolute !important;
|
|
188
|
+
inset: 0 !important;
|
|
189
|
+
display: flex !important;
|
|
190
|
+
align-items: center !important;
|
|
191
|
+
padding: inherit !important;
|
|
192
|
+
background: var(--kj-bg-surface);
|
|
193
|
+
z-index: 1;
|
|
194
|
+
}
|
|
195
|
+
:host td.kj-table-cell--editing ::ng-deep kj-input,
|
|
196
|
+
:host td.kj-table-cell--editing ::ng-deep kj-number-input,
|
|
197
|
+
:host td.kj-table-cell--editing ::ng-deep kj-select {
|
|
198
|
+
display: flex !important;
|
|
199
|
+
width: 100% !important;
|
|
200
|
+
min-width: 0 !important;
|
|
201
|
+
box-sizing: border-box;
|
|
202
|
+
}
|
|
203
|
+
:host td.kj-table-cell--editing ::ng-deep input,
|
|
204
|
+
:host td.kj-table-cell--editing ::ng-deep .kj-select-trigger {
|
|
205
|
+
width: 100% !important;
|
|
206
|
+
min-width: 0 !important;
|
|
207
|
+
box-sizing: border-box;
|
|
208
|
+
}
|
|
209
|
+
/* The anchor span itself contributes nothing to layout — the directive
|
|
210
|
+
creates the editor as its NEXT SIBLING in the cell, not as a child. */
|
|
211
|
+
:host td .kj-table-editor-anchor {
|
|
212
|
+
display: none;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Selected rows get a brand-accent tint + a left border stripe so a
|
|
216
|
+
keyboard user can tell which row is selected at a glance, even when
|
|
217
|
+
the row is not focused. */
|
|
218
|
+
:host tr[data-selected] td {
|
|
219
|
+
background: color-mix(in oklab, var(--kj-bg-primary, var(--kj-bg-accent, currentColor)) 12%, var(--kj-bg-surface));
|
|
220
|
+
}
|
|
221
|
+
:host tr[data-selected] td:first-child {
|
|
222
|
+
box-shadow: inset 3px 0 0 0 var(--kj-bg-primary, var(--kj-fg-primary));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ── Pinned columns ─────────────────────────────────────────────
|
|
226
|
+
Sticky positioning + a visible boundary (border + soft shadow) so the
|
|
227
|
+
pinned edge reads as a separate region from the scrolling middle. The
|
|
228
|
+
:host([data-variant]) toggles don't affect pin borders — they're a
|
|
229
|
+
structural cue, not a visual variant. */
|
|
230
|
+
:host td[data-pin="left"],
|
|
231
|
+
:host th[data-pin="left"] {
|
|
232
|
+
position: sticky;
|
|
233
|
+
left: 0;
|
|
234
|
+
z-index: 1;
|
|
235
|
+
background: var(--kj-bg-surface);
|
|
236
|
+
}
|
|
237
|
+
:host td[data-pin="right"],
|
|
238
|
+
:host th[data-pin="right"] {
|
|
239
|
+
position: sticky;
|
|
240
|
+
right: 0;
|
|
241
|
+
z-index: 1;
|
|
242
|
+
background: var(--kj-bg-surface);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Boundary indicator on the inside edge of each pinned region.
|
|
246
|
+
|
|
247
|
+
Last LEFT-pinned cell: a `[data-pin="left"]` whose next sibling is NOT
|
|
248
|
+
also left-pinned — uses `:not(:has(+ ...))`.
|
|
249
|
+
|
|
250
|
+
First RIGHT-pinned cell: a `[data-pin="right"]` whose previous sibling
|
|
251
|
+
is NOT right-pinned — uses the `:not([..]) + [data-pin="right"]` pattern.
|
|
252
|
+
|
|
253
|
+
Both are purely declarative; no JS bookkeeping. */
|
|
254
|
+
:host td[data-pin="left"]:not(:has(+ td[data-pin="left"])),
|
|
255
|
+
:host th[data-pin="left"]:not(:has(+ th[data-pin="left"])) {
|
|
256
|
+
border-right: 1px solid var(--kj-border-strong, var(--kj-border-default));
|
|
257
|
+
box-shadow: 4px 0 6px -4px rgba(0, 0, 0, 0.25);
|
|
258
|
+
}
|
|
259
|
+
:host tr > :not([data-pin="right"]) + td[data-pin="right"],
|
|
260
|
+
:host tr > :not([data-pin="right"]) + th[data-pin="right"] {
|
|
261
|
+
border-left: 1px solid var(--kj-border-strong, var(--kj-border-default));
|
|
262
|
+
box-shadow: -4px 0 6px -4px rgba(0, 0, 0, 0.25);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Striped variant: pinned cells still get the alternate background so the
|
|
266
|
+
sticky cell doesn't visually float over a coloured row beneath it. */
|
|
267
|
+
:host([data-variant="striped"]) tbody tr:nth-child(even) td[data-pin] {
|
|
268
|
+
background: var(--kj-bg-elevated);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* ── Resize handle ──────────────────────────────────────────────
|
|
272
|
+
Always-visible thin track on the right edge of each `<th>`; widens
|
|
273
|
+
and brightens to the focus color on hover/focus so users can tell
|
|
274
|
+
it's draggable. Hidden when no resize is enabled (the host doesn't
|
|
275
|
+
render the element). */
|
|
276
|
+
.kj-table-resize-handle {
|
|
277
|
+
position: absolute;
|
|
278
|
+
top: 25%;
|
|
279
|
+
right: 0;
|
|
280
|
+
width: 6px;
|
|
281
|
+
height: 50%;
|
|
282
|
+
cursor: col-resize;
|
|
283
|
+
user-select: none;
|
|
284
|
+
touch-action: none;
|
|
285
|
+
background: transparent;
|
|
286
|
+
border-right: 2px solid var(--kj-border-strong, var(--kj-border-default));
|
|
287
|
+
transition: border-color var(--kj-transition), background var(--kj-transition), width var(--kj-transition);
|
|
288
|
+
}
|
|
289
|
+
.kj-table-resize-handle:hover,
|
|
290
|
+
.kj-table-resize-handle:focus-visible {
|
|
291
|
+
border-right-color: var(--kj-border-focus);
|
|
292
|
+
background: color-mix(in srgb, var(--kj-border-focus) 20%, transparent);
|
|
293
|
+
width: 8px;
|
|
294
|
+
outline: none;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ── Resize preview guide ───────────────────────────────────────
|
|
298
|
+
While a column is being resized (TanStack `onEnd` mode), the
|
|
299
|
+
wrapper sets `data-resizing` on the header and pipes the live
|
|
300
|
+
cursor delta into `--kj-resize-delta`. The handle becomes a
|
|
301
|
+
tall vertical line anchored to the column's current right edge
|
|
302
|
+
and translated by the delta — it visually tracks where the new
|
|
303
|
+
edge will land before the column actually reflows on mouseup.
|
|
304
|
+
|
|
305
|
+
`top: 0; height: 9999px` overflows the `<th>`; the surrounding
|
|
306
|
+
`.kj-table-body` has `overflow: auto`, so the visible portion
|
|
307
|
+
is naturally clipped to the table's height. */
|
|
308
|
+
:host th[data-resizing] .kj-table-resize-handle {
|
|
309
|
+
top: 0;
|
|
310
|
+
height: 9999px;
|
|
311
|
+
width: 0;
|
|
312
|
+
background: transparent;
|
|
313
|
+
border-right: 2px solid var(--kj-border-focus, var(--kj-bg-primary));
|
|
314
|
+
transform: translateX(var(--kj-resize-delta, 0px));
|
|
315
|
+
pointer-events: none;
|
|
316
|
+
z-index: 3;
|
|
317
|
+
transition: none;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ── Selection column ───────────────────────────────────────────
|
|
321
|
+
Leading checkbox column auto-rendered when `kjSelectionMode` is
|
|
322
|
+
`'single'` or `'multi'`. Narrow fixed-width column so the row
|
|
323
|
+
data starts at a predictable horizontal position. */
|
|
324
|
+
:host th.kj-table-select-cell,
|
|
325
|
+
:host td.kj-table-select-cell {
|
|
326
|
+
width: 2.5rem;
|
|
327
|
+
padding-inline: var(--kj-base-space-sm);
|
|
328
|
+
text-align: center;
|
|
329
|
+
vertical-align: middle;
|
|
330
|
+
}
|
|
331
|
+
.kj-table-select-cell kj-checkbox {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
justify-content: center;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ── Filter row beneath the header row ──────────────────────────
|
|
338
|
+
Each cell hosts a styled filter input (text / number / date / select)
|
|
339
|
+
mounted by the wrapper. We keep the row visually distinct from headers
|
|
340
|
+
with a lighter background + thinner padding, and let the input fill
|
|
341
|
+
the cell width so filters feel column-anchored. */
|
|
342
|
+
.kj-table-filter-row > th {
|
|
343
|
+
font-weight: 400;
|
|
344
|
+
background: var(--kj-bg-surface);
|
|
345
|
+
border-top: 1px solid var(--kj-border-muted, var(--kj-border-default));
|
|
346
|
+
}
|
|
347
|
+
.kj-table-filter-cell {
|
|
348
|
+
padding-top: var(--kj-base-space-xs);
|
|
349
|
+
padding-bottom: var(--kj-base-space-xs);
|
|
350
|
+
vertical-align: middle;
|
|
351
|
+
}
|
|
352
|
+
.kj-table-filter-cell .kj-table-filter-anchor + *,
|
|
353
|
+
.kj-table-filter-cell kj-input,
|
|
354
|
+
.kj-table-filter-cell kj-number-input,
|
|
355
|
+
.kj-table-filter-cell kj-date-picker,
|
|
356
|
+
.kj-table-filter-cell kj-select {
|
|
357
|
+
width: 100%;
|
|
358
|
+
}
|
|
359
|
+
.kj-table-filter-anchor {
|
|
360
|
+
display: block;
|
|
361
|
+
width: 100%;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Inline editors get the same compact xs treatment as filter cells. The
|
|
365
|
+
filter inputs already bind `kjSize="xs"` themselves; this rule covers the
|
|
366
|
+
editor anchors that mount unsized primitives at runtime. */
|
|
367
|
+
:host .kj-table-editor-anchor .kj-input,
|
|
368
|
+
:host .kj-table-editor-anchor .kj-select {
|
|
369
|
+
--kj-input-height: 1.75rem;
|
|
370
|
+
--kj-input-font-size: var(--kj-text-xs);
|
|
371
|
+
--kj-input-padding-x: var(--kj-base-space-sm);
|
|
372
|
+
--kj-input-padding-y: 0;
|
|
373
|
+
--kj-input-radius: var(--kj-radius-selector);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Empty / error / loading panes. */
|
|
377
|
+
.kj-table-empty,
|
|
378
|
+
.kj-table-error,
|
|
379
|
+
.kj-table-loading {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
justify-content: center;
|
|
383
|
+
padding: var(--kj-base-space-lg);
|
|
384
|
+
color: var(--kj-fg-muted);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ── Empty / error pane sizing ──────────────────────────────────
|
|
388
|
+
These panes are siblings *after* `.kj-table-body`, so with the
|
|
389
|
+
body's default `flex: 1 1 auto` a height-constrained table gave
|
|
390
|
+
the whole leftover column to the (row-less) body and left the
|
|
391
|
+
message stranded at the very bottom, reading as a footer rather
|
|
392
|
+
than a state.
|
|
393
|
+
|
|
394
|
+
When either state is active the body yields: it shrinks to its
|
|
395
|
+
content (the sticky header stays visible, keeping column context)
|
|
396
|
+
and the pane takes the residual space, so its existing
|
|
397
|
+
`align-items/justify-content: center` finally has a box to center
|
|
398
|
+
inside. Unconstrained tables are unaffected — there is no residual
|
|
399
|
+
space to hand over, and the pane keeps its natural padded height.
|
|
400
|
+
|
|
401
|
+
Loading is excluded: it is already an `inset: 0` overlay. */
|
|
402
|
+
:host([data-empty]) .kj-table-body,
|
|
403
|
+
:host([data-error]) .kj-table-body {
|
|
404
|
+
flex: 0 1 auto;
|
|
405
|
+
}
|
|
406
|
+
.kj-table-empty,
|
|
407
|
+
.kj-table-error {
|
|
408
|
+
flex: 1 1 auto;
|
|
409
|
+
min-height: 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* ── Loading state ──────────────────────────────────────────────
|
|
413
|
+
Two modes:
|
|
414
|
+
- Initial load (`data-has-rows="false"`): full-pane overlay with
|
|
415
|
+
a centered "Loading…" message. The user has nothing to look at
|
|
416
|
+
yet so we cover the empty body.
|
|
417
|
+
- Subsequent reloads (`data-has-rows="true"`): a slim 2px brand
|
|
418
|
+
stripe at the top of the body. The stale rows stay readable
|
|
419
|
+
and pointer events keep working — the user can sort/scroll/
|
|
420
|
+
paginate again while the refresh is in flight. */
|
|
421
|
+
.kj-table-loading {
|
|
422
|
+
position: absolute;
|
|
423
|
+
inset: 0;
|
|
424
|
+
background: color-mix(in srgb, var(--kj-bg-surface) 70%, transparent);
|
|
425
|
+
pointer-events: none;
|
|
426
|
+
}
|
|
427
|
+
.kj-table-loading[data-has-rows] {
|
|
428
|
+
inset: 0 0 auto 0;
|
|
429
|
+
height: 2px;
|
|
430
|
+
background: var(--kj-bg-primary);
|
|
431
|
+
padding: 0;
|
|
432
|
+
animation: kj-table-loading-pulse 1.4s ease-in-out infinite;
|
|
433
|
+
}
|
|
434
|
+
.kj-table-loading[data-has-rows] .kj-table-loading-fallback,
|
|
435
|
+
.kj-table-loading[data-has-rows] ::ng-deep [kjLoading] {
|
|
436
|
+
display: none;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
@keyframes kj-table-loading-pulse {
|
|
440
|
+
0%, 100% { opacity: 0.55; }
|
|
441
|
+
50% { opacity: 1; }
|
|
442
|
+
}
|
|
443
|
+
.kj-table-error {
|
|
444
|
+
color: var(--kj-fg-error, var(--kj-fg-default));
|
|
445
|
+
}
|
|
446
|
+
.kj-table-loading-fallback {
|
|
447
|
+
font-size: var(--kj-text-sm);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Row expansion. */
|
|
451
|
+
.kj-table-expansion-row > td {
|
|
452
|
+
background: var(--kj-bg-elevated);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* ── Pinned-row tbody segments ──────────────────────────────────
|
|
456
|
+
Use the brand accent so pinned rows visually pop out of the center
|
|
457
|
+
body; sticky positioning lets them stay anchored at the top/bottom
|
|
458
|
+
even when the body scrolls. */
|
|
459
|
+
.kj-table-tbody-pinned[data-pin="top"] > tr > td {
|
|
460
|
+
background: var(--kj-bg-elevated);
|
|
461
|
+
border-bottom: 2px solid var(--kj-border-primary, var(--kj-border-strong, var(--kj-border-default)));
|
|
462
|
+
box-shadow: 0 4px 6px -4px rgba(0, 0, 0, 0.25);
|
|
463
|
+
position: sticky;
|
|
464
|
+
top: 0;
|
|
465
|
+
z-index: 2;
|
|
466
|
+
}
|
|
467
|
+
.kj-table-tbody-pinned[data-pin="bottom"] > tr > td {
|
|
468
|
+
background: var(--kj-bg-elevated);
|
|
469
|
+
border-top: 2px solid var(--kj-border-primary, var(--kj-border-strong, var(--kj-border-default)));
|
|
470
|
+
box-shadow: 0 -4px 6px -4px rgba(0, 0, 0, 0.25);
|
|
471
|
+
position: sticky;
|
|
472
|
+
bottom: 0;
|
|
473
|
+
z-index: 2;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* ── Grouping row ───────────────────────────────────────────────
|
|
477
|
+
Rows where a column is grouped show a toggle-able header chip on the
|
|
478
|
+
grouped cell + count of subRows. */
|
|
479
|
+
.kj-table-group-toggle {
|
|
480
|
+
display: inline-flex;
|
|
481
|
+
align-items: center;
|
|
482
|
+
gap: var(--kj-base-space-xs, 0.25rem);
|
|
483
|
+
background: transparent;
|
|
484
|
+
border: 0;
|
|
485
|
+
padding: 0;
|
|
486
|
+
font: inherit;
|
|
487
|
+
color: inherit;
|
|
488
|
+
cursor: pointer;
|
|
489
|
+
}
|
|
490
|
+
.kj-table-group-toggle__chevron {
|
|
491
|
+
display: inline-block;
|
|
492
|
+
width: 0.75rem;
|
|
493
|
+
font-family: var(--kj-font-mono);
|
|
494
|
+
color: var(--kj-fg-muted);
|
|
495
|
+
transition: transform var(--kj-transition);
|
|
496
|
+
}
|
|
497
|
+
.kj-table-group-toggle[aria-expanded="true"] .kj-table-group-toggle__chevron {
|
|
498
|
+
transform: rotate(90deg);
|
|
499
|
+
}
|
|
500
|
+
.kj-table-group-toggle__count {
|
|
501
|
+
margin-inline-start: var(--kj-base-space-xs, 0.25rem);
|
|
502
|
+
font-size: 0.75em;
|
|
503
|
+
color: var(--kj-fg-muted);
|
|
504
|
+
font-variant-numeric: tabular-nums;
|
|
505
|
+
}
|
|
506
|
+
:host tbody tr[data-row-grouped] td {
|
|
507
|
+
background: var(--kj-bg-elevated);
|
|
508
|
+
font-weight: 600;
|
|
509
|
+
}
|