svelte-headless-table
svelte-headless-table copied to clipboard
addResizedColumns plugin breaks if data store referenced multiple times in script functions without addHiddenColumns plugin
The ability to resize columns breaks if you subscribe to the main data store more than once in separate functions and don't also implement the addHiddenColumns plugin.
Problem
Here is an example in a REPL: https://svelte.dev/repl/71eb58bfe8754460926531cf211405da?version=3.49.0
Details: The REPL above is a clone of the 'Simple Column Resizing' REPL. The only difference is it adds two functions.
function read1() {
console.log($data[0])
}
function read2() {
console.log($data[1])
}
Just adding those functions completely breaks the resizing of columns. The functions don't even have to be called. I haven't yet figured out what causes them to break the resizing; though, I assume it has to do with how Svelte Headless Table manages the state of the resizing columns.
Workaround
By adding and implementing the addHiddenColumns plugin, the columns will resume resizing as expected.
Here is an example in a REPL: https://svelte.dev/repl/9c2868beb9ef4c0897ccdcab0dbfcda4?version=3.49.0
This is the code that has to be added:
import { addHiddenColumns, addResizedColumns } from 'svelte-headless-table/plugins';
const table = createTable(data, {
hideColumns: addHiddenColumns(),
resize: addResizedColumns(),
});
const {
flatColumns,
headerRows,
pageRows,
tableAttrs,
tableBodyAttrs,
visibleColumns,
pluginStates,
} = table.createViewModel(columns);
const ids = flatColumns.map((c) => c.id);
const { hiddenColumnIds } = pluginStates.hideColumns;
let hideForId = Object.fromEntries(ids.map((id) => [id, false]));
$: $hiddenColumnIds = Object.entries(hideForId)
.filter(([, hide]) => hide)
.map(([id]) => id);
It looks like this workaround also only works if you have
const { columnWidths } = pluginStates.resize;
and in the HTML
<pre>$columnWidths = {JSON.stringify($columnWidths, null, 2)}</pre>
I'm going to continue testing to see if I can find anything else out. (The addHiddenColumns plugin is still required in addition to the columnWidths object.)
@patrickhaertel It might be due to how I derive state for the plugins. Each plugin derives new row data given an existing row data from the previous plugin. Some plugins use this hook to set the data of a caching store to provide a reference to "pre-processed" data, but I didn't consider how that would affect multiple subscriptions to the data.
I assumed that multiple subscriptions would be idempotent but that does not seem to be the case with my architecture. Thanks for the report!
It looks like the issue might not be with svelte-headless-table at all but with svelte-subscribe. I might try and look further into this, but here's what I've found so far.
Create an empty object at component top-level and store cell attributes state in it.
let test = {}
for (const row of $headerRows) {
for (const cell of row.cells) {
cell.attrs().subscribe(value => {
test[cell.id] = value
})
}
}
In the table template, replace:
<th {...attrs} use:props.resize>
{...}
</th>
with:
<th {...test[cell.id]} use:props.resize>
{...}
</th>
This will effectively fix the column resizing in every scenario I have tested. It also does not require any other plugins like the previous solutions have. The only change to this workaround might be to add types and to have two separate objects one for headerRow attributes and the other for bodyRow attributes. (You might also be able to use a derived store instead, but haven't tested)
Here's a simple REPL to explore this further: https://svelte.dev/repl/dfa326c49c8d4800a6b3e305619b3595?version=3.48.0
What I find intriguing is that the attrs object is in sync and updates until the data object is subscribed to three times in the simple read() functions.


Overall though, it might make sense to get rid of the <Subscribe />
blocks and replace them with objects created by functions. For example:
let rowAttributes = createRowAttributes(headerRows)
That's just an idea though. Lmk what your thoughts on this are and then I can try and work on a PR if you think this is something that might work.
The <Subscribe />
blocks are needed due to the way I've set up the architecture of Svelte Headless Table. Each table element has a view model item that represents it, and each view model item holds a reference to an attrs
and props
readable store.
If we remove <Subscribe />
, we lose out on the ability to auto-subscribe to those stores in the template. If we replace it with an object created with functions, then Svelte won't know to react to updates on those stores.
I'll have to re-look at the architecture of Svelte Headless Tables to fix any oddities I've introduced into the library.
@patrickhaertel Have you been able to workaround this issue? Unfortunately I do not have the capacity to look into this at the moment.
I'll close this for now. Please feel free to re-open this if the issue persists.