Skip to content

Commit

Permalink
fix(renderer): fix missing slot ref callback handling (#5337)
Browse files Browse the repository at this point in the history
* fix(renderer): fix missing slot ref callback handling

fixes one of the issues raised in #5335

adds a new internal variable which is stored during vnode rendering to reuse it within the relocatedNodes logic

* test(slot): add missing e2e tests for slot ref handling

* test(slot): add slotted element ref correctness to test case

* style(slot): fix linter issue for the e2e test case
  • Loading branch information
yigityuce committed Mar 12, 2024
1 parent b6b9617 commit 41f877e
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,12 @@ export interface RenderNode extends HostElement {
*/
host?: Element;

/**
* On Ref Function:
* Callback function to be called when the slotted node ref is ready.
*/
['s-rf']?: (elm: Element) => unknown;

/**
* Is initially hidden
* Whether this node was originally rendered with the `hidden` attribute.
Expand Down
1 change: 1 addition & 0 deletions src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const patchCloneNode = (HostElementPrototype: HTMLElement) => {
's-ol',
's-nr',
's-si',
's-rf',
];

for (; i < srcNode.childNodes.length; i++) {
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ const createElm = (oldParentVNode: d.VNode, newParentVNode: d.VNode, childIndex:
// remember the slot name, or empty string for default slot
elm['s-sn'] = newVNode.$name$ || '';

// remember the ref callback function
elm['s-rf'] = newVNode.$attrs$?.ref;

// check if we've got an old vnode for this slot
oldVNode = oldParentVNode && oldParentVNode.$children$ && oldParentVNode.$children$[childIndex];
if (oldVNode && oldVNode.$tag$ === newVNode.$tag$ && oldParentVNode.$elm$) {
Expand Down Expand Up @@ -1094,6 +1097,8 @@ render() {
}
}
}

nodeToRelocate && typeof slotRefNode['s-rf'] === 'function' && slotRefNode['s-rf'](nodeToRelocate);
} else {
// this node doesn't have a slot home to go to, so let's hide it
if (nodeToRelocate.nodeType === NODE_TYPE.ElementNode) {
Expand Down
13 changes: 13 additions & 0 deletions test/karma/test-app/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ export namespace Components {
interface SlotParentTagChangeRoot {
"element": string;
}
interface SlotRef {
}
interface SlotReorder {
"reordered": boolean;
}
Expand Down Expand Up @@ -1373,6 +1375,12 @@ declare global {
prototype: HTMLSlotParentTagChangeRootElement;
new (): HTMLSlotParentTagChangeRootElement;
};
interface HTMLSlotRefElement extends Components.SlotRef, HTMLStencilElement {
}
var HTMLSlotRefElement: {
prototype: HTMLSlotRefElement;
new (): HTMLSlotRefElement;
};
interface HTMLSlotReorderElement extends Components.SlotReorder, HTMLStencilElement {
}
var HTMLSlotReorderElement: {
Expand Down Expand Up @@ -1622,6 +1630,7 @@ declare global {
"slot-no-default": HTMLSlotNoDefaultElement;
"slot-parent-tag-change": HTMLSlotParentTagChangeElement;
"slot-parent-tag-change-root": HTMLSlotParentTagChangeRootElement;
"slot-ref": HTMLSlotRefElement;
"slot-reorder": HTMLSlotReorderElement;
"slot-reorder-root": HTMLSlotReorderRootElement;
"slot-replace-wrapper": HTMLSlotReplaceWrapperElement;
Expand Down Expand Up @@ -2008,6 +2017,8 @@ declare namespace LocalJSX {
interface SlotParentTagChangeRoot {
"element"?: string;
}
interface SlotRef {
}
interface SlotReorder {
"reordered"?: boolean;
}
Expand Down Expand Up @@ -2187,6 +2198,7 @@ declare namespace LocalJSX {
"slot-no-default": SlotNoDefault;
"slot-parent-tag-change": SlotParentTagChange;
"slot-parent-tag-change-root": SlotParentTagChangeRoot;
"slot-ref": SlotRef;
"slot-reorder": SlotReorder;
"slot-reorder-root": SlotReorderRoot;
"slot-replace-wrapper": SlotReplaceWrapper;
Expand Down Expand Up @@ -2351,6 +2363,7 @@ declare module "@stencil/core" {
"slot-no-default": LocalJSX.SlotNoDefault & JSXBase.HTMLAttributes<HTMLSlotNoDefaultElement>;
"slot-parent-tag-change": LocalJSX.SlotParentTagChange & JSXBase.HTMLAttributes<HTMLSlotParentTagChangeElement>;
"slot-parent-tag-change-root": LocalJSX.SlotParentTagChangeRoot & JSXBase.HTMLAttributes<HTMLSlotParentTagChangeRootElement>;
"slot-ref": LocalJSX.SlotRef & JSXBase.HTMLAttributes<HTMLSlotRefElement>;
"slot-reorder": LocalJSX.SlotReorder & JSXBase.HTMLAttributes<HTMLSlotReorderElement>;
"slot-reorder-root": LocalJSX.SlotReorderRoot & JSXBase.HTMLAttributes<HTMLSlotReorderRootElement>;
"slot-replace-wrapper": LocalJSX.SlotReplaceWrapper & JSXBase.HTMLAttributes<HTMLSlotReplaceWrapperElement>;
Expand Down
24 changes: 24 additions & 0 deletions test/karma/test-app/slot-ref/cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, Element, h, Host } from '@stencil/core';

@Component({
tag: 'slot-ref',
shadow: false,
scoped: true,
})
export class SlotRef {
@Element() hostElement: HTMLElement;

render() {
return (
<Host>
<slot
name="title"
ref={(el) => {
this.hostElement.setAttribute('data-ref-id', el.id);
this.hostElement.setAttribute('data-ref-tagname', el.tagName);
}}
/>
</Host>
);
}
}
6 changes: 6 additions & 0 deletions test/karma/test-app/slot-ref/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="./build/testapp.esm.js" type="module"></script>
<script src="./build/testapp.js" nomodule></script>

<slot-ref><span slot="title" id="slotted-element-id">Hello World!</span></slot-ref>
19 changes: 19 additions & 0 deletions test/karma/test-app/slot-ref/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setupDomTests, waitForChanges } from '../util';

describe('slot-ref', function () {
const { setupDom, tearDownDom } = setupDomTests(document);
let app: HTMLElement;

beforeEach(async () => {
app = await setupDom('/slot-ref/index.html');
});
afterEach(tearDownDom);

it('ref callback of slot is called', async () => {
await waitForChanges();

const host = app.querySelector('slot-ref');
expect(host.getAttribute('data-ref-id')).toBe('slotted-element-id');
expect(host.getAttribute('data-ref-tagname')).toBe('SPAN');
});
});

0 comments on commit 41f877e

Please sign in to comment.