implement `bind:this`

pull/1882/head
Rich Harris 6 years ago committed by GitHub
parent ea567f1492
commit 48f1f6b4d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.0.0-alpha1",
"version": "3.0.0-alpha3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -80,7 +80,6 @@ export default class Component {
indirectDependencies: Map<string, Set<string>> = new Map();
template_references: Set<string> = new Set();
refs: Set<string> = new Set();
file: string;
locate: (c: number) => { line: number, column: number };

@ -71,18 +71,6 @@ export default class Selector {
break;
}
i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
if (selector.type === 'RefSelector') {
code.overwrite(selector.start, selector.end, `.svelte-ref-${selector.name}`, {
contentOnly: true,
storeName: false
});
}
}
}
this.blocks.forEach((block, i) => {
@ -173,15 +161,6 @@ function applySelector(stylesheet: Stylesheet, blocks: Block[], node: Node, stac
if (node.name.toLowerCase() !== selector.name.toLowerCase() && selector.name !== '*') return false;
}
else if (selector.type === 'RefSelector') {
if (node.ref && node.ref.name === selector.name) {
stylesheet.nodesWithRefCssClass.set(selector.name, node);
toEncapsulate.push({ node, block });
return true;
}
return;
}
else {
// bail. TODO figure out what these could be
toEncapsulate.push({ node, block });

@ -342,9 +342,6 @@ export default class Stylesheet {
this.nodesWithCssClass.forEach((node: Node) => {
node.addCssClass();
});
this.nodesWithRefCssClass.forEach((node: Node, name: String) => {
node.addCssClass(`svelte-ref-${name}`);
})
}
render(cssOutputFilename: string, shouldTransformSelectors: boolean) {

@ -13,7 +13,6 @@ import { namespaces } from '../../utils/namespaces';
import mapChildren from './shared/mapChildren';
import { dimensions } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch';
import Ref from './Ref';
import list from '../../utils/list';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
@ -86,8 +85,6 @@ export default class Element extends Node {
outro?: Transition = null;
animation?: Animation = null;
children: Node[];
ref: Ref;
namespace: string;
constructor(component, parent, scope, info: any) {
@ -181,10 +178,6 @@ export default class Element extends Node {
this.animation = new Animation(component, this, scope, node);
break;
case 'Ref':
this.ref = new Ref(component, this, scope, node);
break;
default:
throw new Error(`Not implemented: ${node.type}`);
}
@ -551,7 +544,7 @@ export default class Element extends Node {
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`
});
}
} else {
} else if (name !== 'this') {
component.error(binding, {
code: `invalid-binding`,
message: `'${binding.name}' is not a valid binding`

@ -5,7 +5,6 @@ import Binding from './Binding';
import EventHandler from './EventHandler';
import Expression from './shared/Expression';
import Component from '../Component';
import Ref from './Ref';
export default class InlineComponent extends Node {
type: 'InlineComponent';
@ -15,7 +14,6 @@ export default class InlineComponent extends Node {
bindings: Binding[];
handlers: EventHandler[];
children: Node[];
ref: Ref;
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
@ -62,10 +60,6 @@ export default class InlineComponent extends Node {
this.handlers.push(new EventHandler(component, this, scope, node));
break;
case 'Ref':
this.ref = new Ref(component, this, scope, node);
break;
case 'Transition':
component.error(node, {
code: `invalid-transition`,

@ -1,31 +0,0 @@
import Node from './shared/Node';
import isValidIdentifier from '../../utils/isValidIdentifier';
export default class Ref extends Node {
type: 'Ref';
name: string;
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
if (parent.ref) {
component.error({
code: 'duplicate-refs',
message: `Duplicate refs`
});
}
if (!isValidIdentifier(info.name)) {
const suggestion = info.name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
component.error(info, {
code: `invalid-reference-name`,
message: `Reference name '${info.name}' is invalid — must be a valid identifier such as ${suggestion}`
});
} else {
component.refs.add(info.name);
}
this.name = info.name;
}
}

@ -55,8 +55,6 @@ export default function dom(
builder.addBlock(block.toString());
});
const refs = Array.from(component.refs);
if (options.dev && !options.hydratable) {
block.builders.claim.addLine(
'throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");'
@ -87,14 +85,6 @@ export default function dom(
`
: null;
const inject_refs = refs.length > 0
? deindent`
$$refs => {
${refs.map(name => `${name} = $$refs.${name};`)}
}
`
: null;
const body = [];
const not_equal = component.options.immutable ? `@not_equal` : `@safe_not_equal`;
@ -307,8 +297,6 @@ export default function dom(
if (${Array.from(d.dependencies).map(n => `$$dirty.${n}`).join(' || ')}) ${d.snippet}`)}
};
`}
${inject_refs && `$$self.$$.inject_refs = ${inject_refs};`}
}
`);
}

@ -178,6 +178,10 @@ function getDomUpdater(
return null;
}
if (binding.node.name === 'this') {
return null;
}
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?
`@selectOptions(${element.var}, ${snippet})` :
@ -259,6 +263,10 @@ function getValueFromDom(
const { node } = element;
const { name } = binding.node;
if (name === 'this') {
return `$$node`;
}
// <select bind:value='selected>
if (node.name === 'select') {
return node.getStaticAttributeValue('multiple') === true ?

@ -185,7 +185,6 @@ export default class ElementWrapper extends Wrapper {
if (node.classes.length > 0) this.parent.cannotUseInnerHTML();
if (node.intro || node.outro) this.parent.cannotUseInnerHTML();
if (node.handlers.length > 0) this.parent.cannotUseInnerHTML();
if (node.ref) this.parent.cannotUseInnerHTML();
if (this.node.name === 'option') this.parent.cannotUseInnerHTML();
@ -300,7 +299,6 @@ export default class ElementWrapper extends Wrapper {
this.addBindings(block);
this.addEventHandlers(block);
if (this.node.ref) this.addRef(block);
this.addAttributes(block);
this.addTransitions(block);
this.addAnimation(block);
@ -527,6 +525,24 @@ export default class ElementWrapper extends Wrapper {
});
this.initialUpdate = mungedBindings.map(binding => binding.initialUpdate).filter(Boolean).join('\n');
const this_binding = this.bindings.find(b => b.node.name === 'this');
if (this_binding) {
const name = renderer.component.getUniqueName(`${this.var}_binding`);
renderer.component.declarations.push(name);
renderer.component.template_references.add(name);
const { handler } = this_binding.munge(block);
renderer.component.partly_hoisted.push(deindent`
function ${name}($$node) {
${handler.mutation}
}
`);
block.builders.mount.addLine(`@add_binding_callback(() => ctx.${name}(${this.var}));`);
block.builders.destroy.addLine(`ctx.${name}(null);`);
}
}
addAttributes(block: Block) {
@ -597,21 +613,6 @@ export default class ElementWrapper extends Wrapper {
addEventHandlers(block, this.var, this.node.handlers);
}
addRef(block: Block) {
const ref = `#component.$$.refs.${this.node.ref.name}`;
block.builders.mount.addLine(
`${ref} = ${this.var};`
);
block.builders.destroy.addLine(
`if (${ref} === ${this.var}) {
${ref} = null;
#component.$$.inject_refs(#component.$$.refs);
}`
);
}
addTransitions(
block: Block
) {

@ -196,6 +196,33 @@ export default class InlineComponentWrapper extends Wrapper {
}
const munged_bindings = this.node.bindings.map(binding => {
if (binding.name === 'this') {
const fn = component.getUniqueName(`${this.var}_binding`);
component.declarations.push(fn);
component.template_references.add(fn);
let lhs;
if (binding.isContextual && binding.expression.node.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x;
const { name } = binding.expression.node;
const { object, property, snippet } = block.bindings.get(name)();
lhs = snippet;
} else {
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
}
component.partly_hoisted.push(deindent`
function ${fn}($$component) {
${lhs} = $$component;
}
`);
block.builders.destroy.addLine(`ctx.${fn}(null);`);
return `@add_binding_callback(() => ctx.${fn}(${this.var}));`;
}
component.has_reactive_assignments = true;
const name = component.getUniqueName(`${this.var}_${binding.name}_binding`);
@ -318,7 +345,6 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.mount.addBlock(deindent`
if (${name}) {
@mount_component(${name}, ${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});
${this.node.ref && `#component.$$.refs.${this.node.ref.name} = ${name};`}
}
`);
@ -354,15 +380,8 @@ export default class InlineComponentWrapper extends Wrapper {
${this.node.handlers.map(handler => deindent`
${name}.$on("${handler.name}", ${handler.var});
`)}
${this.node.ref && `#component.$$.refs.${this.node.ref.name} = ${name};`}
} else {
${name} = null;
${this.node.ref && deindent`
if (#component.$$.refs.${this.node.ref.name} === ${name}) {
#component.$$.refs.${this.node.ref.name} = null;
#component.$$.inject_refs(#component.$$.refs);
}`}
}
}
`);
@ -391,8 +410,6 @@ export default class InlineComponentWrapper extends Wrapper {
${munged_bindings}
${munged_handlers}
${this.node.ref && `#component.$$.refs.${this.node.ref.name} = ${name};`}
`);
block.builders.create.addLine(`${name}.$$.fragment.c();`);
@ -417,12 +434,6 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.destroy.addBlock(deindent`
${name}.$destroy(${parentNode ? '' : 'detach'});
${this.node.ref && deindent`
if (#component.$$.refs.${this.node.ref.name} === ${name}) {
#component.$$.refs.${this.node.ref.name} = null;
#component.$$.inject_refs(#component.$$.refs);
}
`}
`);
}

@ -10,12 +10,10 @@ export function bind(component, name, callback) {
}
export function mount_component(component, target, anchor) {
const { fragment, refs, inject_refs, on_mount, on_destroy, after_render } = component.$$;
const { fragment, on_mount, on_destroy, after_render } = component.$$;
fragment[fragment.i ? 'i' : 'm'](target, anchor);
inject_refs(refs);
// onMount happens after the initial afterUpdate. Because
// afterUpdate callbacks happen in reverse order (inner first)
// we schedule onMount callbacks before afterUpdate callbacks
@ -69,7 +67,6 @@ export function init(component, options, define, create_fragment, not_equal) {
get: empty,
set: noop,
update: noop,
inject_refs: noop,
not_equal,
bound: blankObject(),
@ -82,7 +79,6 @@ export function init(component, options, define, create_fragment, not_equal) {
// everything else
callbacks: blankObject(),
slotted: options.slots || {},
refs: {},
dirty: null,
binding_groups: []
};

@ -32,7 +32,6 @@ export function handlePromise(promise, info) {
block[block.i ? 'i' : 'm'](info.mount(), info.anchor);
// TODO is some of this redundant?
info.component.$$.inject_refs(info.component.$$.refs);
run_all(info.component.$$.after_render);
flush();
}

@ -58,7 +58,6 @@ function update($$) {
$$.update($$.dirty);
run_all($$.before_render);
$$.fragment.p($$.dirty, $$.get());
$$.inject_refs($$.refs);
$$.dirty = null;
$$.after_render.forEach(add_render_callback);

@ -38,12 +38,10 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
const b = node.children[i + 1];
if (isRefSelector(a, b)) {
node.children.splice(i, 2, {
type: 'RefSelector',
start: a.loc.start.offset,
end: b.loc.end.offset,
name: b.name
});
parser.error({
code: `invalid-ref-selector`,
message: 'ref selectors are no longer supported'
}, a.loc.start.offset);
}
}
}

@ -390,6 +390,13 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
if (type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (type === 'Ref') {
parser.error({
code: `invalid-ref-directive`,
message: `The ref directive is no longer supported — use \`bind:this={${directive_name}}\` instead`
}, start);
}
if (value[0]) {
if (value.length > 1 || value[0].type === 'Text') {
parser.error({

@ -1,28 +0,0 @@
export default {
props: {
active: true
},
warnings: [{
code: `css-unused-selector`,
message: 'Unused CSS selector',
start: {
column: 1,
line: 17,
character: 222
},
end: {
column: 20,
line: 17,
character: 241
},
pos: 222,
frame: `
15: }
16:
17: ref:button.inactive {
^
18: color: green;
19: }`
}]
};

@ -1 +0,0 @@
.svelte-ref-button.active.svelte-xyz{color:red}

@ -1 +0,0 @@
<button class="active svelte-xyz svelte-ref-button">deactivate</button>

@ -1,20 +0,0 @@
<script>
export let active;
let button;
</script>
{#if active}
<button ref:button class='active'>deactivate</button>
{:else}
<button ref:button>activate</button>
{/if}
<style>
ref:button.active {
color: red;
}
ref:button.inactive {
color: green;
}
</style>

@ -1,24 +0,0 @@
export default {
warnings: [{
code: `css-unused-selector`,
message: 'Unused CSS selector',
start: {
column: 1,
line: 14,
character: 120
},
end: {
column: 6,
line: 14,
character: 125
},
pos: 120,
frame: `
12: }
13:
14: ref:d {
^
15: color: blue;
16: }`
}]
};

@ -1 +0,0 @@
.svelte-ref-a.svelte-xyz{color:red}.svelte-ref-b.svelte-xyz{color:green}

@ -1,3 +0,0 @@
<div class="svelte-xyz svelte-ref-a"></div>
<div class="svelte-xyz svelte-ref-b"></div>
<div></div>

@ -1,17 +0,0 @@
<div ref:a></div>
<div ref:b></div>
<div ref:c></div>
<style>
ref:a {
color: red;
}
ref:b {
color: green;
}
ref:d {
color: blue;
}
</style>

@ -2,4 +2,4 @@
export let h1;
</script>
<h1 ref:h1>Hello world!</h1>
<h1 bind:this={h1}>Hello world!</h1>

@ -1,7 +0,0 @@
<div ref:foo/>
<style>
ref:foo {
color: red;
}
</style>

@ -1,99 +0,0 @@
{
"html": {
"start": 0,
"end": 14,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 14,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 5,
"end": 12,
"type": "Ref",
"name": "foo",
"modifiers": [],
"expression": null
}
],
"children": []
},
{
"start": 14,
"end": 16,
"type": "Text",
"data": "\n\n"
}
]
},
"css": [
{
"start": 16,
"end": 60,
"attributes": [],
"children": [
{
"type": "Rule",
"selector": {
"type": "SelectorList",
"children": [
{
"type": "Selector",
"children": [
{
"type": "RefSelector",
"start": 25,
"end": 32,
"name": "foo"
}
],
"start": 25,
"end": 32
}
],
"start": 25,
"end": 32
},
"block": {
"type": "Block",
"children": [
{
"type": "Declaration",
"important": false,
"property": "color",
"value": {
"type": "Value",
"children": [
{
"type": "Identifier",
"name": "red",
"start": 44,
"end": 47
}
],
"start": 43,
"end": 47
},
"start": 37,
"end": 47
}
],
"start": 33,
"end": 51
},
"start": 25,
"end": 51
}
],
"content": {
"start": 23,
"end": 52,
"styles": "\n\tref:foo {\n\t\tcolor: red;\n\t}\n"
}
}
],
"js": []
}

@ -1 +1 @@
<canvas ref:foo></canvas>
<canvas bind:this={foo}></canvas>

@ -1,22 +1,27 @@
{
"html": {
"start": 0,
"end": 25,
"end": 33,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 25,
"end": 33,
"type": "Element",
"name": "canvas",
"attributes": [
{
"start": 8,
"end": 15,
"type": "Ref",
"name": "foo",
"end": 23,
"type": "Binding",
"name": "this",
"modifiers": [],
"expression": null
"expression": {
"type": "Identifier",
"start": 19,
"end": 22,
"name": "foo"
}
}
],
"children": []

@ -11,5 +11,5 @@
});
</script>
<p ref:a>{value}</p>
<p ref:b></p>
<p bind:this={a}>{value}</p>
<p bind:this={b}></p>

@ -4,5 +4,5 @@
</script>
{#if visible}
<input ref:input autofocus>
<input bind:this={input} autofocus>
{/if}

@ -7,7 +7,7 @@
{#await thePromise}
<p>loading...</p>
{:then theValue}
<button ref:button on:click='{() => clicked = theValue}'>click me</button>
<button bind:this={button} on:click='{() => clicked = theValue}'>click me</button>
{:catch theError}
<p>oh no! {theError.message}</p>
{/await}

@ -4,4 +4,4 @@
import List from './List.html';
</script>
<List ref:list/>
<List bind:this={list}/>

@ -6,7 +6,7 @@
export let letters = ['a', 'b', 'c'];
</script>
<Modal ref:modal>
<Modal bind:this={modal}>
<span>{letter}</span>
<select bind:value={letter}>
{#each letters as letter}

@ -4,4 +4,4 @@
export let one;
</script>
<One ref:one/>
<One bind:this={one}/>

@ -8,5 +8,5 @@
export let baz;
</script>
<Foo ref:foo bind:bar bind:baz/>
<p ref:p>{bar + baz}</p>
<Foo bind:this={foo} bind:bar bind:baz/>
<p bind:this={p}>{bar + baz}</p>

@ -4,4 +4,4 @@
export let l1;
</script>
<Level1 ref:l1 />
<Level1 bind:this={l1} />

@ -4,5 +4,5 @@
</script>
<div>
<Widget ref:widget/>
<Widget bind:this={widget}/>
</div>

@ -4,6 +4,6 @@
export let nested;
</script>
<Nested ref:nested>
<Nested bind:this={nested}>
<p>override default slot</p>
</Nested>

@ -6,5 +6,5 @@
</script>
<div>
<Widget ref:widget>{data}</Widget>
<Widget bind:this={widget}>{data}</Widget>
</div>

@ -7,7 +7,7 @@
{#each components as component}
<li>
{#if component.edit}
<input ref:name bind:value={component.name} />
<input bind:this={name} bind:value={component.name} />
{:else}
{component.name}
{/if}

@ -4,4 +4,4 @@
export let test;
</script>
<svelte:component this={Foo} ref:test/>
<svelte:component this={Foo} bind:this={test}/>

@ -5,5 +5,5 @@
</script>
{#if visible}
<input ref:input on:blur='{() => blurred = true}'>
<input bind:this={input} on:blur='{() => blurred = true}'>
{/if}

@ -5,5 +5,5 @@
</script>
<div>
<Nested ref:nested />
<Nested bind:this={nested} />
</div>

@ -4,4 +4,4 @@
import Folder from './Folder.html';
</script>
<Folder ref:folder dir="a"/>
<Folder bind:this={folder} dir="a"/>

@ -9,4 +9,4 @@
});
</script>
<div ref:element></div>
<div bind:this={element}></div>

@ -6,5 +6,5 @@
</script>
{#if visible}
<Top ref:top></Top>
<Top bind:this={top}></Top>
{/if}

@ -10,4 +10,4 @@
});
</script>
<p ref:x>{inDocument}</p>
<p bind:this={x}>{inDocument}</p>

@ -10,4 +10,4 @@
});
</script>
<p ref:x>{inDocument}</p>
<p bind:this={x}>{inDocument}</p>

@ -2,4 +2,4 @@
export let foo;
</script>
<div><canvas ref:foo></canvas></div>
<div><canvas bind:this={foo}></canvas></div>

@ -4,7 +4,7 @@
</script>
{#if x}
<canvas ref:foo data-x='true'></canvas>
<canvas bind:this={foo} data-x='true'></canvas>
{:else}
<canvas ref:foo data-x='false'></canvas>
<canvas bind:this={foo} data-x='false'></canvas>
{/if}

@ -2,4 +2,4 @@
export let foo;
</script>
<canvas ref:foo></canvas>
<canvas bind:this={foo}></canvas>

@ -13,5 +13,5 @@
</script>
{#if visible}
<div out:fade style="opacity: 1;" ref:div>yes</div>
<div out:fade style="opacity: 1;" bind:this={div}>yes</div>
{/if}

@ -16,7 +16,7 @@
</script>
{#if x}
<div ref:yes out:foo>{z}</div>
<div bind:this={yes} out:foo>{z}</div>
{:else}
<div ref:no out:foo>{z}</div>
<div bind:this={no} out:foo>{z}</div>
{/if}

@ -15,7 +15,7 @@
</script>
{#if x}
<div ref:yes in:foo>yes</div>
<div bind:this={yes} in:foo>yes</div>
{:else}
<div ref:no in:foo>no</div>
<div bind:this={no} in:foo>no</div>
{/if}

@ -15,7 +15,7 @@
</script>
{#if x}
<div ref:yes out:foo>yes</div>
<div bind:this={yes} out:foo>yes</div>
{:else}
<div ref:no out:foo>no</div>
<div bind:this={no} out:foo>no</div>
{/if}

@ -16,7 +16,7 @@
</script>
{#if x}
<div ref:yes out:foo>yes</div>
<div bind:this={yes} out:foo>yes</div>
{:elseif y}
<div ref:no out:foo>no</div>
<div bind:this={no} out:foo>no</div>
{/if}

@ -6,4 +6,4 @@
export let foo = 42;
</script>
<div><Widget ref:widget foo='{foo}'/></div>
<div><Widget bind:this={widget} foo='{foo}'/></div>

@ -4,4 +4,4 @@
import Widget from './Widget.html';
</script>
<div><Widget ref:widget/></div>
<div><Widget bind:this={widget}/></div>

@ -0,0 +1,15 @@
[{
"code": "invalid-ref-selector",
"message": "ref selectors are no longer supported",
"pos": 68,
"start": {
"line": 8,
"column": 1,
"character": 68
},
"end": {
"line": 8,
"column": 1,
"character": 68
}
}]

@ -0,0 +1,11 @@
<script>
let foo;
</script>
<div bind:this={foo}></div>
<style>
ref:foo {
color: red;
}
</style>

@ -0,0 +1,15 @@
[{
"code": "invalid-ref-directive",
"message": "The ref directive is no longer supported — use `bind:this={foo}` instead",
"pos": 5,
"start": {
"line": 1,
"column": 5,
"character": 5
},
"end": {
"line": 1,
"column": 5,
"character": 5
}
}]

@ -1,15 +0,0 @@
[{
"message": "Reference name 'foo-bar' is invalid — must be a valid identifier such as foo_bar",
"start": {
"line": 1,
"column": 5,
"character": 5
},
"end": {
"line": 1,
"column": 16,
"character": 16
},
"pos": 5,
"code": "invalid-reference-name"
}]

@ -1,8 +0,0 @@
<div ref:foo-bar>
</div>
<style>
ref:foo-bar {
display: flex;
}
</style>
Loading…
Cancel
Save