@lavalogic/scoria 0.37.19 → 0.37.21
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/Components/Table/Body/QuickSearchCell.svelte +3 -2
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +1 -7
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +13 -0
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +30 -6
- package/dist/Components/Table/ColumnFactory.svelte.js +1 -0
- package/dist/Components/Table/Headers/TableHead.svelte +5 -1
- package/dist/Components/Table/Misc/TableFooter.svelte +7 -2
- package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.d.ts +11 -0
- package/dist/Components/Table/Types/Columns/Definitions/ColumnDef.svelte.js +23 -0
- package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.d.ts +6 -0
- package/dist/Components/Table/Types/Columns/Definitions/Display/BubbleDef.svelte.js +3 -0
- package/dist/Components/Table/Types/Public/ColumnOptions.d.ts +6 -0
- package/dist/Components/Table/createTable.svelte.js +38 -0
- package/dist/scss/_mixins.scss +14 -11
- package/package.json +1 -1
|
@@ -724,10 +724,11 @@
|
|
|
724
724
|
div.input-cell {
|
|
725
725
|
background-color: #f4f7f9;
|
|
726
726
|
height: var(--height);
|
|
727
|
-
overflow:
|
|
727
|
+
overflow-x: clip;
|
|
728
|
+
overflow-y: visible;
|
|
728
729
|
padding-left: 0;
|
|
729
730
|
padding-right: 0;
|
|
730
|
-
border-right:
|
|
731
|
+
border-right: none;
|
|
731
732
|
box-sizing: border-box;
|
|
732
733
|
}
|
|
733
734
|
div.input-cell div.stick-right {
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import Action from '../../../Action.svelte';
|
|
8
8
|
import Checkbox from '../../../Checkbox.svelte';
|
|
9
9
|
import ThreeStateRadio from '../../../ThreeStateRadio.svelte';
|
|
10
|
-
import { ColumnType } from '../../Types/Columns/ColumnType.js';
|
|
11
10
|
import { TableContext } from '../../Types/Context/TableContext.svelte.js';
|
|
12
11
|
import type { Primitive } from '../../Types/Context/TableInitOptions.js';
|
|
13
12
|
import { Size } from '../../../../Types/Internal/Size.js';
|
|
@@ -18,7 +17,6 @@
|
|
|
18
17
|
|
|
19
18
|
const { columnDef }: ColumnListRowProps<T> = $props();
|
|
20
19
|
|
|
21
|
-
const isExpandColumn = $derived(columnDef?.columnType === ColumnType.Expand);
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* Position of this column inside the sorted column list, used to
|
|
@@ -412,13 +410,9 @@
|
|
|
412
410
|
Expand columns can never be pinned, so the group goes
|
|
413
411
|
fully disabled for them.
|
|
414
412
|
-->
|
|
415
|
-
<div
|
|
416
|
-
class="pin-selector"
|
|
417
|
-
class:disabled={isExpandColumn}
|
|
418
|
-
>
|
|
413
|
+
<div class="pin-selector">
|
|
419
414
|
<ThreeStateRadio
|
|
420
415
|
value={pinSide}
|
|
421
|
-
disabled={isExpandColumn}
|
|
422
416
|
noLabel="Pin to left"
|
|
423
417
|
neutralLabel="Unpinned"
|
|
424
418
|
yesLabel="Pin to right"
|
|
@@ -115,11 +115,24 @@
|
|
|
115
115
|
tableContext.endFocus(focusDetails.coordinates);
|
|
116
116
|
}}
|
|
117
117
|
>
|
|
118
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
118
119
|
<div
|
|
119
120
|
class="slot-wrapper"
|
|
120
121
|
class:multifocus={focusDetails.isMultiFocused}
|
|
121
122
|
class:multifocus-start={focusDetails.isMultiFocusStart}
|
|
122
123
|
bind:this={cellRef}
|
|
124
|
+
onmousedown={(e) => {
|
|
125
|
+
// Stop the browser's default mousedown -> focus side effect
|
|
126
|
+
// on the inner Action button. The default focus call
|
|
127
|
+
// implicitly calls `scrollIntoView` against the button (which
|
|
128
|
+
// sits inside a sticky right-pinned cell whose layout position
|
|
129
|
+
// is at the far right end of the row), causing the table to
|
|
130
|
+
// jump horizontally on every click of the ellipsis menu. We
|
|
131
|
+
// still want focus / activation behaviour, so the click handler
|
|
132
|
+
// on the Action proper handles the popup toggle and the outer
|
|
133
|
+
// `.focusable` cell handler manages the focus state.
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
}}
|
|
123
136
|
>
|
|
124
137
|
<!-- TODO {isMultiFocusStart} -->
|
|
125
138
|
<Action
|
|
@@ -83,11 +83,17 @@
|
|
|
83
83
|
>
|
|
84
84
|
<div class="bubble-parent">
|
|
85
85
|
{#await values then values}
|
|
86
|
-
<!--
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
<!-- `maxBubbles` (default 3) caps how many pills render inline;
|
|
87
|
+
anything beyond gets a single `"+N more"` overflow chip with
|
|
88
|
+
the hidden labels as a hover tooltip. Key combines index and
|
|
89
|
+
value because duplicate labels would otherwise collapse and
|
|
90
|
+
reordering with identical labels would force unrelated
|
|
91
|
+
bubbles to swap. -->
|
|
92
|
+
{@const maxBubbles = (cell.column.columnDef as BubbleDef<T>).maxBubbles ?? 3}
|
|
93
|
+
{@const visibleBubbles = values.slice(0, maxBubbles)}
|
|
94
|
+
{@const overflowCount = Math.max(0, values.length - maxBubbles)}
|
|
95
|
+
{@const overflowTitle = values.slice(maxBubbles).join(', ')}
|
|
96
|
+
{#each visibleBubbles as value, index (`${index}:${value}`)}
|
|
91
97
|
{@const colourPromise = (cell.column.columnDef as BubbleDef<T>).getColour?.(
|
|
92
98
|
cell.row.original,
|
|
93
99
|
cell.row.originalIndex,
|
|
@@ -105,6 +111,12 @@
|
|
|
105
111
|
{/await}
|
|
106
112
|
{/if}
|
|
107
113
|
{/each}
|
|
114
|
+
{#if overflowCount > 0}
|
|
115
|
+
<span
|
|
116
|
+
class="bubble-overflow"
|
|
117
|
+
title={overflowTitle}
|
|
118
|
+
>+{overflowCount} more</span>
|
|
119
|
+
{/if}
|
|
108
120
|
{/await}
|
|
109
121
|
</div>
|
|
110
122
|
</div>
|
|
@@ -186,7 +198,7 @@
|
|
|
186
198
|
|
|
187
199
|
div.bubble-parent {
|
|
188
200
|
display: flex;
|
|
189
|
-
flex-flow: row
|
|
201
|
+
flex-flow: row nowrap;
|
|
190
202
|
max-width: 100%;
|
|
191
203
|
height: 100%;
|
|
192
204
|
overflow-x: clip;
|
|
@@ -194,4 +206,16 @@ div.bubble-parent {
|
|
|
194
206
|
align-items: center;
|
|
195
207
|
gap: 0.75rem;
|
|
196
208
|
--line-height: 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.bubble-overflow {
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
padding: 0.125rem 0.5rem;
|
|
214
|
+
border-radius: 9999px;
|
|
215
|
+
background-color: #e9ecf1;
|
|
216
|
+
color: #647d8e;
|
|
217
|
+
font-size: 0.75rem;
|
|
218
|
+
font-weight: 500;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
cursor: default;
|
|
197
221
|
}</style>
|
|
@@ -370,6 +370,7 @@ export function createColumnFactory() {
|
|
|
370
370
|
id,
|
|
371
371
|
...commonProps(opts, 'bubble'),
|
|
372
372
|
accessorFn: opts.value,
|
|
373
|
+
maxBubbles: opts.maxBubbles,
|
|
373
374
|
getColour: opts.colour,
|
|
374
375
|
// Default to a string-typed filter so bubble columns appear in
|
|
375
376
|
// the quick-filter row and advanced filter panel by default
|
|
@@ -360,7 +360,11 @@
|
|
|
360
360
|
handler(e);
|
|
361
361
|
}}
|
|
362
362
|
ondblclick={() => {
|
|
363
|
-
|
|
363
|
+
// Reset the column back to its factory-default width
|
|
364
|
+
// (clears the user-resized flag too, so the grid
|
|
365
|
+
// track returns to its original `minmax(W, 1fr)` /
|
|
366
|
+
// fixed-`${W}px` branch as configured).
|
|
367
|
+
header.column.columnDef.resetWidth();
|
|
364
368
|
}}
|
|
365
369
|
class="resizer right"
|
|
366
370
|
class:isResizing={header.column.getIsResizing()}
|
|
@@ -349,12 +349,17 @@
|
|
|
349
349
|
--input-width: 4rem;
|
|
350
350
|
--input-border: none;
|
|
351
351
|
}
|
|
352
|
-
.spread_row .
|
|
352
|
+
.spread_row .records-per-page {
|
|
353
|
+
--input-width: 4.5rem;
|
|
354
|
+
padding: 0 0.5rem;
|
|
355
|
+
gap: 0.375rem;
|
|
356
|
+
}
|
|
357
|
+
.spread_row .records-per-page.nopad {
|
|
353
358
|
padding: 0;
|
|
354
359
|
--button-padding-horizontal: 1rem;
|
|
355
360
|
--button-padding-vertical: 0.56rem;
|
|
356
361
|
}
|
|
357
|
-
.spread_row .
|
|
362
|
+
.spread_row .records-per-page.nopad.page-arrow {
|
|
358
363
|
--button-padding-horizontal: 0.6rem;
|
|
359
364
|
--button-padding-vertical: 0.42rem;
|
|
360
365
|
}
|
|
@@ -162,6 +162,17 @@ export declare abstract class ColumnDef<T extends object, in FilterFnType = neve
|
|
|
162
162
|
* track, false = `minmax(width, 1fr)` flex track. */
|
|
163
163
|
get widthIsFixed(): boolean;
|
|
164
164
|
set widthIsFixed(v: boolean);
|
|
165
|
+
private readonly _originalWidth;
|
|
166
|
+
private readonly _originalMaxWidth;
|
|
167
|
+
private readonly _originalWidthIsFixed;
|
|
168
|
+
/**
|
|
169
|
+
* Reset this column's width-related state back to the values the
|
|
170
|
+
* factory created it with. Clears `userResized` so the grid track
|
|
171
|
+
* goes back to its flexible / fixed branch as originally configured.
|
|
172
|
+
* Used by the header resize handle's double-click to give consumers
|
|
173
|
+
* a single-action "restore default size" affordance.
|
|
174
|
+
*/
|
|
175
|
+
resetWidth(): void;
|
|
165
176
|
private _isResizing;
|
|
166
177
|
/** Whether the resize handle is currently active. */
|
|
167
178
|
get isResizing(): boolean;
|
|
@@ -36,9 +36,12 @@ export class ColumnDef {
|
|
|
36
36
|
this.accessorFn = $derived(props.accessorFn);
|
|
37
37
|
this._minSize = $state(props.minSize ?? 48);
|
|
38
38
|
this._width = $state(props.width ?? props.minSize ?? 48);
|
|
39
|
+
this._originalWidth = props.width ?? props.minSize ?? 48;
|
|
39
40
|
this._maxWidth = $state(props.maxWidth);
|
|
41
|
+
this._originalMaxWidth = props.maxWidth;
|
|
40
42
|
this._wrapValue = $state(props.wrapValue ?? false);
|
|
41
43
|
this._widthIsFixed = $state(props.widthIsFixed ?? false);
|
|
44
|
+
this._originalWidthIsFixed = props.widthIsFixed ?? false;
|
|
42
45
|
this.isEditable = $derived(props.isEditable ?? (() => Promise.resolve(false)));
|
|
43
46
|
this.resizable = $derived(props.resizable ?? true);
|
|
44
47
|
this.header = $derived(props.header);
|
|
@@ -119,6 +122,26 @@ export class ColumnDef {
|
|
|
119
122
|
set widthIsFixed(v) {
|
|
120
123
|
this._widthIsFixed = v;
|
|
121
124
|
}
|
|
125
|
+
// Snapshots of width / maxWidth / widthIsFixed taken at construction
|
|
126
|
+
// time. The user can revert any in-place mutation (drag-resize,
|
|
127
|
+
// programmatic write) back to these values via `resetWidth()`. They
|
|
128
|
+
// are plain fields (not `$state`) because they never change.
|
|
129
|
+
_originalWidth;
|
|
130
|
+
_originalMaxWidth;
|
|
131
|
+
_originalWidthIsFixed;
|
|
132
|
+
/**
|
|
133
|
+
* Reset this column's width-related state back to the values the
|
|
134
|
+
* factory created it with. Clears `userResized` so the grid track
|
|
135
|
+
* goes back to its flexible / fixed branch as originally configured.
|
|
136
|
+
* Used by the header resize handle's double-click to give consumers
|
|
137
|
+
* a single-action "restore default size" affordance.
|
|
138
|
+
*/
|
|
139
|
+
resetWidth() {
|
|
140
|
+
this._width = this._originalWidth;
|
|
141
|
+
this._maxWidth = this._originalMaxWidth;
|
|
142
|
+
this._widthIsFixed = this._originalWidthIsFixed;
|
|
143
|
+
this._userResized = false;
|
|
144
|
+
}
|
|
122
145
|
_isResizing = $state(false);
|
|
123
146
|
/** Whether the resize handle is currently active. */
|
|
124
147
|
get isResizing() {
|
|
@@ -14,6 +14,10 @@ import { AbstractDisplayDef, type AbstractDisplayDefProps } from './AbstractDisp
|
|
|
14
14
|
export interface BubbleDefProps<T extends object, in FilterValueType, out FilterOptionsType> extends AbstractDisplayDefProps<T, FilterValueType, FilterOptionsType> {
|
|
15
15
|
/** Per-bubble colour mapper. Invoked per bubble value per render. */
|
|
16
16
|
getColour?: (item: T, index: number, value: string) => BubbleColour | Promise<BubbleColour | null | undefined>;
|
|
17
|
+
/** Maximum number of pills shown per cell before the overflow chip
|
|
18
|
+
* kicks in. Pills beyond this cap are hidden and a single
|
|
19
|
+
* `"+N more"` chip is rendered in their place. Defaults to 3. */
|
|
20
|
+
maxBubbles?: number;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
23
|
* `BubbleDef<T, FilterValueType, FilterOptionsType>` renders the cell
|
|
@@ -29,4 +33,6 @@ export declare class BubbleDef<T extends object, in FilterValueType = StringFilt
|
|
|
29
33
|
set filterFn(v: FilterFn<T, FilterValueType> | undefined);
|
|
30
34
|
/** Per-bubble colour mapper. */
|
|
31
35
|
readonly getColour: ((item: T, index: number, value: string) => BubbleColour | Promise<BubbleColour | null | undefined>) | undefined;
|
|
36
|
+
/** Cap on rendered pills before the overflow chip. */
|
|
37
|
+
readonly maxBubbles: number;
|
|
32
38
|
}
|
|
@@ -15,6 +15,7 @@ export class BubbleDef extends AbstractDisplayDef {
|
|
|
15
15
|
// props.getColour re-derives the wrapper and discards the stale cache.
|
|
16
16
|
this.getColour = $derived(memoiseRowValueAccessor(props.getColour));
|
|
17
17
|
this._filterFn = $state(props.filterFn);
|
|
18
|
+
this.maxBubbles = $derived(props.maxBubbles ?? 3);
|
|
18
19
|
}
|
|
19
20
|
_filterFn;
|
|
20
21
|
/** Filter predicate. Reassignable. */
|
|
@@ -26,4 +27,6 @@ export class BubbleDef extends AbstractDisplayDef {
|
|
|
26
27
|
}
|
|
27
28
|
/** Per-bubble colour mapper. */
|
|
28
29
|
getColour;
|
|
30
|
+
/** Cap on rendered pills before the overflow chip. */
|
|
31
|
+
maxBubbles;
|
|
29
32
|
}
|
|
@@ -208,6 +208,12 @@ export interface BubbleColumnOptions<TRow extends object, TValue = unknown> exte
|
|
|
208
208
|
* supported (scoria internally wraps it in `Promise.resolve`).
|
|
209
209
|
*/
|
|
210
210
|
getOptions?: () => ReadonlyArray<SelectOption<TValue>> | Promise<ReadonlyArray<SelectOption<TValue>>>;
|
|
211
|
+
/**
|
|
212
|
+
* Maximum number of pills rendered before they collapse into an
|
|
213
|
+
* overflow chip (`"+N more"`). Defaults to 3. Set a larger number
|
|
214
|
+
* to show more pills inline; set `Infinity` to disable the cap.
|
|
215
|
+
*/
|
|
216
|
+
maxBubbles?: number;
|
|
211
217
|
}
|
|
212
218
|
/** Options for `col.progress({ header, value, ... })`. Renders a horizontal
|
|
213
219
|
* progress bar from a numeric ratio. */
|
|
@@ -107,6 +107,44 @@ export function createTable(options) {
|
|
|
107
107
|
if (options.initialState) {
|
|
108
108
|
applyInitialState(ctx, options.initialState);
|
|
109
109
|
}
|
|
110
|
+
// ── 9. Auto-pin structural columns ──────────────────────────────
|
|
111
|
+
// `col.actions` (the ellipsis menu) and `col.rowSelection` (the
|
|
112
|
+
// select-row checkbox) are conventionally pinned to the right and
|
|
113
|
+
// left respectively across every table in the app, so apply that
|
|
114
|
+
// pin automatically here. The caller can override either pin via
|
|
115
|
+
// `initialState.columnPinning` (which runs first above), or via
|
|
116
|
+
// the live `setColumnPinning` updater on the table context.
|
|
117
|
+
const autoPinLeft = new Map();
|
|
118
|
+
const autoPinRight = new Map();
|
|
119
|
+
for (const spec of specs) {
|
|
120
|
+
const d = specsById.get(spec.id);
|
|
121
|
+
if (!d) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const def = unwrapColumnSpec(d).def;
|
|
125
|
+
if (spec.kind === 'actions') {
|
|
126
|
+
autoPinRight.set(spec.id, def.width);
|
|
127
|
+
}
|
|
128
|
+
else if (spec.kind === 'rowSelection') {
|
|
129
|
+
autoPinLeft.set(spec.id, def.width);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (autoPinLeft.size > 0 || autoPinRight.size > 0) {
|
|
133
|
+
const current = ctx.columnPinning;
|
|
134
|
+
const nextLeft = new Map(current.left ?? []);
|
|
135
|
+
const nextRight = new Map(current.right ?? []);
|
|
136
|
+
for (const [id, width] of autoPinLeft) {
|
|
137
|
+
if (!nextLeft.has(id) && !nextRight.has(id)) {
|
|
138
|
+
nextLeft.set(id, width);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const [id, width] of autoPinRight) {
|
|
142
|
+
if (!nextLeft.has(id) && !nextRight.has(id)) {
|
|
143
|
+
nextRight.set(id, width);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
ctx.columnPinning = { left: nextLeft, right: nextRight };
|
|
147
|
+
}
|
|
110
148
|
// ── 9. Build the structured Table instance ─────────────────────
|
|
111
149
|
const table = Object.freeze({
|
|
112
150
|
get name() {
|
package/dist/scss/_mixins.scss
CHANGED
|
@@ -1866,19 +1866,22 @@
|
|
|
1866
1866
|
div.input-cell {
|
|
1867
1867
|
background-color: colours.$ui-blue-1;
|
|
1868
1868
|
height: var(--height);
|
|
1869
|
-
overflow:
|
|
1869
|
+
// `overflow-x: clip` so a narrow filter cell's internal widget
|
|
1870
|
+
// (select chevron, filter-mode funnel, etc.) cannot paint past
|
|
1871
|
+
// the column boundary. The column-separator border now lives on
|
|
1872
|
+
// the outer grid item (`.quick-filter-row > .cell` in
|
|
1873
|
+
// `TableBody.svelte`), so clipping here keeps both the widget
|
|
1874
|
+
// AND the cell's own background inside the track and the border
|
|
1875
|
+
// stays visible. `overflow-y: visible` preserves the inline
|
|
1876
|
+
// dropdown spillage for select / date popups.
|
|
1877
|
+
overflow-x: clip;
|
|
1878
|
+
overflow-y: visible;
|
|
1870
1879
|
padding-left: 0;
|
|
1871
1880
|
padding-right: 0;
|
|
1872
|
-
//
|
|
1873
|
-
//
|
|
1874
|
-
//
|
|
1875
|
-
|
|
1876
|
-
// visually merge because the inner widget's background covers
|
|
1877
|
-
// the cell's table-cell-basic border. `!important` is here to
|
|
1878
|
-
// override the more-specific .focusable rule from
|
|
1879
|
-
// table-cell-basic that occasionally re-paints the right edge
|
|
1880
|
-
// transparent during focus.
|
|
1881
|
-
border-right: solid 1px colours.$ui-blue-4 !important;
|
|
1881
|
+
// Inner widget no longer paints a border-right - the outer cell
|
|
1882
|
+
// wrapper owns the column divider so adjacent filters cannot
|
|
1883
|
+
// visually merge regardless of what the widget paints inside.
|
|
1884
|
+
border-right: none;
|
|
1882
1885
|
box-sizing: border-box;
|
|
1883
1886
|
|
|
1884
1887
|
div.stick-right {
|