diff --git a/packages/@glimmer/debug/lib/opcode-metadata.ts b/packages/@glimmer/debug/lib/opcode-metadata.ts
index 44252a4c78..3e11fd1d15 100644
--- a/packages/@glimmer/debug/lib/opcode-metadata.ts
+++ b/packages/@glimmer/debug/lib/opcode-metadata.ts
@@ -549,7 +549,7 @@ METADATA[Op.PushRemoteElement] = {
name: 'PushRemoteElement',
mnemonic: 'apnd_remotetag',
before: null,
- stackChange: -3,
+ stackChange: -4,
ops: [],
operands: 0,
check: true,
diff --git a/packages/@glimmer/integration-tests/lib/suites/in-element.ts b/packages/@glimmer/integration-tests/lib/suites/in-element.ts
index d38119ff1a..27a31e0b7d 100644
--- a/packages/@glimmer/integration-tests/lib/suites/in-element.ts
+++ b/packages/@glimmer/integration-tests/lib/suites/in-element.ts
@@ -79,10 +79,13 @@ export class InElementSuite extends RenderTest {
let initialContent = '
Hello there!
';
replaceHTML(externalElement, initialContent);
- this.render(stripTight`{{#in-element externalElement}}[{{foo}}]{{/in-element}}`, {
- externalElement,
- foo: 'Yippie!',
- });
+ this.render(
+ stripTight`{{#in-element externalElement insertBefore=null}}[{{foo}}]{{/in-element}}`,
+ {
+ externalElement,
+ foo: 'Yippie!',
+ }
+ );
equalsElement(externalElement, 'div', {}, `${initialContent}[Yippie!]`);
this.assertHTML('');
@@ -107,13 +110,47 @@ export class InElementSuite extends RenderTest {
@test
'With nextSibling'() {
let externalElement = this.delegate.createElement('div');
- replaceHTML(externalElement, 'Hellothere!');
this.render(
stripTight`{{#in-element externalElement nextSibling=nextSibling}}[{{foo}}]{{/in-element}}`,
{ externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' }
);
+ equalsElement(externalElement, 'div', {}, '[Yippie!]');
+ this.assertHTML('');
+ this.assertStableRerender();
+
+ this.rerender({ foo: 'Double Yips!' });
+ equalsElement(externalElement, 'div', {}, '[Double Yips!]');
+ this.assertHTML('');
+ this.assertStableNodes();
+
+ this.rerender({ nextSibling: null });
+ equalsElement(externalElement, 'div', {}, '[Double Yips!]');
+ this.assertHTML('');
+ this.assertStableRerender();
+
+ this.rerender({ externalElement: null });
+ equalsElement(externalElement, 'div', {}, '');
+ this.assertHTML('');
+ this.assertStableRerender();
+
+ this.rerender({ externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' });
+ equalsElement(externalElement, 'div', {}, '[Yippie!]');
+ this.assertHTML('');
+ this.assertStableRerender();
+ }
+
+ @test
+ 'With nextSibling (retain content)'() {
+ let externalElement = this.delegate.createElement('div');
+ replaceHTML(externalElement, 'Hellothere!');
+
+ this.render(
+ stripTight`{{#in-element externalElement nextSibling=nextSibling insertBefore=null}}[{{foo}}]{{/in-element}}`,
+ { externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' }
+ );
+
equalsElement(externalElement, 'div', {}, 'Hello[Yippie!]there!');
this.assertHTML('');
this.assertStableRerender();
diff --git a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts
index 69f70986e4..ffa6a56747 100644
--- a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts
+++ b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts
@@ -40,7 +40,8 @@ export interface DOMStack {
pushRemoteElement(
element: SimpleElement,
guid: string,
- nextSibling: Option
+ nextSibling: Option,
+ insertBefore: Option
): Option;
popRemoteElement(): void;
popElement(): void;
diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts
index 9e83bc8488..1b93827b6e 100644
--- a/packages/@glimmer/node/lib/serialize-builder.ts
+++ b/packages/@glimmer/node/lib/serialize-builder.ts
@@ -96,13 +96,14 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder {
pushRemoteElement(
element: SimpleElement,
cursorId: string,
- nextSibling: Option = null
+ nextSibling: Option = null,
+ _insertBefore: Option
): Option {
let { dom } = this;
let script = dom.createElement('script');
script.setAttribute('glmr', cursorId);
dom.insertBefore(element, script, nextSibling);
- return super.pushRemoteElement(element, cursorId, nextSibling);
+ return super.pushRemoteElement(element, cursorId, nextSibling, null);
}
}
diff --git a/packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts b/packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts
index c90ef1dd46..3c1fb4fd4b 100644
--- a/packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts
+++ b/packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts
@@ -173,7 +173,7 @@ export function populateBuiltins(
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
- if (key === 'nextSibling' || key === 'guid') {
+ if (key === 'nextSibling' || key === 'guid' || key === 'insertBefore') {
actions.push(op('Expr', values[i]));
} else {
throw new Error(`SYNTAX ERROR: #in-element does not take a \`${keys[0]}\` option`);
@@ -182,7 +182,7 @@ export function populateBuiltins(
actions.push(op('Expr', params[0]), op(Op.Dup, $sp, 0));
- return { count: 4, actions };
+ return { count: 5, actions };
},
ifTrue() {
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
index 944cdd40b9..23d88fa6b3 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
@@ -43,6 +43,7 @@ APPEND_OPCODES.add(Op.OpenDynamicElement, vm => {
APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
let elementRef = check(vm.stack.pop(), CheckReference);
+ let insertBefore = check(vm.stack.pop(), CheckReference);
let nextSiblingRef = check(vm.stack.pop(), CheckReference);
let guidRef = check(vm.stack.pop(), CheckReference);
@@ -66,7 +67,9 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
vm.updateWith(new Assert(cache));
}
- let block = vm.elements().pushRemoteElement(element, guid, nextSibling);
+ let shouldClear = insertBefore.value();
+
+ let block = vm.elements().pushRemoteElement(element, guid, nextSibling, shouldClear);
if (block) vm.associateDestroyable(block);
});
diff --git a/packages/@glimmer/runtime/lib/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts
index 66bd2fb37d..d2080e6b3c 100644
--- a/packages/@glimmer/runtime/lib/vm/element-builder.ts
+++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts
@@ -205,18 +205,28 @@ export class NewElementBuilder implements ElementBuilder {
pushRemoteElement(
element: SimpleElement,
guid: string,
- nextSibling: Option = null
+ nextSibling: Option = null,
+ insertBefore: Option
): Option {
- return this.__pushRemoteElement(element, guid, nextSibling);
+ return this.__pushRemoteElement(element, guid, nextSibling, insertBefore);
}
__pushRemoteElement(
element: SimpleElement,
_guid: string,
- nextSibling: Option
+ nextSibling: Option,
+ insertBefore: Option
): Option {
this.pushElement(element, nextSibling);
+
+ if (insertBefore !== null) {
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ }
+
let block = new RemoteLiveBlock(element);
+
return this.pushLiveBlock(block, true);
}
diff --git a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts
index d6634e8325..a6d2da567f 100644
--- a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts
+++ b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts
@@ -379,11 +379,18 @@ export class RehydrateBuilder extends NewElementBuilder implements ElementBuilde
__pushRemoteElement(
element: SimpleElement,
cursorId: string,
- nextSibling: Option = null
+ nextSibling: Option = null,
+ insertBefore: Option
): Option {
let marker = this.getMarker(element as HTMLElement, cursorId);
if (marker.parentNode === element) {
+ if (insertBefore !== null) {
+ while (element.lastChild !== marker) {
+ element.removeChild(element.lastChild!);
+ }
+ }
+
let currentCursor = this.currentCursor;
let candidate = currentCursor!.candidate;
diff --git a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts
index 029b7ae9eb..44849cfc93 100644
--- a/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts
+++ b/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts
@@ -442,11 +442,20 @@ function addElementModifier(element: Tag<'StartTag'>, mustache: AST.MustacheStat
function addInElementHash(cursor: string, hash: AST.Hash, loc: AST.SourceLocation) {
let hasNextSibling = false;
+ let hasInsertBefore = false;
hash.pairs.forEach(pair => {
if (pair.key === 'guid') {
throw new SyntaxError('Cannot pass `guid` from user space', loc);
}
+ if (pair.key === 'insertBefore') {
+ if (pair.value.type !== 'NullLiteral') {
+ throw new SyntaxError('insertBefore only takes `null` as an argument', loc);
+ }
+
+ hasInsertBefore = true;
+ }
+
if (pair.key === 'nextSibling') {
hasNextSibling = true;
}
@@ -462,6 +471,12 @@ function addInElementHash(cursor: string, hash: AST.Hash, loc: AST.SourceLocatio
hash.pairs.push(nextSibling);
}
+ if (!hasInsertBefore) {
+ let undefinedLiteral = b.literal('UndefinedLiteral', undefined);
+ let beforeSibling = b.pair('insertBefore', undefinedLiteral);
+ hash.pairs.push(beforeSibling);
+ }
+
return hash;
}