Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add support for class list #11299

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -491,7 +491,11 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
let update;

if (name === 'class') {
update = b.stmt(b.call(is_svg ? '$.set_svg_class' : '$.set_class', node_id, value));
if (value.type === 'ObjectExpression') {
update = b.stmt(b.call('$.set_class_list', node_id, value));
} else {
update = b.stmt(b.call(is_svg ? '$.set_svg_class' : '$.set_class', node_id, value));
}
} else if (DOMProperties.includes(name)) {
update = b.stmt(b.assignment('=', b.member(node_id, b.id(name)), value));
} else {
Expand Down
Expand Up @@ -2019,8 +2019,24 @@ function serialize_element_attributes(node, context) {
WhitespaceInsensitiveAttributes.includes(name)
);

//Any other tidy way to rewrite
const is_class_list =
name === 'class' &&
attribute &&
attribute.value &&
attribute.value[0] &&
attribute.value[0].type === 'ExpressionTag' &&
attribute.value[0].expression.type === 'ObjectExpression';

context.state.template.push(
t_expression(b.call('$.attr', b.literal(name), value, b.literal(is_boolean)))
t_expression(
b.call(
is_class_list ? '$.attr_class_list' : '$.attr',
b.literal(name),
value,
b.literal(is_boolean)
)
)
);
}
}
Expand Down
36 changes: 36 additions & 0 deletions packages/svelte/src/internal/client/dom/elements/class.js
Expand Up @@ -62,6 +62,42 @@ export function set_class(dom, value) {
}
}

/**
* @param {HTMLElement} dom
* @param {{[s: string]: any}} value
* @returns {void}
*/
export function set_class_list(dom, value) {

// with the toggle, and force option, as per
// the entry value, class can be added or removed
if (value) {
var entries = Object.entries(value)
for (let [key, entry] of entries) {
dom.classList.toggle(key, !!entry)
}
}

var next_class_name = dom.className;

// for performance reason remove this
if (!next_class_name) {
dom.removeAttribute('class');
}
// Set the updated className
// @ts-expect-error need to add __className to patched prototype
dom.__className = next_class_name;
// always remove the attribute

// Does classlist, need the check of
// dom.className === next_class_name ? in case of
// hydration, if the value
// or say next_class_name differs from the
// className, isn't this should simply update the
// token list and set the _className
}


/**
* @template V
* @param {V} value
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Expand Up @@ -20,7 +20,7 @@ export {
set_dynamic_element_attributes,
set_xlink_attribute
} from './dom/elements/attributes.js';
export { set_class, set_svg_class, toggle_class } from './dom/elements/class.js';
export { set_class, set_svg_class, toggle_class, set_class_list } from './dom/elements/class.js';
export { event, delegate } from './dom/elements/events.js';
export { autofocus, remove_textarea_child } from './dom/elements/misc.js';
export { set_style } from './dom/elements/style.js';
Expand Down
21 changes: 21 additions & 0 deletions packages/svelte/src/internal/server/index.js
Expand Up @@ -263,6 +263,27 @@ export function attr(name, value, boolean) {
return ` ${name}${assignment}`;
}

// Separating the classlist for
// readability?

/**
* @template V
* @param {string} name
* @param {{[s: string]: any}} value
* @returns {string}
*/
export function attr_class_list(name, value) {
const entries = Object.entries(value).reduce((prev, [key, val], idx) => {
if (val) {
return idx === 0 ? key : prev + ' ' + key;
}
return prev;
}, '');

const assignment = `="${escape(entries, true)}"`;
return ` ${name}${assignment}`;
}

/**
* @param {Payload} payload
* @param {boolean} is_html
Expand Down