From 73b5bd2dd646a75ed30fb6314d4f05b86ab315cf Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 25 Oct 2019 08:29:26 -0400 Subject: [PATCH] fix scoping class and `class:` with spread attributes (#3790) --- src/compiler/compile/nodes/Element.ts | 6 ++++ .../render_dom/wrappers/Element/index.ts | 9 +++++ .../compile/render_ssr/handlers/Element.ts | 36 +++++++++---------- src/runtime/internal/ssr.ts | 9 ++++- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 2981741fa06c..d353d20158bb 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -105,6 +105,7 @@ export default class Element extends Node { animation?: Animation = null; children: INode[]; namespace: string; + needs_manual_style_scoping: boolean; constructor(component, parent, scope, info: any) { super(component, parent, scope, info); @@ -712,6 +713,11 @@ export default class Element extends Node { } add_css_class() { + if (this.attributes.some(attr => attr.is_spread)) { + this.needs_manual_style_scoping = true; + return; + } + const { id } = this.component.stylesheet; const class_attribute = this.attributes.find(a => a.name === 'class'); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 8f54ebf4686f..253ab107d8e6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -344,6 +344,7 @@ export default class ElementWrapper extends Wrapper { this.add_animation(block); this.add_actions(block); this.add_classes(block); + this.add_manual_style_scoping(block); if (nodes && this.renderer.options.hydratable) { block.chunks.claim.push( @@ -838,6 +839,14 @@ export default class ElementWrapper extends Wrapper { } }); } + + add_manual_style_scoping(block) { + if (this.node.needs_manual_style_scoping) { + const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`; + block.chunks.hydrate.push(updater); + block.chunks.update.push(updater); + } + } } function to_html(wrappers: Array, block: Block, literal: any, state: any) { diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 1f7c0b7b9fcd..65013a8d079a 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,5 +1,4 @@ import { is_void } from '../../../utils/names'; -import Class from '../../nodes/Class'; import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import { get_slot_scope } from './shared/get_slot_scope'; import Renderer, { RenderOptions } from '../Renderer'; @@ -69,15 +68,17 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_string(`<${node.name}`); - const class_expression = node.classes.length > 0 && node.classes - .map((class_directive: Class) => { - const { expression, name } = class_directive; - const snippet = expression ? expression.node : x`#ctx.${name}`; - return x`${snippet} ? "${name}" : ""`; - }) - .reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); - - let add_class_attribute = class_expression ? true : false; + const class_expression_list = node.classes.map(class_directive => { + const { expression, name } = class_directive; + const snippet = expression ? expression.node : x`#ctx.${name}`; + return x`${snippet} ? "${name}" : ""`; + }); + if (node.needs_manual_style_scoping) { + class_expression_list.push(x`"${node.component.stylesheet.id}"`); + } + const class_expression = + class_expression_list.length > 0 && + class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); if (node.attributes.some(attr => attr.is_spread)) { // TODO dry this out @@ -98,17 +99,15 @@ export default function(node: Element, renderer: Renderer, options: RenderOption ) { // a boolean attribute with one non-Text chunk args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} || null }`); - } else if (name === 'class' && class_expression) { - // Add class expression - args.push(x`{ ${attribute.name}: [${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim() }`); } else { - args.push(x`{ ${attribute.name}: ${(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)} }`); + args.push(x`{ ${attribute.name}: ${get_attribute_value(attribute)} }`); } } }); - renderer.add_expression(x`@spread([${args}])`); + renderer.add_expression(x`@spread([${args}], ${class_expression});`); } else { + let add_class_attribute = !!class_expression; node.attributes.forEach(attribute => { const name = attribute.name.toLowerCase(); if (name === 'value' && node.name.toLowerCase() === 'textarea') { @@ -137,6 +136,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_string(`"`); } }); + if (add_class_attribute) { + renderer.add_expression(x`@add_classes([${class_expression}].join(' ').trim())`); + } } node.bindings.forEach(binding => { @@ -162,10 +164,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } }); - if (add_class_attribute) { - renderer.add_expression(x`@add_classes([${class_expression}].join(' ').trim())`); - } - renderer.add_string('>'); if (node_contents !== undefined) { diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index 208a4637c765..83e585a899ae 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -5,8 +5,15 @@ export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFF // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://infra.spec.whatwg.org/#noncharacter -export function spread(args) { +export function spread(args, classes_to_add) { const attributes = Object.assign({}, ...args); + if (classes_to_add) { + if (attributes.class == null) { + attributes.class = classes_to_add; + } else { + attributes.class += ' ' + classes_to_add; + } + } let str = ''; Object.keys(attributes).forEach(name => {