wip of trying to auto-open a tbody when we detect we need it. currently fails because the dom walking mechanism needs to know that it should walk differently in that case

table-td-autoopen
Simon Holthausen 1 year ago
parent 06e7da8266
commit befad31a7a

@ -150,7 +150,8 @@ export default function element(parser) {
svg: false,
mathml: false,
scoped: false,
has_spread: false
has_spread: false,
auto_opens: null
},
parent: null
}

@ -622,15 +622,23 @@ const validation = {
ancestor.type === 'RegularElement' &&
ancestor.name === context.state.parent_element
) {
past_parent = true;
if (!is_tag_valid_with_parent(node.name, context.state.parent_element)) {
if (only_warn) {
if (node.name === 'tr' && ancestor.name === 'table') {
// Handle this and do what the browser does: auto-open a <tbody> prior to the first child
ancestor.metadata.auto_opens = '<tbody>';
// Note that we don't properly handle cases like <table><tbody></tbody><tr><tr>, but that's a rare edge case
} else if (node.name === 'td' && ancestor.name === 'table') {
// Handle this and do what the browser does: auto-open a <tbody><tr> prior to the first child
ancestor.metadata.auto_opens = '<tbody><tr>';
// Note that we don't properly handle cases like <table><tr><tr><td></td></table>, but that's a rare edge case
} else if (only_warn) {
w.node_invalid_placement_ssr(node, `<${node.name}>`, context.state.parent_element);
} else {
e.node_invalid_placement(node, `<${node.name}>`, context.state.parent_element);
}
}
past_parent = true;
}
} else if (ancestor.type === 'RegularElement') {
if (!is_tag_valid_with_ancestor(node.name, ancestor.name)) {

@ -2196,6 +2196,10 @@ export const template_visitors = {
context.state.template.push('>');
if (node.metadata.auto_opens !== null) {
context.state.template.push(node.metadata.auto_opens);
}
/** @type {SourceLocation[]} */
const child_locations = [];
@ -2248,10 +2252,22 @@ export const template_visitors = {
child_state.init.push(b.stmt(b.call('$.reset', arg)));
}
process_children(trimmed, () => b.call('$.child', arg), true, {
...context,
state: child_state
});
process_children(
trimmed,
// TODO: this doesn't work when the table is a sibling, as the expression is then not used
() => {
let call = b.call('$.child', arg);
for (let i = (node.metadata.auto_opens?.split('<').length ?? 1) - 1; i > 0; i--) {
call = b.call('$.child', call);
}
return call;
},
true,
{
...context,
state: child_state
}
);
if (needs_reset) {
child_state.init.push(b.stmt(b.call('$.reset', context.state.node)));

@ -39,6 +39,10 @@ export function RegularElement(node, context) {
return;
}
if (node.metadata.auto_opens !== null) {
context.state.template.push(b.literal(node.metadata.auto_opens));
}
const { hoisted, trimmed } = clean_nodes(
node,
node.fragment.nodes,

@ -303,7 +303,10 @@ export interface RegularElement extends BaseElement {
mathml: boolean;
/** `true` if contains a SpreadAttribute */
has_spread: boolean;
/** `true` if should get a hash on the `class` attribute */
scoped: boolean;
/** Contains a string of the tag(s) that are implicitly opened after this element */
auto_opens: string | null;
};
}

@ -0,0 +1,57 @@
import { test } from '../../test';
let console_error = console.error;
/**
* @type {any[]}
*/
const log = [];
export default test({
solo: true,
compileOptions: {
dev: true // enable validation to ensure it doesn't throw
},
html: `
<table>
<tbody>
<tr>
<td>works1</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td>works2</td>
</tr>
<tr>
<td>works3</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td>works4</td>
</tr>
</tbody>
<tbody>
<tr>
<td>works5</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td>works6</td>
</tr>
</tbody>
</table>
`
});

@ -0,0 +1,28 @@
<table>
<tr>
<td>works1</td>
</tr>
</table>
<table>
{#each ['works2', 'works3'] as cell}
<tr>
<td>{cell}</td>
</tr>
{/each}
</table>
<table>
<tr>
<td>works4</td>
</tr>
<tbody>
<tr>
<td>works5</td>
</tr>
</tbody>
</table>
<table>
<td>works6</td>
</table>

@ -1728,7 +1728,10 @@ declare module 'svelte/compiler' {
mathml: boolean;
/** `true` if contains a SpreadAttribute */
has_spread: boolean;
/** `true` if should get a hash on the `class` attribute */
scoped: boolean;
/** Contains a string of the tag(s) that are implicitly opened after this element */
auto_opens: string | null;
};
}

Loading…
Cancel
Save