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

@chasemoskal/magical

v0.1.9

Published

web toolkit for lit apps

Downloads

46

Readme

🪄 magical

web toolkit for lit apps

🕹️ live demo — magical.chasemoskal.com
📦 npm install @chasemoskal/magical
💖 made with open source love

magical is a collection of tools we build, maintain, and use every day to make great lit applications.

🤖 magic element

every magic element is also a lit element.

but magic elements have a realize method instead of a render method.

in your realize method, use this.use, to get access to a "hooks" interface for state management.

import {MagicElement, mixinCss, UseElement} from "@chasemoskal/magical"

import {html} from "lit"
import {property} from "lit/decorators.js"
import stylesCss from "./styles.css.js"

@mixinCss(stylesCss)
export class CounterElement extends MagicElement {

  @property({type: Number})
  start = 0

  realize() {
    const {use} = this
    const [count, setCount] = use.state(this.start)
    const increment = () => setCount(x => x + 1)

    use.setup(() => {
      const listener = () => console.log("resized")
      window.addEventListener("resize", listener)
      return () => window.removeEventListener("resize", listener)
    })

    return html`
      <div>
        <p>count ${count}</p>
        <button @click=${increment}>increment</button>
      </div>
    `
  }
}

there are some things to know about:

  • you should never access use outside of realize
  • like any hooks interface, your use calls must be in the same order every time
    • so don't put use.state or use.setup calls inside a for loop or in a callback function or anything like that
    • best practice is to keep use calls at the top-level
  • use.state returns an array with four things:
    • the current value
    • the setter function
      • you can pass it a new value
      • or a function that takes the previous value and returns a new value
    • the getter function
      • the getter is useful getting the latest version of state in a callback
    • the previous value
      • you could compare current===previous to see if the value has changed
  • use.setup
    • use this to run a setup routine every time the component connects to the dom
    • the setup function you provide should return a function that tears down and cleans up any mess, called when the component disconnects from the dom

✨ magic view

views have the same use hook interface, but views are not components or elements.

they're lit directives.

but like elements, views too can have a shadow dom, and their own css styles.

import {view} from "@chasemoskal/magical"

import {html} from "lit"
import stylesCss from "./styles.css.js"

export const CounterView = view({
    shadow: true,
    styles: stylesCss,
  }, use => (start: number) => {

  const [count, setCount] = use.state(start)
  const increment = () => setCount(x => x + 1)

  return html`
    <div>
      <p>count ${count}</p>
      <button @click=${increment}>increment</button>
    </div>
  `
})

the important thing to understand, is how they are used:

  • views are used like this:
    // 🧐
    return html`
      <div>
        ${CounterView(2)}
      </div>
    `
    • this is great, because CounterView is fully typescript-typed
    • and it's directly imported, so it's easy to trace where views are being used (vscode find all references)
    • typescript will sniff out and complain about places you need to change when you update those parameters
  • whereas using an element would be like this:
    // 🤮
    return html`
      <div>
        <counter-element start=2></counter-element>
      </div>
    `
    • this is OK for an html-only interface, but for real app development?
    • this sucks, no typescript typing
    • no imports, no vscode find all references
    • have to worry about dom registrations
    • views solve all of this

compared against elements:

  • views are typescript functions, so their parameters are fully typed, vscode auto-refactoring works
  • views are less cumbersome, because they don't need to be registered to the dom

compared against simple render functions:

  • views have state
  • views are independent rendering contexts
  • views can have shadow dom and their own stylesheets

i think a good way to think about elements and views is like this:

  • elements are entrypoints at the html-level
  • most of our app features are implemented as views
  • our views are comprised of simple render functions

📻 magic event

we have this handy helper for making custom dom events.

import {MagicEvent} from "@chasemoskal/magical"

export class ProfileChanged extends
  MagicEvent<{count: number}>("profile_changed") {}

// dispatch the event
MyCoolEvent
  .target(window)
  .dispatch({count: 1})

// listen for the event
const unlisten = MyCoolEvent
  .target(window)
  .listen(event => {
    console.log("profile changed", event.detail.count)
  })

instead of extending MagicEvent, you can just use ev directly to listen and dispatch custom events:

import {ev} from "@chasemoskal/magical"

ev(MyCustomEvent)
  .target(window)
  .dispatch({lol: "example"})

const unlisten = ev(MyCustomEvent)
  .target(window)
  .listen(event => {
    console.log("example event", event.detail.lol)
  })

🐫 camel css

we wanted sass-like css nesting, but in our web components.

so we built a parser and compiler for a new css language.

it can run serverside, as part of a build script, or our preferred method — live on the clientside, compiling stylesheets for our elements and views.

camel css can be a drop-in replacement for lit's css tagged-template function:

import {css} from "@chasemoskal/magical"

const styles = css`
div {
  p { color: red; }
}
`

camel-css uses ^ instead of sass's &

🪄 more magical tools

⚙️ registerElements and themeElements

for the love of god, if you're writing a web components library, do not call customElements.define in those component modules.

be polite, and allow us the opportunity to augment your elements, rename them, apply a css theme, and then we can register our augmented elements.

so, when we're making a library, we like to have a function like getElements that returns all the library's elements classes.

then it's easy for anybody to apply a css theme and register the elements:

import {registerElements, themeElements} from "@chasemoskal/magical"

registerElements(
  themeElements(
    themeCss,
    getElements(),
  )
)
  • registerElements will automatically take CamelCaseComponent names and convert them into camel-case-component names

🎨 mixins for your lit elements

TODO documentation for these

  • mixinCss
  • mixinLightDom
  • mixinRefreshInterval
  • mixinContextRequired

🏀 debounce

i've made like ten versions of this, and i think this is my masterpiece. it even has unit tests.

import {debounce} from "@chasemoskal/magical"

const action = () => console.log("action!")
const debouncedAction = debounce(1000, action)
// debouncedAction is a promise that resolves
// after the 1000 millseconds of no activity

debouncedAction()
debouncedAction()
await debouncedAction()
//> "action!"
// the action only fires once

this debouncer

  • typescript
  • works with functions or async functions
  • returns promises
  • the promises resolve with the actual value

    💖 made with open source love