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

@dolanske/beskydy

v2.0.2

Published

Like alps, but smaller. Inspired by alpine.js and petite-vue with my own simplified implementation.

Downloads

66

Readme

beskydy

Like alps, but smaller. Alpine / vue-petite inspired but mostly implemented by me. Define small interactive partitions within your HTML without needing to write javascript.

The Concept

Create a reactive partition by adding x-scope directive on an element. This will create a reactive scope for said element and expose all of its properties to its descendants.

<div x-scope="{ count: 0 }">
 <button
   x-data="{ incrBy: 2 }"
   x-on:click="count += incrBy"
   x-text="`Add ${incrBy}`"
 ></button>
 <span>Count is: {{ count }}</span>
</div>

The only real JS we need to write is the app initialization. This can be done by simply create new Beskydy() class and calling the collect function. Optionally, we can provide global reactive properties into the constructor, which will be shared and available across all scopes.

import { Beskydy } from 'beskydy'

const app = new Beskydy({
  characters: [],
  isLoading: false,
  async fetchCharacters() {
    this.isLoading = true
    this.characters = await fetch('https://swapi.dev/api/people/')
    this.isLoading = false
  }
})

// Calling this method will start the app initialization.
// All declared scopes will be collected and activated.
app.collect()

// If needed, you can destroy Beskydy instance.
// This will remove event listeners, all reactive bindings
// and turn the DOM back into being static
app.teardown()

Beskydy offers a flexible and very extensible API. You can creative as many new directives as you please or extend model & event modifiers. Below are a few examples.

import { Beskydy } from 'beskydy'

const app = new Beskydy()

// Change the delimiters, which Beskydy uses to collect text content expressions
// Default: {{ & }}
app.setDelimiters('[', ']')

// Run code once all the scopes have been initialized
app.onInit(() => {})

// Run code when the app instance is closed
app.onTeardown(() => {})

// Add custom directives
// This directive will append HIHI after the provided text property
app.defineDirective('x-funny', (ctx, node, attr) => {
  // Usage
  // <div x-data="{ text: 'hello' }">
  // -> <span x-funny="text"></span>

  // Whenever a reactive property is updated (text), this function is ran
  ctx.effect(() => {
    // Eval returns a value from a string we provide
    // In our example, attr.value is a "text",
    // which means we're referencing a value defined in the data object
    const value = ctx.eval(attr.value)

    // Here's the funny, we add HAHAHA before the text value
    // Result is <span>hello HIHI</span>
    node.textContent = `${String(value)} HIHI`

    // If the text property is changed to 'world' (or anything else)
    // This element will automatically update
    // <span>world HIHI</span>
  })
})

// Add custom event modifier. Read more about modifiers in the `x-on` section below
app.defineEventModifier('save', (event, customState, param) => {
  localStorage.setItem(param, String(event.data))
})

// Add custom `x-model` modifier. Also supports modifiers with parameters
app.defineModelModifier('toLowerCase', (newValue, oldValue, param) => {
  return String(value).toLowerCase()
})

Expressions

Each expression is a piece of code that gets evaluated. Because it's all in a string, you don't have autocomplete available. Expessions expose a few additional properties, which you can use to your advantage.

  • $el: Expose the current element we're writing expression for
  • $event: Expose the event, if used within event listeners
  • $refs: Expose the scope's element refs

Inline expressions can be added to a text content of any element. You need to wrap these with delimiters {{ test }}, and they do not expose anything, but it's the best way to add reactive pieces into text.

Directives

There are 18 directives in total. Each simplifying the way we can interact or update the DOM.

x-scopex-datax-ifx-switchx-showx-forx-portalx-spyx-refx-onx-modelx-bindx-classx-stylex-textx-htmlx-initx-processed

x-scope

Initializes a reactive scope. Every directive only works if it exists within a scope.

<div x-scope="{ initialData: 0, count: 10 }">
  ...
</div>

x-data

Append data into scope's dataset. Any data within this directive will be available to its parent elements, but this practise is discouraged, as on first draw, this data will be undefined.

<div x-scope="{ initialData: 0, count: 10 }">
  <div x-data="{ someMoreData: 'hello' }">
    ...
  </div>
</div>

x-if, x-else-if, x-else

Conditionally render elements based on the expression results.

<div x-scope="{ count: 0 }">
  <span x-if="count < 3">Less than 3</span>
  <span x-else-if="count >= 3 && count <= 6">Between and including 3 and 6</span>
  <span x-else>More than 6</span>
</div>

x-switch

Cleaner way to write many of conditional statements for a single reactive value.

<div x-scope="{ htmlNodeType: 1 }">
  <div x-switch="htmlNodeType">
    <span x-case="1">Element Node</span>
    <span x-case="2">Attribute Node</span>
    <span x-case="3">Text node</span>
    <span x-case="11">Document Fragment</span>
    <span x-default>Other nodes</span>
  </div>
</div>

x-show

Show or hide the element based on the expression result. It adds display:none when hiding and reverts back to the original setting of the display property.

<div x-scope="{ visible: false }">
  <p x-show="visible">I am still in the DOM but just hidden</p>
</div>

x-for

Render list of elements based on the provided array, object or range.

Range

<ul x-scope="{ items: 10 }">
  <li x-for="item in people">{{item + 1}}</li>
</ul>

Array

Iterator exposes the property and the index.

<ul x-scope="{ people: ['Jan', 'Andrew', 'Jokum', 'Anton'] }">
  <li x-for="(name, personIndex) in people">{{ personIndex + 1 }} {{ name }}</li>
</ul>

Object

Iterator exposes the property, property key and the index.

<table x-scope="{ people: { name: 'Jan', age: 52 } }">
  <tr x-for="(value, key) in people">
    <th>{{ key }}</th>
    <td>{{ value }}</td>
  </tr>
</table>

x-portal

Allows you to move piece of a scope anywhere in the DOM, while retaining its reactive context.

<!-- Original scope -->
<div x-scope="{ text: 'Hello World' }">
  <input type="text" x-model="text">
  <div class="wrapper blue">
    <div>
      <span x-portal="#target" x-data="{ append: ' hehe' }">{{ text + append }}</span>
    </div>
  </div>
</div>

<!-- Anywhere else in the DOM -->
<div class="wrapper red" id="target" />

The <span> will act like it's always been part of the #target element, but it'll have access to all the properties defined within the original scope.

x-spy

Allows you to execute a provided callback whenever the reactive scope is updated.

<div x-scope="{ first: 1 }">
  <button @click="first++" x-spy="console.log('Updated!', first)">Increment</button>
</div>

If you want to spy on a specific property, you can add its key as a parameter to x-spy. Just note, this way you can only watch for changes in the top-level properties in your scope.

<div x-scope="{ first: 1, second: 10 }">
  <button @click="first++">Increment First</button>
  <button @click="second++">Increment Second</button>

  <!-- The spy callback only runs if the `second` property is updated -->
  <div x-spy:second="console.log('Updated second', second)"></div>
</div>

x-ref

Saves the element to the $refs object which is available in the scope. Any changes made to the ref element will trigger reactive updates.

<div x-scope="{ text: 'Hello' }">
  <input x-model="text" />
    <!-- When we change the input, the ref element's textContent is updated -->
    <span x-ref="item">{{ text }}</span>
    <!-- $refs object is updated whenever the element is modified -->
    <span>{{ $refs.item.textContent }}</span>
  </div>
</div>

x-on

Binds an event listener with optional modifiers.

<div x-on:eventName.modifier.modifier="expression" />
<div @eventName.modifier.modifier="expression" />
<div x-scope="{ open: false }">
  <button x-on:click="open = !open">Toggle</button>
  <p x-if="open">I am visible</p>
</div>

Modifiers

  • once: Runs only once
  • self: Runs the expression only if event.target equals to event.currentTarget
  • left, middle, right: Filter mouse clicks
  • prevent: Runs event.preventDefault()
  • stop: Runs event.stopPropagation()
  • stopImmediate: Runs event.stopImmediatePropagation()

Modifiers with parameters

You can provide a single parameter to the modifier using this syntax. You can also pass in a property defined in the x-scope or x-data directives. Note: it does not accept expressions, only variables/primitive values.

<button x-on:click.only[5]="doNothingAfterFiveClicks()" />

<div x-scope="{ limit: 5 }">
  <button x-on:click.only[limit]="doNothingAfterFiveClicks()" />
</div>
  • only (default=1): Run until the provided amount is reached
  • if: Runs if the provided value is truthy
  • throttle: (default=300) Limits the amount of calls within the specified timeframe in milliseconds

x-model

Provides two way data binding to a input/textarea/select/details. It listens to an input event as well as binding the reactive data to the element's value/state.

<div x-scope="{ text: 'Hello' }">
  <!-- The input will start by having "Hello" written within. Any change to the input from said element will update the reactive `text` property -->
  <input x-model="text" />
</div>

The following example works exactly the same as the one above.

<input x-on:input="text = $el.target.value" x-bind:value="text" />

x-bind

Binds an attribute or an attribute object to an element.

<div x-scope="{ isDisabled: false }">
  <div x-bind:disabled="isDisabled" />
  <div :disabled="isDisabled" />
  <div x-bind="{
    disabled: isDisabled,
    class: isDisabled ? 'is-disabled' : 'is-enabled'
  }" />
</div>

x-class

Binds a class or class list to an element.

<div x-scope="{ visible: true, isActive: false }">
  <!-- Inline -->
  <p x-class="visible ? 'is-visible' : null"></p>
  <!-- Object syntax -->
  <p x-class="{ 'is-visible': visible }"></p>
  <!-- Array syntax (combines both previous ones) -->
  <p x-class="[{ 'is-visible': visible }, isActive ? 'active' : null]"></p>
</div>

x-style

Binds reactive style object to an element. The properties can be written both in camel case and kebab case.

<div x-scope="{ offset: 10 }">
  <div class="ellipse" :style="{ top: offset + '%' }">
</div>

x-text

Update the element's textContent

<div x-scope="{ count: 5 }">
  <span x-text="`The count is ${count}`"></span>
</div>

Note, you can get the same result when writing expressions within the delimiters anywhere in the scope. Both of these examples have the exact same result.

<div x-scope="{ count: 5 }">
  <span>The count is {{ count }}</span>
</div>

Note When using x-text, the entire element's textContent as well as any of its child elements will be overwritten by the provided expression.

x-html

Same as with x-text, but sets the element.innerHTML instead.

<div x-scope="{ data: '<span>some fetched html</span>' }">
  <div class='conten-wrapper' x-html="data"></div>
</div>

x-init

Runs the provided expression when the element's data attributes have been initialized, but not the rest of the directives. If you want to run some code when the entire app instance has been initialized, use the app.onInit hook instead.

<div class="{ scopeLoaded: false }" x-init="scopeLoaded = true">
  Loaded {{ scopeLoaded }}
</div>

x-processed

Runs the provided expression when all of the element's directives have been processed

<div class="{ scopeProcessed: false }" x-init="scopeProcessed = true">
  Loaded {{ scopeProcessed }}
</div>