entasis 0.9.2 → 0.10.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/components/AppShell/appShell.theme.js +3 -3
- package/dist/components/ButtonGroup/ButtonGroup.svelte +10 -4
- package/dist/components/ButtonGroup/buttonGroup.mcp.d.ts +1 -1
- package/dist/components/ButtonGroup/buttonGroup.mcp.js +23 -3
- package/dist/components/ButtonGroup/buttonGroup.props.d.ts +19 -5
- package/dist/components/Chart/Chart.svelte +1 -1
- package/dist/components/Chart/chart.cartesian.js +3 -2
- package/dist/components/Chart/chart.fit.d.ts +53 -0
- package/dist/components/Chart/chart.fit.js +81 -0
- package/dist/components/Chart/chart.mcp.d.ts +1 -1
- package/dist/components/Chart/chart.mcp.js +2 -1
- package/dist/components/Chart/chart.polar.js +137 -18
- package/dist/components/Chart/chart.polar.props.d.ts +5 -0
- package/dist/components/Chart/chart.proportion.js +12 -6
- package/dist/components/Chart/chart.relation.network.js +32 -4
- package/dist/components/Chart/chart.relation.sankey.js +53 -6
- package/dist/components/Chart/chart.relation.tree.js +84 -24
- package/dist/components/Chart/chart.series.props.d.ts +6 -0
- package/dist/components/Chart/chart.state.svelte.d.ts +1 -0
- package/dist/components/Chart/chart.state.svelte.js +9 -6
- package/dist/components/Chart/chart.viewport.svelte.d.ts +1 -0
- package/dist/components/Chart/chart.viewport.svelte.js +25 -7
- package/dist/components/DataTable/DataTable.svelte +25 -8
- package/dist/components/DataTable/DataTableRow.svelte +33 -10
- package/dist/components/DataTable/dataTable.mcp.d.ts +1 -1
- package/dist/components/DataTable/dataTable.mcp.js +12 -1
- package/dist/components/DataTable/dataTable.model.svelte.js +4 -3
- package/dist/components/DataTable/dataTable.props.d.ts +17 -0
- package/dist/components/DataTable/dataTable.theme.d.ts +12 -0
- package/dist/components/DataTable/dataTable.theme.js +3 -2
- package/dist/components/DataTable/index.d.ts +1 -1
- package/dist/components/Dialog/Dialog.svelte +12 -7
- package/dist/components/Dialog/dialog.state.svelte.d.ts +2 -1
- package/dist/components/Dialog/dialog.state.svelte.js +7 -7
- package/dist/components/Dialog/dialog.theme.js +1 -1
- package/dist/components/FloatingWindow/floatingWindow.theme.js +1 -1
- package/dist/components/Form/Select/Select.svelte +3 -1
- package/dist/components/PageShell/pageShell.state.svelte.d.ts +1 -1
- package/dist/components/PageShell/pageShell.state.svelte.js +6 -13
- package/dist/components/Popover/Popover.svelte +5 -5
- package/dist/components/Popover/index.d.ts +1 -1
- package/dist/components/Popover/popover.mcp.d.ts +1 -1
- package/dist/components/Popover/popover.mcp.js +33 -6
- package/dist/components/Popover/popover.state.svelte.d.ts +9 -1
- package/dist/components/Popover/popover.state.svelte.js +61 -8
- package/dist/components/Popover/popover.theme.js +1 -1
- package/dist/components/Sidebar/Sidebar.svelte +1 -1
- package/dist/components/Sidebar/SidebarDesktopShell.svelte +26 -6
- package/dist/components/Sidebar/sidebar-layout.js +7 -6
- package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.mcp.js +2 -2
- package/dist/components/Sidebar/sidebar.theme.js +36 -20
- package/dist/components/Theme/index.d.ts +1 -1
- package/dist/components/Theme/index.js +1 -1
- package/dist/components/Theme/theme.mcp.d.ts +1 -1
- package/dist/components/Theme/theme.mcp.js +3 -0
- package/dist/components/Theme/theme.state.svelte.d.ts +4 -0
- package/dist/components/Theme/theme.state.svelte.js +4 -0
- package/dist/components/Tooltip/Tooltip.svelte +2 -4
- package/dist/components/Tooltip/tooltip.attachment.svelte.js +5 -0
- package/dist/components/Tooltip/tooltip.mcp.d.ts +1 -1
- package/dist/components/Tooltip/tooltip.mcp.js +4 -2
- package/dist/generated/componentAliases.d.ts +1 -0
- package/dist/generated/componentAliases.js +1 -0
- package/dist/generated/componentContract.d.ts +13 -3
- package/dist/generated/componentContract.js +15 -1
- package/dist/generated/componentMcpRegistry.d.ts +8 -7
- package/dist/generated/componentMcpRegistry.js +2 -0
- package/dist/i18n/ar.js +1 -1
- package/dist/i18n/de.js +1 -1
- package/dist/i18n/en.js +1 -1
- package/dist/i18n/es.js +1 -1
- package/dist/i18n/fr.js +1 -1
- package/dist/i18n/pt.js +1 -1
- package/dist/i18n/zh.js +1 -1
- package/dist/tailwind/colors.d.ts +10 -3
- package/dist/tailwind/colors.js +6 -0
- package/dist/tailwind/palette.d.ts +5 -0
- package/dist/tailwind/palette.js +4 -0
- package/dist/tailwind/palette.mcp.d.ts +1 -0
- package/dist/tailwind/palette.mcp.js +34 -0
- package/dist/utils/layers.svelte.js +9 -8
- package/dist/utils/registry.svelte.d.ts +21 -0
- package/dist/utils/registry.svelte.js +49 -0
- package/package.json +8 -1
|
@@ -11,7 +11,9 @@ export function compileProportion({ data, mark, path, category, value }) {
|
|
|
11
11
|
: compileArcs(summaries, variant, id);
|
|
12
12
|
}
|
|
13
13
|
export function isProportionDatum(value) {
|
|
14
|
-
return (typeof value === 'object' &&
|
|
14
|
+
return (typeof value === 'object' &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
Reflect.get(value, '__entasisProportion') === true);
|
|
15
17
|
}
|
|
16
18
|
function normalizeProportionVariant(mark, path) {
|
|
17
19
|
const input = mark.variant;
|
|
@@ -119,8 +121,11 @@ function compileArcs(summaries, variant, id) {
|
|
|
119
121
|
})
|
|
120
122
|
],
|
|
121
123
|
scales: { angle: null, radius: null },
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
// A pie has no guides or outside labels to make room for (the legend lives outside the
|
|
125
|
+
// plot), so it fills the plot like any other mark. The inset only keeps the outer half of
|
|
126
|
+
// the slice separator stroke inside the plot.
|
|
127
|
+
inset: arcOptions.strokeWidth,
|
|
128
|
+
radiusRatio: 1
|
|
124
129
|
});
|
|
125
130
|
return [base];
|
|
126
131
|
}
|
|
@@ -162,9 +167,10 @@ function waffleMark(summaries, cells, variant, id) {
|
|
|
162
167
|
seriesFromColor: true,
|
|
163
168
|
render({ chart, color: resolveColor }) {
|
|
164
169
|
const rows = Math.ceil(variant.cells / variant.columns);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
170
|
+
// Square cells as large as the plot allows: the grid fills the plot's width or its
|
|
171
|
+
// height, whichever runs out first, and centres on the other.
|
|
172
|
+
const gap = Math.min(variant.gap, Math.min(chart.width / variant.columns, chart.height / rows) / 2);
|
|
173
|
+
const size = Math.max(0, Math.min((chart.width - gap * (variant.columns - 1)) / variant.columns, (chart.height - gap * (rows - 1)) / rows));
|
|
168
174
|
const width = size * variant.columns + gap * (variant.columns - 1);
|
|
169
175
|
const height = size * rows + gap * (rows - 1);
|
|
170
176
|
const originX = chart.x + (chart.width - width) / 2;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forceCenter, forceCollide, forceLink, forceManyBody, forceSimulation, forceX, forceY } from 'd3-force';
|
|
2
2
|
import { compileRelationEdges } from './chart.relation.data.js';
|
|
3
|
+
import { axisFraction, estimateLabelWidth, fitAxis, LABEL_LINE_BOX } from './chart.fit.js';
|
|
3
4
|
import { compileRelationPointMarks } from './chart.relation.marks.js';
|
|
4
5
|
export function compileNetworkRelation(data, mark, compiled, path, width, height) {
|
|
5
6
|
const relations = compileRelationEdges(data, compiled, mark, path);
|
|
@@ -23,10 +24,37 @@ export function compileNetworkRelation(data, mark, compiled, path, width, height
|
|
|
23
24
|
.force('y', forceY(height / 2).strength(0.035))
|
|
24
25
|
.stop();
|
|
25
26
|
simulation.tick(300);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
// The simulation settles a shape around the centre at whatever size its forces give it; fit
|
|
28
|
+
// that shape to the plot on each axis, leaving room for the labels drawn above the nodes.
|
|
29
|
+
const labels = compiled.labels;
|
|
30
|
+
const settled = nodes.map((node) => ({
|
|
31
|
+
node,
|
|
32
|
+
x: coordinate(node.x, node.id, 'x'),
|
|
33
|
+
y: coordinate(node.y, node.id, 'y'),
|
|
34
|
+
labelWidth: labels.enabled
|
|
35
|
+
? estimateLabelWidth(node.label, labels.fontSize, labels.fontWeight)
|
|
36
|
+
: 0
|
|
37
|
+
}));
|
|
38
|
+
const xs = settled.map((node) => node.x);
|
|
39
|
+
const ys = settled.map((node) => node.y);
|
|
40
|
+
const [xMin, xMax, yMin, yMax] = [
|
|
41
|
+
Math.min(...xs),
|
|
42
|
+
Math.max(...xs),
|
|
43
|
+
Math.min(...ys),
|
|
44
|
+
Math.max(...ys)
|
|
45
|
+
];
|
|
46
|
+
const xFit = fitAxis(settled.map((node) => {
|
|
47
|
+
const extent = Math.max(radius, node.labelWidth / 2);
|
|
48
|
+
return { fraction: axisFraction(node.x, xMin, xMax), before: extent, after: extent };
|
|
49
|
+
}), width);
|
|
50
|
+
const yFit = fitAxis(settled.map((node) => ({
|
|
51
|
+
fraction: axisFraction(node.y, yMin, yMax),
|
|
52
|
+
before: labels.enabled ? radius + 10 + labels.fontSize * LABEL_LINE_BOX.middle.above : radius,
|
|
53
|
+
after: radius
|
|
54
|
+
})), height);
|
|
55
|
+
const positionedNodes = settled.map(({ node, x: settledX, y: settledY }) => {
|
|
56
|
+
const x = xFit.start + axisFraction(settledX, xMin, xMax) * xFit.span;
|
|
57
|
+
const y = yFit.start + axisFraction(settledY, yMin, yMax) * yFit.span;
|
|
30
58
|
return {
|
|
31
59
|
...node,
|
|
32
60
|
kind: 'relation-node',
|
|
@@ -3,23 +3,22 @@ import { sankey, sankeyCenter, sankeyJustify, sankeyLeft, sankeyRight } from 'd3
|
|
|
3
3
|
import { curveBumpX } from 'd3-shape';
|
|
4
4
|
import { compileColor } from './chart.channels.js';
|
|
5
5
|
import { compileRelationEdges } from './chart.relation.data.js';
|
|
6
|
+
import { axisFraction, CHART_FIT_MARGIN, estimateLabelWidth, fitAxis, LABEL_LINE_BOX } from './chart.fit.js';
|
|
6
7
|
import { withoutTooltipPoints } from './chart.mark.js';
|
|
7
8
|
export function compileSankeyRelation(data, mark, compiled, path, width, height) {
|
|
8
9
|
const relations = compileRelationEdges(data, compiled, mark, path);
|
|
9
|
-
const
|
|
10
|
-
const top = clamp(height * 0.06, 20, 36);
|
|
11
|
-
const bottom = top;
|
|
10
|
+
const labels = compiled.labels;
|
|
12
11
|
const nodeWidth = mark.nodeWidth ?? clamp(width * 0.022, 10, 22);
|
|
13
12
|
const nodeGap = mark.nodeGap ?? clamp(height * 0.055, 10, 30);
|
|
14
|
-
const
|
|
13
|
+
const layout = (top, bottom) => sankey()
|
|
15
14
|
.nodeId((node) => node.identity)
|
|
16
15
|
.nodeAlign(resolveAlignment(mark.align))
|
|
17
16
|
.nodeSort((left, right) => left.index - right.index)
|
|
18
17
|
.nodeWidth(nodeWidth)
|
|
19
18
|
.nodePadding(nodeGap)
|
|
20
19
|
.extent([
|
|
21
|
-
[
|
|
22
|
-
[
|
|
20
|
+
[0, top],
|
|
21
|
+
[width, Math.max(top + 1, height - bottom)]
|
|
23
22
|
])
|
|
24
23
|
.iterations(32)({
|
|
25
24
|
nodes: compiled.nodes.map((node) => ({ ...node })),
|
|
@@ -30,10 +29,58 @@ export function compileSankeyRelation(data, mark, compiled, path, width, height)
|
|
|
30
29
|
relation
|
|
31
30
|
}))
|
|
32
31
|
});
|
|
32
|
+
let graph = layout(CHART_FIT_MARGIN, CHART_FIT_MARGIN);
|
|
33
|
+
// Labels are centred on their node, so only a node shorter than a line pushes its label past
|
|
34
|
+
// the plot edge. Lay out again with exactly that overhang reserved.
|
|
35
|
+
const overhang = labelOverhang(graph.nodes, labels, height);
|
|
36
|
+
if (overhang.top > 0.5 || overhang.bottom > 0.5) {
|
|
37
|
+
graph = layout(CHART_FIT_MARGIN + overhang.top, CHART_FIT_MARGIN + overhang.bottom);
|
|
38
|
+
}
|
|
39
|
+
fitColumns(graph.nodes, nodeWidth, labels, width);
|
|
33
40
|
const nodes = graph.nodes.map((node) => compileNode(node, width));
|
|
34
41
|
const links = graph.links.map(compileLink);
|
|
35
42
|
return compileSankeyMarks(nodes, links, mark, compiled.labels, path);
|
|
36
43
|
}
|
|
44
|
+
function labelOverhang(nodes, labels, height) {
|
|
45
|
+
if (!labels.enabled)
|
|
46
|
+
return { top: 0, bottom: 0 };
|
|
47
|
+
const above = labels.fontSize * LABEL_LINE_BOX.middle.above;
|
|
48
|
+
const below = labels.fontSize * LABEL_LINE_BOX.middle.below;
|
|
49
|
+
let top = 0;
|
|
50
|
+
let bottom = 0;
|
|
51
|
+
for (const node of nodes) {
|
|
52
|
+
const { y0, y1 } = resolveNodeBounds(node);
|
|
53
|
+
const middle = (y0 + y1) / 2;
|
|
54
|
+
top = Math.max(top, CHART_FIT_MARGIN - (middle - above));
|
|
55
|
+
bottom = Math.max(bottom, middle + below - (height - CHART_FIT_MARGIN));
|
|
56
|
+
}
|
|
57
|
+
return { top, bottom };
|
|
58
|
+
}
|
|
59
|
+
// Columns are placed evenly across the plot, which leaves the outer labels to overflow or, with
|
|
60
|
+
// guessed margins, a gap. Re-place them so the widest outer labels reach the plot edges: a
|
|
61
|
+
// source's label sits left of its node, every other label to the right.
|
|
62
|
+
function fitColumns(nodes, nodeWidth, labels, width) {
|
|
63
|
+
const columns = nodes.map((node) => resolveNodeBounds(node).x0);
|
|
64
|
+
const first = Math.min(...columns);
|
|
65
|
+
const last = Math.max(...columns);
|
|
66
|
+
const placed = nodes.map((node, index) => {
|
|
67
|
+
const label = labels.enabled
|
|
68
|
+
? 7 + estimateLabelWidth(node.label, labels.fontSize, labels.fontWeight)
|
|
69
|
+
: 0;
|
|
70
|
+
const labelOnLeft = (node.targetLinks?.length ?? 0) === 0;
|
|
71
|
+
return {
|
|
72
|
+
node,
|
|
73
|
+
fraction: axisFraction(columns[index] ?? first, first, last),
|
|
74
|
+
before: labelOnLeft ? label : 0,
|
|
75
|
+
after: nodeWidth + (labelOnLeft ? 0 : label)
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const fit = fitAxis(placed, width);
|
|
79
|
+
for (const { node, fraction } of placed) {
|
|
80
|
+
node.x0 = fit.start + fraction * fit.span;
|
|
81
|
+
node.x1 = node.x0 + nodeWidth;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
37
84
|
function compileSankeyMarks(nodes, links, mark, labels, path) {
|
|
38
85
|
const linkMark = link(links, {
|
|
39
86
|
id: `${mark.id ?? path}:links`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { stratify, tree } from 'd3-hierarchy';
|
|
2
2
|
import { compileChannel } from './chart.channels.js';
|
|
3
3
|
import { relationKeyIdentity } from './chart.relation.data.js';
|
|
4
|
+
import { axisFraction, estimateLabelWidth, fitAxis, LABEL_LINE_BOX } from './chart.fit.js';
|
|
4
5
|
import { compileRelationPointMarks } from './chart.relation.marks.js';
|
|
5
6
|
export function compileTreeRelation(data, mark, compiled, path, width, height) {
|
|
6
7
|
if (compiled.nodes.length === 0) {
|
|
@@ -26,16 +27,74 @@ export function compileTreeRelation(data, mark, compiled, path, width, height) {
|
|
|
26
27
|
.parentId((node) => node.parentIdentity)(sourceNodes);
|
|
27
28
|
const orientation = mark.orientation ?? 'horizontal';
|
|
28
29
|
const radius = mark.nodeRadius ?? 5;
|
|
29
|
-
const padding = clamp(Math.min(width, height) * 0.06, 24, 48);
|
|
30
|
-
const labelSpace = compiled.labels.enabled ? clamp(width * 0.13, 64, 120) : padding;
|
|
31
30
|
const horizontal = orientation === 'horizontal';
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const labels = compiled.labels;
|
|
32
|
+
// d3 spaces the tree on a unit grid; the fit below maps it onto the plot. Only the relative
|
|
33
|
+
// positions matter here, and d3's half-separation gaps at the edges are dropped.
|
|
34
|
+
const layoutRoot = tree().size([1, 1])(root);
|
|
35
|
+
const layoutNodes = layoutRoot.descendants();
|
|
36
|
+
const breadthValues = layoutNodes.map((node) => node.x);
|
|
37
|
+
const breadthMin = Math.min(...breadthValues);
|
|
38
|
+
const breadthMax = Math.max(...breadthValues);
|
|
39
|
+
const maxDepth = layoutRoot.height;
|
|
40
|
+
const placed = layoutNodes.map((node) => {
|
|
38
41
|
const internal = node.children !== undefined;
|
|
42
|
+
const labelWidth = labels.enabled
|
|
43
|
+
? estimateLabelWidth(node.data.label, labels.fontSize, labels.fontWeight)
|
|
44
|
+
: 0;
|
|
45
|
+
return {
|
|
46
|
+
node,
|
|
47
|
+
internal,
|
|
48
|
+
labelWidth,
|
|
49
|
+
breadth: axisFraction(node.x, breadthMin, breadthMax),
|
|
50
|
+
depth: maxDepth > 0 ? node.depth / maxDepth : 0.5
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
// Horizontal: internal labels sit left of their node and leaf labels right, both centred on
|
|
54
|
+
// it vertically. Vertical: labels are centred on the node, above internal nodes and below
|
|
55
|
+
// leaves. Each axis is fitted to the extents those labels add around the nodes.
|
|
56
|
+
const lineAbove = labels.enabled ? labels.fontSize * LABEL_LINE_BOX.middle.above : 0;
|
|
57
|
+
const lineBelow = labels.enabled ? labels.fontSize * LABEL_LINE_BOX.middle.below : 0;
|
|
58
|
+
const depthFit = fitAxis(placed.map(({ depth, internal, labelWidth }) => {
|
|
59
|
+
if (!labels.enabled)
|
|
60
|
+
return { fraction: depth, before: radius, after: radius };
|
|
61
|
+
if (horizontal) {
|
|
62
|
+
return {
|
|
63
|
+
fraction: depth,
|
|
64
|
+
before: internal ? radius + 7 + labelWidth : radius,
|
|
65
|
+
after: internal ? radius : radius + 7 + labelWidth
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
fraction: depth,
|
|
70
|
+
before: internal ? radius + 8 + lineAbove : radius,
|
|
71
|
+
after: internal ? radius : radius + 10 + lineBelow
|
|
72
|
+
};
|
|
73
|
+
}), horizontal ? width : height);
|
|
74
|
+
const breadthFit = fitAxis(placed.map(({ breadth, labelWidth }) => {
|
|
75
|
+
if (horizontal) {
|
|
76
|
+
return {
|
|
77
|
+
fraction: breadth,
|
|
78
|
+
before: Math.max(radius, lineAbove),
|
|
79
|
+
after: Math.max(radius, lineBelow)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const extent = Math.max(radius, labelWidth / 2);
|
|
83
|
+
return { fraction: breadth, before: extent, after: extent };
|
|
84
|
+
}), horizontal ? height : width);
|
|
85
|
+
const position = new Map(placed.map(({ node, breadth, depth }) => {
|
|
86
|
+
const along = depthFit.start + depth * depthFit.span;
|
|
87
|
+
const across = breadthFit.start + breadth * breadthFit.span;
|
|
88
|
+
return [node, horizontal ? { x: along, y: across } : { x: across, y: along }];
|
|
89
|
+
}));
|
|
90
|
+
const at = (node) => {
|
|
91
|
+
const point = position.get(node);
|
|
92
|
+
if (!point)
|
|
93
|
+
throw new Error('[Chart] Tree layout lost a positioned node.');
|
|
94
|
+
return point;
|
|
95
|
+
};
|
|
96
|
+
const nodes = placed.map(({ node, internal }) => {
|
|
97
|
+
const { x, y } = at(node);
|
|
39
98
|
return {
|
|
40
99
|
...node.data,
|
|
41
100
|
kind: 'relation-node',
|
|
@@ -47,21 +106,22 @@ export function compileTreeRelation(data, mark, compiled, path, width, height) {
|
|
|
47
106
|
labelAnchor: horizontal ? (internal ? 'end' : 'start') : 'middle'
|
|
48
107
|
};
|
|
49
108
|
});
|
|
50
|
-
const links = layoutRoot.links().map(({ source, target }) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
109
|
+
const links = layoutRoot.links().map(({ source, target }) => {
|
|
110
|
+
const from = at(source);
|
|
111
|
+
const to = at(target);
|
|
112
|
+
return {
|
|
113
|
+
kind: 'relation-link',
|
|
114
|
+
identity: `${source.data.identity}:${target.data.identity}`,
|
|
115
|
+
source: source.data.id,
|
|
116
|
+
target: target.data.id,
|
|
117
|
+
sourceLabel: source.data.label,
|
|
118
|
+
targetLabel: target.data.label,
|
|
119
|
+
group: target.data.group,
|
|
120
|
+
x1: from.x,
|
|
121
|
+
y1: from.y,
|
|
122
|
+
x2: to.x,
|
|
123
|
+
y2: to.y
|
|
124
|
+
};
|
|
125
|
+
});
|
|
63
126
|
return compileRelationPointMarks(nodes, links, mark, compiled.labels, path);
|
|
64
127
|
}
|
|
65
|
-
function clamp(value, minimum, maximum) {
|
|
66
|
-
return Math.min(maximum, Math.max(minimum, value));
|
|
67
|
-
}
|
|
@@ -95,6 +95,12 @@ export type ChartSeriesMark<TRow> = ChartDataMarkProps<TRow> & ChartFillStyle<TR
|
|
|
95
95
|
line?: boolean | ChartLineOptions<TRow>;
|
|
96
96
|
points?: boolean | ChartPointOptions<TRow>;
|
|
97
97
|
curve?: ChartCurve;
|
|
98
|
+
/**
|
|
99
|
+
* How an area is painted: `gradient` shades the series colour along the value axis, `solid`
|
|
100
|
+
* is one flat fill at `fillOpacity` (20% by default). Gradients apply to palette colours.
|
|
101
|
+
* @default 'gradient'
|
|
102
|
+
*/
|
|
103
|
+
areaFill?: 'gradient' | 'solid';
|
|
98
104
|
analysis?: readonly [ChartSeriesAnalysis, ...ChartSeriesAnalysis[]];
|
|
99
105
|
};
|
|
100
106
|
type ChartBarCoordinates<TRow> = {
|
|
@@ -78,6 +78,7 @@ export declare class ChartState<TRow extends object> extends BoundChartOptions<C
|
|
|
78
78
|
readonly plotClass: string;
|
|
79
79
|
readonly size: import("./chart.validation.js").ChartSize | undefined;
|
|
80
80
|
readonly rootStyle: string;
|
|
81
|
+
readonly plotStyle: string | undefined;
|
|
81
82
|
readonly initialMarkup: string;
|
|
82
83
|
constructor(options: ChartStateOptions<TRow>);
|
|
83
84
|
/** Row key of the pinned datum, controlled by `tooltip.value` when it is provided. */
|
|
@@ -24,13 +24,16 @@ export class ChartState extends BoundChartOptions {
|
|
|
24
24
|
rootClass = $derived(this.classes.root({ className: this.className }));
|
|
25
25
|
plotClass = $derived(this.classes.plot());
|
|
26
26
|
size = $derived(resolveChartSize(this.height, this.aspectRatio));
|
|
27
|
-
// `height` and `aspectRatio`
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// `height` and `aspectRatio` size the plot, the box the engine draws into, so they go on the
|
|
28
|
+
// plot box and the legend row adds to the chart's height. Sizing the root instead left the
|
|
29
|
+
// plot shorter than the scene the engine laid out, which then shrank to fit. With neither,
|
|
30
|
+
// the root class keeps its own height and the engine measures what the legend leaves.
|
|
31
|
+
rootStyle = $derived(this.size ? 'position:relative;width:100%;height:auto' : 'position:relative;width:100%');
|
|
32
|
+
plotStyle = $derived(this.size?.height !== undefined
|
|
33
|
+
? `flex:none;height:${this.size.height}px`
|
|
31
34
|
: this.size?.aspectRatio !== undefined
|
|
32
|
-
? `
|
|
33
|
-
:
|
|
35
|
+
? `flex:none;aspect-ratio:${this.size.aspectRatio}`
|
|
36
|
+
: undefined);
|
|
34
37
|
initialMarkup;
|
|
35
38
|
#adapter;
|
|
36
39
|
#interaction;
|
|
@@ -20,6 +20,7 @@ export declare class ChartViewportState<TRow extends object> {
|
|
|
20
20
|
readonly controls: readonly ChartControl<any, any>[] | undefined;
|
|
21
21
|
readonly status: string;
|
|
22
22
|
constructor(chart: ChartState<TRow>);
|
|
23
|
+
private clear;
|
|
23
24
|
syncScene(scene: ChartScene<TRow>): void;
|
|
24
25
|
reset: () => void;
|
|
25
26
|
private createControls;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
1
2
|
import { brushX } from '@tanstack/charts/interaction/brush';
|
|
2
3
|
import { controlledSignal } from '@tanstack/charts/interaction/signal';
|
|
3
4
|
import { resolveChartViewport, sameChartViewportDomain } from './chart.viewport.js';
|
|
@@ -20,16 +21,32 @@ export class ChartViewportState {
|
|
|
20
21
|
animation = $derived(this.configuration?.animation);
|
|
21
22
|
controls = $derived(this.createControls());
|
|
22
23
|
status = $derived(this.isZoomed ? 'Chart zoomed on the x axis.' : '');
|
|
24
|
+
/** Whether the x scale brushes categories or a continuous range. */
|
|
25
|
+
#xKind = $derived(this.chart.x?.scale.type === 'band' || this.chart.x?.scale.type === 'point'
|
|
26
|
+
? 'categorical'
|
|
27
|
+
: 'continuous');
|
|
23
28
|
constructor(chart) {
|
|
24
29
|
this.#chart = chart;
|
|
25
30
|
$effect(() => {
|
|
26
31
|
if (this.configuration)
|
|
27
32
|
return;
|
|
28
|
-
this.
|
|
29
|
-
this.fullDomain = [];
|
|
30
|
-
this.range = undefined;
|
|
31
|
-
this.isBrushing = false;
|
|
33
|
+
this.clear();
|
|
32
34
|
});
|
|
35
|
+
// A zoom window kept across a switch between categorical and continuous x would hold
|
|
36
|
+
// bounds of the wrong kind; start over from the next scene's domain instead.
|
|
37
|
+
let kind = untrack(() => this.#xKind);
|
|
38
|
+
$effect(() => {
|
|
39
|
+
if (this.#xKind === kind)
|
|
40
|
+
return;
|
|
41
|
+
kind = this.#xKind;
|
|
42
|
+
untrack(() => this.clear());
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
clear() {
|
|
46
|
+
this.xDomain = undefined;
|
|
47
|
+
this.fullDomain = [];
|
|
48
|
+
this.range = undefined;
|
|
49
|
+
this.isBrushing = false;
|
|
33
50
|
}
|
|
34
51
|
syncScene(scene) {
|
|
35
52
|
if (!this.configuration || this.isZoomed)
|
|
@@ -81,9 +98,10 @@ export class ChartViewportState {
|
|
|
81
98
|
];
|
|
82
99
|
}
|
|
83
100
|
const { start, end } = this.range;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
// Category bounds left from before the x scale turned continuous: the reset above clears
|
|
102
|
+
// them, and the next scene publishes the continuous domain.
|
|
103
|
+
if (typeof start === 'string' || typeof end === 'string')
|
|
104
|
+
return undefined;
|
|
87
105
|
return [
|
|
88
106
|
brushX({
|
|
89
107
|
...options,
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
toolbarSuffix,
|
|
65
65
|
bulkActions,
|
|
66
66
|
rowActions,
|
|
67
|
+
rowActionsWidth,
|
|
68
|
+
onRowActivate,
|
|
67
69
|
expandedContent,
|
|
68
70
|
loading = false,
|
|
69
71
|
error,
|
|
@@ -120,6 +122,8 @@
|
|
|
120
122
|
toolbarSuffix,
|
|
121
123
|
bulkActions,
|
|
122
124
|
rowActions,
|
|
125
|
+
rowActionsWidth,
|
|
126
|
+
onRowActivate,
|
|
123
127
|
expandedContent,
|
|
124
128
|
loading,
|
|
125
129
|
error,
|
|
@@ -164,7 +168,8 @@
|
|
|
164
168
|
rowCount,
|
|
165
169
|
selectionMode,
|
|
166
170
|
pagination,
|
|
167
|
-
rowActions
|
|
171
|
+
rowActions,
|
|
172
|
+
rowActionsWidth
|
|
168
173
|
];
|
|
169
174
|
model.reconcileProcessingMode();
|
|
170
175
|
model.reconcileColumns();
|
|
@@ -439,6 +444,18 @@
|
|
|
439
444
|
if (pagination === false) return null;
|
|
440
445
|
return pagination;
|
|
441
446
|
});
|
|
447
|
+
// The current size is always offered, so a `pageSize` missing from `pageSizes` still shows in
|
|
448
|
+
// the select instead of leaving it on its placeholder.
|
|
449
|
+
const pageSizeItems = $derived.by(() => {
|
|
450
|
+
const sizes = paginationConfig?.pageSizes ?? [25, 50, 100];
|
|
451
|
+
const current = tableState!.pagination.pageSize;
|
|
452
|
+
const offered = sizes.includes(current)
|
|
453
|
+
? sizes
|
|
454
|
+
: [...sizes, current].sort((left, right) => left - right);
|
|
455
|
+
return offered.map((value) => ({ value: String(value), label: String(value) }));
|
|
456
|
+
});
|
|
457
|
+
const uid = $props.id();
|
|
458
|
+
const rowsPerPageId = `${uid}-rows-per-page`;
|
|
442
459
|
const tableApi: DataTableApi<TData> = {
|
|
443
460
|
get state() {
|
|
444
461
|
return model.state;
|
|
@@ -633,15 +650,15 @@
|
|
|
633
650
|
{#if paginationConfig && paginationConfig.showControls !== false}
|
|
634
651
|
<div class={classes.footer()}>
|
|
635
652
|
<div class={classes.toolbarGroup()}>
|
|
636
|
-
<span class={classes.summary()}>
|
|
637
|
-
|
|
653
|
+
<span class={classes.summary()}>
|
|
654
|
+
{t.paginationSummary(pageStart, pageEnd, totalItems)}
|
|
655
|
+
</span>
|
|
656
|
+
<!-- The visible text names the select; a field label would print it a second time. -->
|
|
657
|
+
<span id={rowsPerPageId} class={classes.summary()}>{t.dataTableRowsPerPage}</span>
|
|
638
658
|
<Select
|
|
639
659
|
size="small"
|
|
640
|
-
|
|
641
|
-
items={
|
|
642
|
-
value: String(value),
|
|
643
|
-
label: String(value)
|
|
644
|
-
}))}
|
|
660
|
+
triggerAttrs={{ 'aria-labelledby': rowsPerPageId }}
|
|
661
|
+
items={pageSizeItems}
|
|
645
662
|
value={String(tableState!.pagination.pageSize)}
|
|
646
663
|
{disabled}
|
|
647
664
|
onValueChange={(value) => model.setPageSize(Number(value))}
|
|
@@ -151,21 +151,44 @@
|
|
|
151
151
|
const control = (event.currentTarget as HTMLElement).querySelector<HTMLElement>(
|
|
152
152
|
'button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
153
153
|
);
|
|
154
|
-
control
|
|
154
|
+
if (control) control.focus();
|
|
155
|
+
else if (event.key === 'Enter' && activatesFrom(column.id)) activate(event);
|
|
155
156
|
};
|
|
156
157
|
|
|
157
|
-
const handleCellClick = (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
const handleCellClick = (
|
|
159
|
+
event: MouseEvent,
|
|
160
|
+
rowIndex: number,
|
|
161
|
+
columnIndex: number,
|
|
162
|
+
columnId: string
|
|
163
|
+
) => {
|
|
161
164
|
const target = event.target;
|
|
162
165
|
const interactive =
|
|
163
166
|
target instanceof Element
|
|
164
|
-
? target.closest(
|
|
167
|
+
? target.closest(
|
|
168
|
+
'a[href], button, input, select, textarea, label, [contenteditable], [tabindex]:not([tabindex="-1"])'
|
|
169
|
+
)
|
|
165
170
|
: null;
|
|
166
|
-
|
|
167
|
-
(
|
|
171
|
+
const onControl = !!interactive && interactive !== event.currentTarget;
|
|
172
|
+
if (interactionMode === 'grid') {
|
|
173
|
+
model.moveFocusedCell(rowIndex, columnIndex);
|
|
174
|
+
if (!onControl) (event.currentTarget as HTMLElement).focus();
|
|
175
|
+
}
|
|
176
|
+
// A click that ends a text selection is the reader copying a value, not opening the row.
|
|
177
|
+
const selecting = !(window.getSelection()?.isCollapsed ?? true);
|
|
178
|
+
if (!onControl && !selecting && activatesFrom(columnId)) activate(event);
|
|
168
179
|
};
|
|
180
|
+
|
|
181
|
+
// The selection and actions columns own their clicks; group rows expand rather than open.
|
|
182
|
+
const activatable = $derived(
|
|
183
|
+
!!model.props.onRowActivate && !model.props.disabled && !row.getIsGrouped()
|
|
184
|
+
);
|
|
185
|
+
const activatesFrom = (columnId: string) =>
|
|
186
|
+
activatable &&
|
|
187
|
+
columnId !== DATA_TABLE_SELECTION_COLUMN &&
|
|
188
|
+
columnId !== DATA_TABLE_ACTIONS_COLUMN &&
|
|
189
|
+
model.editing?.row.id !== row.id;
|
|
190
|
+
const activate = (event: MouseEvent | KeyboardEvent) =>
|
|
191
|
+
model.props.onRowActivate?.({ ...rowPayload, event });
|
|
169
192
|
</script>
|
|
170
193
|
|
|
171
194
|
<tr
|
|
@@ -177,7 +200,7 @@
|
|
|
177
200
|
data-row-id={row.id}
|
|
178
201
|
data-grid-row={rowIndex}
|
|
179
202
|
data-selected={rowSelected}
|
|
180
|
-
class={classes.row({ density, grouped: row.getIsGrouped() })}
|
|
203
|
+
class={classes.row({ density, grouped: row.getIsGrouped(), activatable })}
|
|
181
204
|
style:grid-template-columns={gridTemplate}
|
|
182
205
|
{@attach measureAttachment}
|
|
183
206
|
>
|
|
@@ -225,7 +248,7 @@
|
|
|
225
248
|
onmousedown={(event) => {
|
|
226
249
|
if (event.detail > 1 && config?.editor) event.preventDefault();
|
|
227
250
|
}}
|
|
228
|
-
onclick={(event) => handleCellClick(event, rowIndex, allIndex)}
|
|
251
|
+
onclick={(event) => handleCellClick(event, rowIndex, allIndex, column.id)}
|
|
229
252
|
onkeydown={(event) => handleCellKeydown(event, column, allIndex)}
|
|
230
253
|
ondblclick={(event) => {
|
|
231
254
|
if (!config?.editor) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const dataTableDescription = "\n# DataTable\n\nDataTable is the typed, interactive, virtualized table for application data. Its public API is\nEntasis-native; TanStack Table Core remains private, but it must be installed as an optional peer\ndependency. Use Table for static tabular content.\n\n## Requires\n\nDataTable builds its row model with TanStack Table Core, which ships with entasis.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n import {\n createDataTableColumnHelper,\n DataTable\n } from './index.ts';\n\n type Person = { id: string; name: string; role: string; active: boolean };\n const column = createDataTableColumnHelper<Person>();\n const columns = [\n column.accessor('name', {\n id: 'name',\n header: 'Name',\n sortable: true,\n filter: { type: 'text' }\n }),\n column.accessor('role', { id: 'role', header: 'Role' }),\n column.accessor('active', {\n id: 'active',\n header: 'Active',\n filter: { type: 'boolean' }\n })\n ];\n</script>\n\n<DataTable\n {items}\n {columns}\n getRowId={(person) => person.id}\n height={420}\n search\n pagination={{ pageSize: 25, pageSizes: [25, 50, 100] }}\n/>\n```\n\n`items`, `columns`, and `getRowId` are required. Without `height`, DataTable fills a\nparent with a definite height so the virtual viewport remains bounded. Pass `virtualize={false}`\ninstead to drop the table into normal document flow at its natural height:\n\n```svelte\n<DataTable {items} {columns} {getRowId} virtualize={false} />\n```\n\n## Core props\n\n- **items**: `readonly TData[]`.\n- **columns**: `readonly DataTableColumn<TData>[]`.\n- **getRowId**: stable row identity used by selection, expansion, virtualization, and focus.\n- **height**: optional `string | number`; otherwise fills a definite-height parent. Only read\n while virtualizing.\n- **virtualize**: default true. False renders every row in normal flow, so the table needs no\n height and no definite-height parent.\n- **density**: `compact | normal | comfortable`, default `normal`.\n- **interactionMode**: `table | grid`, default `table`.\n- **processingMode**: `client | manual`, default `client`.\n- **selectionMode**: `none | single | multiple`, default `none`.\n- **pagination**: `false | DataTablePaginationConfig`. False disables pagination processing.\n `showControls: false` keeps processing active while hiding the built-in footer.\n- **search**: false, true, or placeholder/debounce configuration.\n- **showColumnVisibilityControl**: default false.\n- **stickyHeader**, **overscan**, **estimatedRowHeight**, and **animateRows** control rendering.\n- **disabled** blocks sorting controls, selection paths, editing, and column manipulation.\n- **loading** and **error** drive table states and the NetworkIndicator.\n- **api**: bindable narrow external-control instance handle (`DataTableApi<TData>`).\n- **cell** and **header**: table-level renderer snippets with `renderDefault` delegates.\n- **class**, **theme**, **ref**, and Svelte attachments follow Entasis conventions.\n\n## Column definitions\n\n`DataTableColumn<TData, TValue>` supports `id`, `accessor`, required `header`, optional\n`cell` and `aggregatedCell`, sorting, filtering, grouping, aggregation, editing, visibility,\nresizing, reordering, pinning, alignment, `width` / `minWidth` / `maxWidth`, and column\nclasses.\n\nUse `createDataTableColumnHelper<TData>()` to infer accessor values inside cell and editor\nconfiguration. Raw DataTableColumn definitions remain available as an escape hatch.\n\n## Rendering\n\nColumn-level renderers are part of the default rendering chain. A table-level `cell` renderer\nwraps every public ordinary, grouped, aggregated, and placeholder cell. It does not wrap selection\ncells, action cells, detail rows, or active editors. A table-level `header` renderer wraps only\nconfigured public header content; sorting, menus, dragging, resizing, focus, and ARIA remain owned\nby DataTable.\n\n`DataTableCellRenderPayload<TData>` contains the normal cell payload plus `column`,\n`placeholder`, and `renderDefault`. `DataTableHeaderRenderPayload<TData>` contains the normal\nheader payload plus `renderDefault`.\n\n```svelte\n{#snippet cell({ columnId, value, renderDefault })}\n {#if columnId === 'status'}\n <Chip>{value}</Chip>\n {:else}\n {@render renderDefault()}\n {/if}\n{/snippet}\n```\n\nPrecedence is structural rendering or an active editor, then the table-level renderer, then\n`renderDefault`, which resolves the column renderer before the built-in fallback.\n\n## Filters\n\nBuilt-in filters are text, number range, select, multi-select, date range, and boolean.\nA custom filter is `{ type: 'custom', render, predicate }`. Its renderer receives the column,\ncurrent value, active state, `setValue(value)`, and `clear()`; `predicate(row, value,\ncolumnValue)` owns client matching. Filtering, searching, sorting, and grouping reset page one.\n\n## State and external composition\n\nUse `createDataTableState(columns, initialState?, pageSize?)` for controlled state. The bindable\nstate contains sorting, globalFilter, columnFilters, one-based pagination, rowSelection,\ncolumnVisibility, columnOrder, columnPinning, columnSizing, grouping, and expanded.\n\nDataTable updates replace slices immutably and call `onStateChange(nextState)`. Parent-originated\nmutations through `bind:state` are observed without calling that callback again.\n\n`bind:api` exposes a stable `DataTableApi<TData>` with reactive state, totals, visible\nrows, selected loaded rows, save status, and commands for global/column filters, selection clearing,\npage, and page size. Commands use the same guards and reset rules as built-in controls.\n\n```svelte\n<DataTable\n bind:state\n bind:api\n pagination={{ pageSize: 25, showControls: false }}\n {...props}\n/>\n```\n\n## Client and manual processing\n\nClient mode runs filtering, sorting, grouping, aggregation, expansion, and pagination locally.\n\nManual mode delegates filtering, sorting, and pagination together. It requires `rowCount`, and\n`items` must contain the already processed current page. Grouping and aggregation are client-only\nin this release: grouping commands are hidden and unsupported manual grouping state is normalized\naway. Abort stale requests when state changes. In manual mode, `dataTable.selectedRows` includes\nonly loaded row objects; all selected IDs remain in `state.rowSelection`.\n\n## Editing\n\nBuilt-in editors are text, number, select, date, and switch. A custom editor snippet receives row\nmetadata, draft, pending/error state, `setDraft`, `commit`, and `cancel`. DataTable never\nmutates rows; changed values call `onCellCommit` and render optimistically while the\nNetworkIndicator tracks the request. Unchanged drafts close without a commit. Rejection rolls back,\nrestores the editor and draft, and exposes the error. Only one asynchronous transaction can be\nactive or recoverable. Columns with editors do not enter edit mode without `onCellCommit` and\nissue a development warning.\n\nEnter and outside clicks commit, Escape cancels, and Tab commits without leaving the edited cell.\nCalendar selection synchronizes its draft before commit. Arrow keys remain owned by editor controls.\n\n## Slots\n\n- **caption**, **toolbarPrefix**, **toolbarSuffix**, **bulkActions**, **rowActions**,\n **expandedContent**, **loadingContent**, **empty**, **noResults**, and **errorContent**.\n- Toolbar slots receive state, selected loaded rows, visible rows, clearFilters, and clearSelection.\n- Row slots receive row identity, selection/expansion state, depth, and guarded toggle actions.\n\n## Accessibility and virtualization\n\nTable mode preserves native table semantics. Grid mode uses one roving cell tab stop with arrows,\nTab/Shift+Tab, Home/End, Ctrl+Home/Ctrl+End, and PageUp/PageDown. Focus is reconciled by row and\ncolumn identity after data, pagination, filtering, grouping, and visibility changes. State rows use\ncomplete column spans and keep recovery controls keyboard reachable. Logical row indices derive\nfrom the final grouped/expanded row model.\n\nRows are virtualized by default; `virtualize={false}` renders all of them in normal flow at\ntheir natural height, keeping the same selection, keyboard navigation, and editing paths. Use it for\nsmall datasets that belong in document flow, not for large ones. Semantic table mode keeps all\ncolumns mounted; grid mode additionally virtualizes center columns while pinned columns stay\nmounted. Resize handles support pointer and\nkeyboard resizing. Reorder handles support left/right keyboard movement and pointer movement within\ntheir current pin region. Logical inset positioning preserves RTL pinning.\n";
|
|
1
|
+
export declare const dataTableDescription = "\n# DataTable\n\nDataTable is the typed, interactive, virtualized table for application data. Its public API is\nEntasis-native; TanStack Table Core remains private, but it must be installed as an optional peer\ndependency. Use Table for static tabular content.\n\n## Requires\n\nDataTable builds its row model with TanStack Table Core, which ships with entasis.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n import {\n createDataTableColumnHelper,\n DataTable\n } from './index.ts';\n\n type Person = { id: string; name: string; role: string; active: boolean };\n const column = createDataTableColumnHelper<Person>();\n const columns = [\n column.accessor('name', {\n id: 'name',\n header: 'Name',\n sortable: true,\n filter: { type: 'text' }\n }),\n column.accessor('role', { id: 'role', header: 'Role' }),\n column.accessor('active', {\n id: 'active',\n header: 'Active',\n filter: { type: 'boolean' }\n })\n ];\n</script>\n\n<DataTable\n {items}\n {columns}\n getRowId={(person) => person.id}\n height={420}\n search\n pagination={{ pageSize: 25, pageSizes: [25, 50, 100] }}\n/>\n```\n\n`items`, `columns`, and `getRowId` are required. Without `height`, DataTable fills a\nparent with a definite height so the virtual viewport remains bounded. Pass `virtualize={false}`\ninstead to drop the table into normal document flow at its natural height:\n\n```svelte\n<DataTable {items} {columns} {getRowId} virtualize={false} />\n```\n\n## Core props\n\n- **items**: `readonly TData[]`.\n- **columns**: `readonly DataTableColumn<TData>[]`.\n- **getRowId**: stable row identity used by selection, expansion, virtualization, and focus.\n- **height**: optional `string | number`; otherwise fills a definite-height parent. Only read\n while virtualizing.\n- **virtualize**: default true. False renders every row in normal flow, so the table needs no\n height and no definite-height parent.\n- **density**: `compact | normal | comfortable`, default `normal`.\n- **interactionMode**: `table | grid`, default `table`.\n- **processingMode**: `client | manual`, default `client`.\n- **selectionMode**: `none | single | multiple`, default `none`.\n- **pagination**: `false | DataTablePaginationConfig`. False disables pagination processing.\n `showControls: false` keeps processing active while hiding the built-in footer. The size select\n always offers the current `pageSize`, even when `pageSizes` leaves it out.\n- **search**: false, true, or placeholder/debounce configuration.\n- **showColumnVisibilityControl**: default false.\n- **stickyHeader**, **overscan**, **estimatedRowHeight**, and **animateRows** control rendering.\n- **disabled** blocks sorting controls, selection paths, editing, and column manipulation.\n- **loading** and **error** drive table states and the NetworkIndicator.\n- **api**: bindable narrow external-control instance handle (`DataTableApi<TData>`).\n- **cell** and **header**: table-level renderer snippets with `renderDefault` delegates.\n- **class**, **theme**, **ref**, and Svelte attachments follow Entasis conventions.\n\n## Column definitions\n\n`DataTableColumn<TData, TValue>` supports `id`, `accessor`, required `header`, optional\n`cell` and `aggregatedCell`, sorting, filtering, grouping, aggregation, editing, visibility,\nresizing, reordering, pinning, alignment, `width` / `minWidth` / `maxWidth`, and column\nclasses.\n\nUse `createDataTableColumnHelper<TData>()` to infer accessor values inside cell and editor\nconfiguration. Raw DataTableColumn definitions remain available as an escape hatch.\n\n## Rendering\n\nColumn-level renderers are part of the default rendering chain. A table-level `cell` renderer\nwraps every public ordinary, grouped, aggregated, and placeholder cell. It does not wrap selection\ncells, action cells, detail rows, or active editors. A table-level `header` renderer wraps only\nconfigured public header content; sorting, menus, dragging, resizing, focus, and ARIA remain owned\nby DataTable.\n\n`DataTableCellRenderPayload<TData>` contains the normal cell payload plus `column`,\n`placeholder`, and `renderDefault`. `DataTableHeaderRenderPayload<TData>` contains the normal\nheader payload plus `renderDefault`.\n\n```svelte\n{#snippet cell({ columnId, value, renderDefault })}\n {#if columnId === 'status'}\n <Chip>{value}</Chip>\n {:else}\n {@render renderDefault()}\n {/if}\n{/snippet}\n```\n\nPrecedence is structural rendering or an active editor, then the table-level renderer, then\n`renderDefault`, which resolves the column renderer before the built-in fallback.\n\n## Filters\n\nBuilt-in filters are text, number range, select, multi-select, date range, and boolean.\nA custom filter is `{ type: 'custom', render, predicate }`. Its renderer receives the column,\ncurrent value, active state, `setValue(value)`, and `clear()`; `predicate(row, value,\ncolumnValue)` owns client matching. Filtering, searching, sorting, and grouping reset page one.\n\n## State and external composition\n\nUse `createDataTableState(columns, initialState?, pageSize?)` for controlled state. The bindable\nstate contains sorting, globalFilter, columnFilters, one-based pagination, rowSelection,\ncolumnVisibility, columnOrder, columnPinning, columnSizing, grouping, and expanded.\n\nDataTable updates replace slices immutably and call `onStateChange(nextState)`. Parent-originated\nmutations through `bind:state` are observed without calling that callback again.\n\n`bind:api` exposes a stable `DataTableApi<TData>` with reactive state, totals, visible\nrows, selected loaded rows, save status, and commands for global/column filters, selection clearing,\npage, and page size. Commands use the same guards and reset rules as built-in controls.\n\n```svelte\n<DataTable\n bind:state\n bind:api\n pagination={{ pageSize: 25, showControls: false }}\n {...props}\n/>\n```\n\n## Client and manual processing\n\nClient mode runs filtering, sorting, grouping, aggregation, expansion, and pagination locally.\n\nManual mode delegates filtering, sorting, and pagination together. It requires `rowCount`, and\n`items` must contain the already processed current page. Grouping and aggregation are client-only\nin this release: grouping commands are hidden and unsupported manual grouping state is normalized\naway. Abort stale requests when state changes. In manual mode, `dataTable.selectedRows` includes\nonly loaded row objects; all selected IDs remain in `state.rowSelection`.\n\n## Editing\n\nBuilt-in editors are text, number, select, date, and switch. A custom editor snippet receives row\nmetadata, draft, pending/error state, `setDraft`, `commit`, and `cancel`. DataTable never\nmutates rows; changed values call `onCellCommit` and render optimistically while the\nNetworkIndicator tracks the request. Unchanged drafts close without a commit. Rejection rolls back,\nrestores the editor and draft, and exposes the error. Only one asynchronous transaction can be\nactive or recoverable. Columns with editors do not enter edit mode without `onCellCommit` and\nissue a development warning.\n\nEnter and outside clicks commit, Escape cancels, and Tab commits without leaving the edited cell.\nCalendar selection synchronizes its draft before commit. Arrow keys remain owned by editor controls.\n\n## Slots\n\n- **caption**, **toolbarPrefix**, **toolbarSuffix**, **bulkActions**, **rowActions**,\n **expandedContent**, **loadingContent**, **empty**, **noResults**, and **errorContent**.\n- Toolbar slots receive state, selected loaded rows, visible rows, clearFilters, and clearSelection.\n- Row slots receive row identity, selection/expansion state, depth, and guarded toggle actions.\n- The row actions column is 36px, one icon button; set `rowActionsWidth` (pixels) for text\n buttons or several actions.\n\n## Row activation\n\n`onRowActivate(payload)` receives the row payload plus the native `event`, for modifier keys.\nIt fires on a click anywhere in the row except on a control (link, button, input, label, focusable\nelement), the selection or actions column, a group row, or a click that ends a text selection.\nIn grid mode Enter activates a cell that has no editor and no control. Rows show a pointer while it\nis set. Table-mode rows are not focusable, so keep a link or row action for keyboard users.\n\n## Accessibility and virtualization\n\nTable mode preserves native table semantics. Grid mode uses one roving cell tab stop with arrows,\nTab/Shift+Tab, Home/End, Ctrl+Home/Ctrl+End, and PageUp/PageDown. Focus is reconciled by row and\ncolumn identity after data, pagination, filtering, grouping, and visibility changes. State rows use\ncomplete column spans and keep recovery controls keyboard reachable. Logical row indices derive\nfrom the final grouped/expanded row model.\n\nRows are virtualized by default; `virtualize={false}` renders all of them in normal flow at\ntheir natural height, keeping the same selection, keyboard navigation, and editing paths. Use it for\nsmall datasets that belong in document flow, not for large ones. Semantic table mode keeps all\ncolumns mounted; grid mode additionally virtualizes center columns while pinned columns stay\nmounted. Resize handles support pointer and\nkeyboard resizing. Reorder handles support left/right keyboard movement and pointer movement within\ntheir current pin region. Logical inset positioning preserves RTL pinning.\n";
|
|
@@ -68,7 +68,8 @@ instead to drop the table into normal document flow at its natural height:
|
|
|
68
68
|
- **processingMode**: \`client | manual\`, default \`client\`.
|
|
69
69
|
- **selectionMode**: \`none | single | multiple\`, default \`none\`.
|
|
70
70
|
- **pagination**: \`false | DataTablePaginationConfig\`. False disables pagination processing.
|
|
71
|
-
\`showControls: false\` keeps processing active while hiding the built-in footer.
|
|
71
|
+
\`showControls: false\` keeps processing active while hiding the built-in footer. The size select
|
|
72
|
+
always offers the current \`pageSize\`, even when \`pageSizes\` leaves it out.
|
|
72
73
|
- **search**: false, true, or placeholder/debounce configuration.
|
|
73
74
|
- **showColumnVisibilityControl**: default false.
|
|
74
75
|
- **stickyHeader**, **overscan**, **estimatedRowHeight**, and **animateRows** control rendering.
|
|
@@ -171,6 +172,16 @@ Calendar selection synchronizes its draft before commit. Arrow keys remain owned
|
|
|
171
172
|
**expandedContent**, **loadingContent**, **empty**, **noResults**, and **errorContent**.
|
|
172
173
|
- Toolbar slots receive state, selected loaded rows, visible rows, clearFilters, and clearSelection.
|
|
173
174
|
- Row slots receive row identity, selection/expansion state, depth, and guarded toggle actions.
|
|
175
|
+
- The row actions column is 36px, one icon button; set \`rowActionsWidth\` (pixels) for text
|
|
176
|
+
buttons or several actions.
|
|
177
|
+
|
|
178
|
+
## Row activation
|
|
179
|
+
|
|
180
|
+
\`onRowActivate(payload)\` receives the row payload plus the native \`event\`, for modifier keys.
|
|
181
|
+
It fires on a click anywhere in the row except on a control (link, button, input, label, focusable
|
|
182
|
+
element), the selection or actions column, a group row, or a click that ends a text selection.
|
|
183
|
+
In grid mode Enter activates a cell that has no editor and no control. Rows show a pointer while it
|
|
184
|
+
is set. Table-mode rows are not focusable, so keep a link or row action for keyboard users.
|
|
174
185
|
|
|
175
186
|
## Accessibility and virtualization
|
|
176
187
|
|