npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vue-slot-spread

v1.1.0

Published

a Vue.js mixin to spread components in slots

Downloads

7

Readme

vue-slot-spread

what

a handy Vue.js mixin to spread components in slots

TL;DR

  1. npm i -s vue-slot-spread
  2. import spread from 'vue-slot-spread'
  3. mixins: [ spread({ 'tag-name': 'slot-name' }) ]

why

Consider the following use-case :

You're doing a Card.vue component with multiple slots, such as header, body, and footer.

All of its slots should be :

  • [ ] customizable from userland with classes and attributes
  • [ ] totally optional
  • [ ] enforce minimal implementation (element type, and attributes).

1. A first implementation

<template>
  <div class="card">
    <div class="header">
      <slot name="header" />
    </div>

    <div class="body">
      <slot name="body" />
    </div>

    <div class="footer">
      <slot name="footer" />
    </div>
  </div>
</template>

- Cons of this implementation

  • Containers are not customizable easily. You will probably end up adding something like v:bind="headerAttrs" and :class="headerClass" for each slot, which results in 2 properties per slot... and let's not talk about directives and event handlers.

  • Containers will be there even if the slot is empty. You would write a computed property like headerEmpty() { return this.$slots.header.length > 0 } but as computed values are cached and slots update doesn't trigger recalculation, it would turn into a methods instead... which is kinda not the place you want it to be (fiddle demo).

- Implementation checklist

  • [ ] customizable from userland with classes and attributes
  • [ ] totally optional
  • [x] enforce minimal implementation (element type, and attributes).

2. A second try

<template>
  <div class="card">
    <slot name="header" />
    <slot name="body" />
    <slot name="footer" />
  </div>
</template>

- This implementation solves

  • [x] totally optional

- This implementation breaks

  • [ ] enforce minimal implementation (element type, and attributes).

- Cons of this implementation :

  • The element given as slot is not controlled by the component : if your slot[name=header] requires the slot to be a div, there is no way to enforce that.

  • You could give properties to the slot, but then you cannot enforce their use as it requires scope='props' (or $attrs, but not both) to be coded, and class will be forgotten anyway.

- Implementation checklist :

  • [x] customizable from userland with classes and attributes
  • [x] totally optional
  • [ ] enforce minimal implementation (element type, and attributes).

3. The ideal implementation

The ideal implementation is actually not far from the 2nd one. If you were willing to add multiple checks for the tag, it would be easy through vnode.tag but it would require a little bit more effort to describe classes, style and attributes ; at the same time, you need to consider that it means the conveniency of use of your component, which could be low if classes and attributes have to be written every time_.

To make it as convenient as possible, we could split into several components which already provide those requirements :

<!-- Card.vue -->
<template>
  <div class="card">
    <slot name="header" />
    <slot name="body" />
    <slot name="footer" />
  </div>
</template>
<!-- Header.vue -->
<template>
  <div class="header"><slot /></div>
</template>
<!-- Body.vue -->
<template>
  <div class="body"><slot /></div>
</template>
<!-- Footer.vue -->
<template>
  <div class="footer"><slot /></div>
</template>

This implementation solves

  • [x] customizable with classes and attributes
  • [x] provide minimal requirements (element type, and attributes).

This implementation breaks

  • [ ] enforce minimal requirements (element type, and attributes).

Cons of this implementation

  • By doing this, you're not actually 100% sure that your component will actually receive a <header /> in the <slot name="header">... You're hoping it will be the case.

4. The real implementation

The latest implementation is quite great, but needs a boost to make sure that only a limited/controlled set of components will be moved to the given slots... and that's where vue-slot-spread comes into action.

<!-- Card.vue -->
<template>
  <div class="card">
    <slot name="header" />
    <slot name="body" />
    <slot name="footer" />
  </div>
</template>

<script>
import spread from 'vue-slot-spread';

export default {
  mixins: [spread({
    'card-header': 'header',
    'card-body'  : 'body',
    'card-footer': 'footer',
  })]

  ...
}
<!-- CardHeader.vue -->
<template>
  <div class="header"><slot /></div>
</template>
<!-- CardBody.vue -->
<template>
  <div class="body"><slot /></div>
</template>
<!-- CardFooter.vue -->
<template>
  <div class="footer"><slot /></div>
</template>

This implementation breaks

  • [x] enforce minimal requirements (element type, and attributes).

Cons of this implementation

  • Slot distribution is totally transparent in userland. Users of your component will not see the deconstruction happen has they will actually write their components in <slot />.

- Implementation checklist :

  • [x] customizable from userland with classes and attributes
  • [x] totally optional
  • [x] enforce minimal implementation (element type, and attributes).

How

Slots are basic arrays, and hopefully not reactive, so we can rearrange them the way we want. The idea is to use a mixin which will move some tag from slot[name=default] to slot[name=].

I first wrote the process function (see source) based on this idea, and it works perfectly fine if you call it at created() hook. Sadly, there's no hook to manipulate slots before rendering, so I had to overwrite _render and insert the slot manipulation process just before rendering. It's nasty, I know... but it works.