Skip to content

Commit

Permalink
Issue #10: WIP: Consolidating 'data-svelte-retag-dom' into 'data-svel…
Browse files Browse the repository at this point in the history
…te-retag-render', as it's only necessary for render. Cleans final rendered HTML (leaves no extra attribs behind).
  • Loading branch information
patricknelson committed Sep 30, 2023
1 parent b293b5c commit 72a3503
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 47 deletions.
10 changes: 5 additions & 5 deletions demo/src/lib/TabsDemo.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script>
import { TabsWrapper, TabList, TabPanel, TabButton } from './tabs';
import Counter from './Counter.svelte';
</script>

<TabsWrapper>
Expand All @@ -14,28 +15,27 @@
<TabList>
<TabButton>nested one</TabButton>
<TabButton>nested two</TabButton>
<TabButton>nested three</TabButton>
</TabList>

<TabPanel>
<h2>First nested panel</h2>
<Counter count="1" award="5"/>
</TabPanel>

<TabPanel>
<h2>Second nested panel</h2>
</TabPanel>

<TabPanel>
<h2>Third nested panel</h2>
<Counter count="2" award="5"/>
</TabPanel>
</TabsWrapper>
</TabPanel>

<TabPanel>
<h2>Second panel</h2>
<Counter count="3" award="5"/>
</TabPanel>

<TabPanel>
<h2>Third panel</h2>
<Counter count="4" award="5"/>
</TabPanel>
</TabsWrapper>
17 changes: 9 additions & 8 deletions demo/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,34 @@ svelteRetag({
href: shadowStylesheet,
});


svelteRetag({
component: TabsWrapper,
tagname: 'tabs-wrapper',
component: TabPanel,
tagname: 'tab-panel',
debugMode,
shadow,
href: shadowStylesheet,
});

svelteRetag({
component: TabList,
tagname: 'tab-list',
component: TabButton,
tagname: 'tab-button',
debugMode,
shadow,
href: shadowStylesheet,
});

svelteRetag({
component: TabPanel,
tagname: 'tab-panel',
component: TabsWrapper,
tagname: 'tabs-wrapper',
debugMode,
shadow,
href: shadowStylesheet,
});

svelteRetag({
component: TabButton,
tagname: 'tab-button',
component: TabList,
tagname: 'tab-list',
debugMode,
shadow,
href: shadowStylesheet,
Expand Down
8 changes: 3 additions & 5 deletions demo/tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@

<div id="app">
<h1>Tabs demo</h1>
<!-- TODO: Write up explanation of what is happening below. -->
<p>Thanks to
<p>Credit to
<a href="https://stackoverflow.com/users/1990514/amir-pournasserian" target="_blank" rel="noopener">AmirPournasserian</a>
from
<a href="https://stackoverflow.com/questions/75024281/how-to-avoid-overwriting-context-in-nested-components-like-tabs-in-svelte" target="_blank" rel="noopener">
this StackOverflow post</a> for the tab components which rely on this context.</p>

<!-- TODO: All contents below are a WIP during debugging -->
this StackOverflow post</a> for the tab components.</p>

<!-- Fully working demo but rendered entirely as one svelte component -->
<!--<tabs-demo></tabs-demo>-->

<!-- Top level context -->
Expand Down
65 changes: 36 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,70 @@ import { createSvelteSlots, findSlotParent, unwrap } from './utils.js';

// TODO: Consider build of svelte-retag so we can drop console.logs() when publishing. See: https://github.com/vitejs/vite/discussions/7920


let rafRunning = false;

function queueForRender(element) {
/**
* TODO: ISSUE-10: Doc
*
* @param {HTMLElement} element
* @param {Boolean} isShadow
*/
function queueForRender(element, isShadow) {
// Skip the queue if a parent is already queued for render, but for the light DOM only. This is because if it's in the
// light DOM slot, it will be disconnected and reconnected again (which will then also trigger a need to render).
if (element.parentElement.closest('[data-svelte-retag-render][data-svelte-retag-dom="light"]') !== null) {
if (element.parentElement.closest('[data-svelte-retag-render="light"]') !== null) {
console.debug('queueForRender(): skipped since a light DOM parent is queued for render:', element);
return;
}

console.debug('queueForRender(): queued for:', element);
element.setAttribute('data-svelte-retag-render', '');
// When queuing for render, it's also necessary to identify the DOM rendering type. This is necessary for child
// components which are *underneath* a parent that is using light DOM rendering (see above). This helps to ensure
// rendering is performed in the correct order (useful for things like context).
element.setAttribute('data-svelte-retag-render', isShadow ? 'shadow' : 'light');
requestAnimationFrame(renderElements);
}


/**
* TODO: ISSUE-10: Doc
*
* @param {number} timestamp
*/
function renderElements(timestamp) {
// Minor Optimization: Reduces quantity of unnecessary querySelectorAll() hits.
// TODO: To be honest though, still not 100% sure why this is ever true. Per my understanding of rAF, they are queued
// to run sequentially, even if on the same frame/timestamp.
if (rafRunning) {
console.warn(`renderElements(${timestamp}): Skipping: Already rendering`);
console.debug(`renderElements(${timestamp}): Skipping: Already rendering`);
return;
}
rafRunning = true;

console.debug(`raF: renderElements(${timestamp})`);

let renderQueue = document.querySelectorAll('[data-svelte-retag-render]');
if (renderQueue.length === 0) {
console.debug(`renderElements(${timestamp}): returned, queue is now empty`);
return;
}


// For each element, double check and skip any which have *light* DOM parents that are queued for render. The reason
// for this is that they will be disconnected and queued for render later anyway.
for (let element of renderQueue) {
let renderTotal = 0;
for(let element of renderQueue) {
// Element was queued but likely rearranged due to the parent rendering first (resulting in a new instance and this
// being forever orphaned).
if (!element.isConnected) {
console.debug(`renderElements(${timestamp}): skipped, no longer connected:`, element);
continue;
}

if (element.parentElement.closest('[data-svelte-retag-render][data-svelte-retag-dom="light"]') === null) {
// Quick double check: Skip any which have *light* DOM parents that are queued for render. See queueForRender() for details.
if (element.parentElement.closest('[data-svelte-retag-render="light"]') === null) {
element.removeAttribute('data-svelte-retag-render');
element._renderSvelteComponent();
renderTotal++; // For debug only.
} else {
console.debug(`renderElements(${timestamp}): skipped since a light DOM parent is queued for render:`, element);
}
}
console.debug(`renderElements(${timestamp}): rendered ${renderTotal} elements`);

rafRunning = false;
}
Expand Down Expand Up @@ -170,12 +182,6 @@ export default function(opts) {
connectedCallback() {
this._debug('connectedCallback()');

// Identifies DOM rendering type to help differentiate during deferred rendering. This is necessary particularly
// for any type of child components which are *underneath* a parent that is using light DOM rendering. This helps
// to ensure rendering is performed in the correct order (useful for things like context).
// TODO: Ok to just consolidate this into data-svelte-retag-render (and/or refactor to data-svelte-retag-render-dom)?
this.setAttribute('data-svelte-retag-dom', opts.shadow ? 'shadow' : 'light');

/**
* TODO: Light DOM: Potential optimization opportunities:
* 1. Don't bother setting up <svelte-retag> wrapper if the component doesn't have a default slot and isn't hydratable
Expand Down Expand Up @@ -255,8 +261,9 @@ export default function(opts) {
disconnectedCallback() {
this._debug('disconnectedCallback()');

// Remove DOM type flag. TODO: Potentially consolidate to the render flag instead, see connectedCallback().
this.removeAttribute('data-svelte-retag-dom');
// Remove render flag (if present). This could happen in case the element is disconnected while waiting to render
// (particularly if slotted under a light DOM parent).
this.removeAttribute('data-svelte-retag-render');

// Remove hydration flag, if present. This component will be able to be rendered from scratch instead.
this.removeAttribute('data-svelte-retag-hydratable');
Expand Down Expand Up @@ -355,9 +362,9 @@ export default function(opts) {
/**
* TODO: ISSUE-10: Doc
*/
_getAncestorContext() {
_getAncestorContext() {
let node = this;
while (node.parentNode) {
while(node.parentNode) {
node = node.parentNode;
let context = node?.componentInstance?.$$?.context;
if (context) {
Expand All @@ -375,7 +382,7 @@ export default function(opts) {
* TODO: ISSUE-10: DOC
*/
_queueForRender() {
queueForRender(this);
queueForRender(this, opts.shadow);
}

/**
Expand Down Expand Up @@ -484,7 +491,7 @@ export default function(opts) {
* NAMED SLOTS *
***************/

// Look for named slots below this element. IMPORTANT: This may return slots nested deeper (see check in forEach below).
// Look for named slots below this element. IMPORTANT: This may return slots nested deeper (see check in forEach below).
const queryNamedSlots = this.querySelectorAll('[slot]');
for(let candidate of queryNamedSlots) {
// Skip this slot if it doesn't happen to belong to THIS custom element.
Expand All @@ -511,10 +518,10 @@ export default function(opts) {
* DEFAULT SLOT (UNNAMED) *
**************************/

// "Unwrap" the remainder of this tag by iterating through child nodes and placing them into a fragment which
// we can use as our default slot. Importantly, we need to ensure we skip our special <svelte-retag> wrapper.
// Here we use a special <svelte-retag-default> custom element that allows us to target it later in case we
// need to hydrate it (e.g. tag was rendered via SSG/SSR and disconnectedCallback() was not run).
// "Unwrap" the remainder of this tag by iterating through child nodes and placing them into a fragment which
// we can use as our default slot. Importantly, we need to ensure we skip our special <svelte-retag> wrapper.
// Here we use a special <svelte-retag-default> custom element that allows us to target it later in case we
// need to hydrate it (e.g. tag was rendered via SSG/SSR and disconnectedCallback() was not run).
let fragment = document.createDocumentFragment();

// For hydratable components, we have to nest these nodes under a tag that we can still recognize once
Expand Down

0 comments on commit 72a3503

Please sign in to comment.