Skip to content

Allow transitions to be local #1734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed

Allow transitions to be local #1734

wants to merge 2 commits into from

Conversation

jacwright
Copy link
Contributor

@jacwright jacwright commented Sep 12, 2018

Allows transitions to only run locally, skipping running when they are added to the DOM or removed from it.

Example:

{#if visible}
  {#each things as thing}
    <div transition:fade|local></div>
  {/each}
{/if}

<script>
export default {
  transitions: {
    fade(node, params) {
      return {
        duration: 400,
        css: t => {
          return `opacity: ${t}`;
        }
      };
    }
  }
};
</script>

In this example items in the each-block will fade in and fade out when they are added to or removed from things but the list will appear suddenly and disappear suddenly (no fade) when visible is toggled to true or false.

This is another try at #1480.

Allows transitions to only run locally, skipping running when they are added to the DOM or removed from it.

Example:

```html
{#if visible}
{#each things as thing}
<div transition:fade|local></div>
{/each}
{/if}

<script>
export default {
  transitions: {
    fade(node, params) {
      return {
        duration: 400,
        css: t => {
          return `opacity: ${t}`;
        }
      };
    }
  }
};
</script>
```

In this example items in the each-block will fade in and fade out when they are added to or removed from `things` but the list will appear suddenly and disappear suddenly (no fade) when `visible` is toggled to `true` or `false`.

This is another try at #1480.
@Rich-Harris
Copy link
Member

Thanks for this. I think using pipes is a smart move.

I think we can make the generated code simpler. We can avoid creating transitions at all when they're local transitions responding to non-local changes; no need to invoke wrapTransitions but with an argument that says 'don't do anything'.

So in a case like this...

{#if x}
  {#if y}
    <div transition:foo|local>foo</div>
  {/if}
{/if}

...we could generate code like this (diff from this PR):

function create_main_fragment(component, ctx) {
-  var if_block_anchor, current;
+  var if_block_anchor;

  var if_block = (ctx.x) && create_if_block(component, ctx);

  return {
    c() {
      if (if_block) if_block.c();
      if_block_anchor = createComment();
    },

-    m(target, anchor, introing) {
-      if (if_block) if_block.i(target, anchor, 1);
+    m(target, anchor) {
+      if (if_block) if_block.i(target, anchor);
      insert(target, if_block_anchor, anchor);
-      current = true;
    },

    p(changed, ctx) {
      if (ctx.x) {
        if (if_block) {
          if_block.p(changed, ctx);
        } else {
          if_block = create_if_block(component, ctx);
          if (if_block) if_block.c();
        }

-        if_block.i(if_block_anchor.parentNode, if_block_anchor);
+        if_block.m(if_block_anchor.parentNode, if_block_anchor);
      } else if (if_block) {
-        groupOutros();
-        if_block.o(function() {
-          if_block.d(1);
-          if_block = null;
-        });
+        if_block.d(1);
+        if_block = null;
      }
    },
-
-    i(target, anchor, introing) {
-      if (current) return;
-
-      this.m(target, anchor, introing);
-    },
-
-    o(outrocallback, outroing) {
-      if (!current) return;
-
-      if (if_block) if_block.o(outrocallback, 1);
-      else outrocallback();
-
-      current = false;
-    },

    d(detach) {
      if (if_block) if_block.d(detach);
      if (detach) {
        detachNode(if_block_anchor);
      }
    }
  };
}

// (2:1) {#if y}
function create_if_block_1(component, ctx) {
  var div, div_transition, current;

  return {
    c() {
      div = createElement("div");
      div.textContent = "foo";
    },

-    m(target, anchor, introing) {
+    m(target, anchor) {
      insert(target, div, anchor);
      current = true;
    },

-    i(target, anchor, introing) {
+    i(target, anchor) {
      if (current) return;
      if (component.root._intro) {
        if (div_transition) div_transition.invalidate();

        component.root._aftercreate.push(() => {
-          if (!div_transition) div_transition = wrapTransition(component, div, foo, {}, 1, 1);
-          div_transition.run(1, null, introing);
+          if (!div_transition) div_transition = wrapTransition(component, div, foo, {}, 1);
+          div_transition.run(1, null);
        });
      }
-      this.m(target, anchor, introing);
+      this.m(target, anchor);
    },

-    o(outrocallback, outroing) {
+    o(outrocallback) {
      if (!current) return;

-      if (!div_transition) div_transition = wrapTransition(component, div, foo, {}, 0, 1);
+      if (!div_transition) div_transition = wrapTransition(component, div, foo, {}, 0);
      div_transition.run(0, () => {
        outrocallback();
        div_transition = null;
-      }, outroing);
+      });

      current = false;
    },

    d(detach) {
      if (detach) {
        detachNode(div);
        if (div_transition) div_transition.abort();
      }
    }
  };
}

// (1:0) {#if x}
function create_if_block(component, ctx) {
-  var if_block_anchor, current;
+  var if_block_anchor;

  var if_block = (ctx.y) && create_if_block_1(component, ctx);

  return {
    c() {
      if (if_block) if_block.c();
      if_block_anchor = createComment();
    },

-    m(target, anchor, introing) {
-      if (if_block) if_block.i(target, anchor, 1);
+    m(target, anchor) {
+      if (if_block) if_block.i(target, anchor);
      insert(target, if_block_anchor, anchor);
-      current = true;
    },

    p(changed, ctx) {
      if (ctx.y) {
        if (!if_block) {
          if_block = create_if_block_1(component, ctx);
          if_block.c();
        }
        if_block.i(if_block_anchor.parentNode, if_block_anchor);
      } else if (if_block) {
-        groupOutros();
-        if_block.o(function() {
-          if_block.d(1);
-          if_block = null;
-        });
+        if_block.d(1);
+        if_block = null;
      }
    },
-
-    i(target, anchor, introing) {
-      if (current) return;
-
-      this.m(target, anchor, introing);
-    },
-
-    o(outrocallback, outroing) {
-      if (!current) return;
-
-      if (if_block) if_block.o(outrocallback, 1);
-      else outrocallback();
-
-      current = false;
-    },

    d(detach) {
      if (if_block) if_block.d(detach);
      if (detach) {
        detachNode(if_block_anchor);
      }
    }
  };
}

function SvelteComponent(options) {
  init(this, options);
  this._state = assign({}, options.data);
  this._intro = !!options.intro;

  this._fragment = create_main_fragment(this, this._state);

  if (options.target) {
    this._fragment.c();
-    this._mount(options.target, options.anchor, 1);
+    this._mount(options.target, options.anchor);

    flush(this);
  }

  this._intro = true;
}

As far as I can see there's only two situations where you need to track the locality of a transition at runtime — when you're crossing a component boundary...

{#if x}
  {#if y}
    <!-- because there's a component here, we need to track whether `y` changing
         is the reason for the component's creation/destruction, in case it has a
         local transition at the top-level -->
    <Widget/>
  {/if}
{/if}

...or when you have global transitions (or components, since they could contain global transitions) at the same level or deeper than a local transition in the same subtree:

{#if x}
  {#if y}
    <!-- we need to generate outro code for the `x` block, but ensure
         that it doesn't cause the first div's transition to run -->
    <div transition:foo|local>foo</div>
    <div transition:foo>foo</div>
  {/if}
{/if}

I made a start on this in a local branch but the further into it I get the more I think it makes sense to land #1721 first, so I'm going to spend some time on that first.

@Rich-Harris
Copy link
Member

Closing this in favour of #2008, as transitions have undergone a number of changes for v3

@Conduitry Conduitry deleted the gh-1480-alt2 branch April 29, 2019 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants