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

surreal.me

v1.2.0

Published

Surreal

Downloads

49

Readme

🗿 Surreal

Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!

cover (Art by shahabalizadeh)

Why does this exist?

For devs who love ergonomics! You may appreciate Surreal if:

  • You want to stay as close as possible to Vanilla JS.
  • Hate typing document.querySelector over.. and over..
  • Hate typing addEventListener over.. and over..
  • Really wish document.querySelectorAll had Array functions..
  • Really wish this would work in any inline <script> tag
  • Enjoyed using jQuery selector syntax.
  • Animations, timelines, tweens with no extra libraries.
  • Only 320 lines. No build step. No dependencies.
  • Pairs well with htmx
  • Want fewer layers, less complexity. Are aware of the cargo cult. ✈️

✨ What does it add to Javascript?

  • ⚡️ Locality of Behavior (LoB) Use me() inside <script>
    • No .class or #id needed! Get an element without creating a unique name.
    • this but much more flexible!
    • Want me in your CSS <style> tags, too? See our companion script
  • 🔗 Call chaining, jQuery style.
  • ♻️ Functions work seamlessly on 1 element or arrays of elements!
    • All functions can use: me(), any(), NodeList, HTMLElement (..or arrays of these!)
    • Get 1 element: me()
    • ..or many elements: any()
    • me() or any() can chain with any Surreal function.
      • me() can be used directly as a single element (like querySelector() or $())
      • any() can use: for / forEach / filter / map (like querySelectorAll() or $())
  • 🌗 No forced style. Use: classAdd or class_add or addClass or add_class
    • Use camelCase (Javascript) or snake_case (Python, Rust, PHP, Ruby, SQL, CSS).

🤔 Why use me() / any() instead of $()

  • 💡 Solves the classic jQuery bloat problem: Am I getting 1 element or an array of elements?
    • me() is guaranteed to return 1 element (or first found, or null).
    • any() is guaranteed to return an array (or empty array).
    • No more checks = write less code. Bonus: Reads more like self-documenting english.

👁️ How does it look?

Do surreal things with Locality of Behavior like:

<label for="file-input" >
  <div class="uploader"></div>
  <script>
    me().on("dragover", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files in drop zone.") })
    me().on("dragleave", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files left drop zone.") })
    me().on("drop", ev => { halt(ev); me(ev).classRemove('.hover').classAdd('.loading'); me('#file-input').attribute('files', ev.dataTransfer.files); me('#form').send('change') })
  </script>
</label>

See the Live Example! Then view source.

🎁 Install

Surreal is only 320 lines. No build step. No dependencies.

📥 Download into your project, and add <script src="/surreal.js"></script> in your <head>

Or, 🌐 via CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal@main/surreal.js"></script>

⚡ Usage

🔍️ DOM Selection

  • Select one element: me(...)
    • Can be any of:
      • CSS selector: ".button", "#header", "h1", "body > .block"
      • Variables: body, e, some_element
      • Events: event.currentTarget will be used.
      • Surreal selectors: me(),any()
      • Choose the start location in the DOM with the 2nd arg. (Default: document)
        • ▶️ any('button', me('#header')).classAdd('red')
          • Add .red to any <button> inside of #header
    • me() ⭐ Get parent element of <script> without a .class or #id !
    • me("body") Gets <body>
    • me(".button") Gets the first <div class="button">...</div>. To get all of them use any()
  • Select one or more elements as an array: any(...)
    • Like me() but guaranteed to return an array (or empty array).
    • any(".foo") ⭐ Get all matching elements.
    • Convert between arrays of elements and single elements: any(me()), me(any(".something"))

🔥 DOM Functions

  • ♻️ All functions work on single elements or arrays of elements.
  • 🔗 Start a chain using me() and any()
    • 🟢 Style A me().classAdd('red') ⭐ Chain style. Recommended!
    • 🟠 Style B: classAdd(me(), 'red')
  • 🌐 Global conveniences help you write less code.
    • globalsAdd() will automatically warn you of any clobbering issues!
    • 💀🩸 If you want no conveniences, or are a masochist, delete globalsAdd()
      • 🟢 me().classAdd('red') becomes surreal.me().classAdd('red')
      • 🟠 classAdd(me(), 'red') becomes surreal.classAdd(surreal.me(), 'red')

See: Quick Start and Reference and No Surreal Needed

⚡ Quick Start

  • Add a class
    • me().classAdd('red')
    • any("button").classAdd('red')
  • Events
    • me().on("click", ev => me(ev).fadeOut() )
    • any('button').on('click', ev => { me(ev).styles('color: red') })
  • Run functions over elements.
    • any('button').run(_ => { alert(_) })
  • Styles / CSS
    • me().styles('color: red')
    • me().styles({ 'color':'red', 'background':'blue' })
  • Attributes
    • me().attribute('active', true)

Timeline animations without any libraries.

<div>I change color every second.
  <script>
    // On click, animate something new every second.
    me().on("click", async ev => {
      let el = me(ev) // Save target because async will lose it.
      me(el).styles({ "transition": "background 1s" })
      await sleep(1000)
      me(el).styles({ "background": "red" })
      await sleep(1000)
      me(el).styles({ "background": "green" })
      await sleep(1000)
      me(el).styles({ "background": "blue" })
      await sleep(1000)
      me(el).styles({ "background": "none" })
      await sleep(1000)
      me(el).remove()
    })
  </script>
</div>
<div>I fade out and remove myself.
  <script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
<div>Change color every second.
  <script>
    // Run immediately.
    (async (e = me()) => {
      me(e).styles({ "transition": "background 1s" })
      await sleep(1000)
      me(e).styles({ "background": "red" })
      await sleep(1000)
      me(e).styles({ "background": "green" })
      await sleep(1000)
      me(e).styles({ "background": "blue" })
      await sleep(1000)
      me(e).styles({ "background": "none" })
      await sleep(1000)
      me(e).remove()
    })()
  </script>
</div>
<script>
  // Run immediately, for every <button> globally!
  (async () => {
    any("button").fadeOut()
  })()
</script>

Array methods

any('button')?.forEach(...)
any('button')?.map(...)

👁️ Functions

Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?

🧭 Legend

  • 🔗 Chainable off me() and any()
  • 🌐 Global shortcut.
  • ▶️ Runnable example.
  • 🔌 Built-in Plugin

👁️ At a glance

  • 🔗 run
    • It's forEach but less wordy and works on single elements, too!
    • ▶️ me().run(e => { alert(e) })
    • ▶️ any('button').run(e => { alert(e) })
  • 🔗 remove
    • ▶️ me().remove()
    • ▶️ any('button').remove()
  • 🔗 classAdd 🌗 class_add 🌗 addClass 🌗 add_class
    • ▶️ me().classAdd('active')
    • Leading . is optional
      • Same thing: me().classAdd('active') 🌗 me().classAdd('.active')
  • 🔗 classRemove 🌗 class_remove 🌗 removeClass 🌗 remove_class
    • ▶️ me().classRemove('active')
  • 🔗 classToggle 🌗 class_toggle 🌗 toggleClass 🌗 toggle_class
    • ▶️ me().classToggle('active')
  • 🔗 styles
    • ▶️ me().styles('color: red') Add style.
    • ▶️ me().styles({ 'color':'red', 'background':'blue' }) Add multiple styles.
    • ▶️ me().styles({ 'background':null }) Remove style.
  • 🔗 attribute 🌗 attributes 🌗 attr
    • Get: ▶️ me().attribute('data-x')
      • For single elements.
      • For many elements, wrap it in: any(...).run(...) or any(...).forEach(...)
    • Set: ▶️me().attribute('data-x', true)
    • Set multiple: ▶️ me().attribute({ 'data-x':'yes', 'data-y':'no' })
    • Remove: ▶️ me().attribute('data-x', null)
    • Remove multiple: ▶️ me().attribute({ 'data-x': null, 'data-y':null })
  • 🔗 send 🌗 trigger
    • ▶️ me().send('change')
    • ▶️ me().send('change', {'data':'thing'})
    • Wraps dispatchEvent
  • 🔗 on
    • ▶️ me().on('click', ev => { me(ev).styles('background', 'red') })
    • Wraps addEventListener
  • 🔗 off
    • ▶️ me().off('click', fn)
    • Wraps removeEventListener
  • 🔗 offAll
    • ▶️ me().offAll()
  • 🔗 disable
    • ▶️ me().disable()
    • Easy alternative to off(). Disables click, key, submit events.
  • 🔗 enable
    • ▶️ me().enable()
    • Opposite of disable()
  • 🌐 sleep
    • ▶️ await sleep(1000, ev => { alert(ev) })
    • async version of setTimeout
    • Wonderful for animation timelines.
  • 🌐 tick
    • ▶️ await tick()
    • await version of rAF / requestAnimationFrame.
    • Animation tick. Waits 1 frame.
    • Great if you need to wait for events to propagate.
  • 🌐 rAF
    • ▶️ rAF(e => { return e })
    • Animation tick. Fires when 1 frame has passed. Alias of requestAnimationFrame
    • Great if you need to wait for events to propagate.
  • 🌐 rIC
    • ▶️ rIC(e => { return e })
    • Great time to compute. Fires function when JS is idle. Alias of requestIdleCallback
  • 🌐 halt
    • ▶️ halt(event)
    • Prevent default browser behaviors.
    • Wrapper for preventDefault
  • 🌐 createElement 🌗 create_element
    • ▶️ e_new = createElement("div"); me().prepend(e_new)
    • Alias of vanilla document.createElement
  • 🌐 onloadAdd 🌗 onload_add 🌗 addOnload 🌗 add_onload
    • ▶️ onloadAdd(_ => { alert("loaded!"); })
    • Execute after the DOM is ready. Similar to jquery ready()
    • Queues functions onto window.onload
    • Why? So you don't overwrite window.onload, also predictable sequential loading!
  • 🔌 fadeOut
    • See below
  • 🔌 fadeIn
    • See below

🔌 Built-in Plugins

Effects

Build effects with me().styles({...}) with timelines using CSS transitioned await or callbacks.

Common effects included:

  • 🔗 fadeOut 🌗 fade_out

    • Fade out and remove element.
    • Keep element with remove=false.
    • ▶️ me().fadeOut()
    • ▶️ me().fadeOut(ev => { alert("Faded out!") }, 3000) Over 3 seconds then call function.
  • 🔗 fadeIn 🌗 fade_in

    • Fade in existing element which has opacity: 0
    • ▶️ me().fadeIn()
    • ▶️ me().fadeIn(ev => { alert("Faded in!") }, 3000) Over 3 seconds then call function.

⚪ No Surreal Needed

More often than not, Vanilla JS is the easiest way!

Logging

  • ▶️ console.log() console.warn() console.error()
  • Event logging: ▶️ monitorEvents(me()) See: Chrome Blog

Benchmarking / Time It!

  • ▶️ console.time('name')
  • ▶️ console.timeEnd('name')

Text / HTML Content

  • ▶️ me().textContent = "hello world"
    • XSS Safe! See: MDN
  • ▶️ me().innerHTML = "<p>hello world</p>"
  • ▶️ me().innerText = "hello world"

Children

  • ▶️ me().children
  • ▶️ me().children.hidden = true

Append / Prepend elements.

  • ▶️ me().prepend(new_element)
  • ▶️ me().appendChild(new_element)
  • ▶️ me().insertBefore(element, other_element.firstChild)
  • ▶️ me().insertAdjacentHTML("beforebegin", new_element)

AJAX (replace jQuery ajax())

me().on("click", async event => {
  let e = me(event)
  // EXAMPLE 1: Hit an endpoint.
  if((await fetch("/webhook")).ok) console.log("Did the thing.")
  // EXAMPLE 2: Get content and replace me()
  try {
    let response = await fetch('/endpoint')
    if (response.ok) e.innerHTML = await response.text()
    else console.warn('fetch(): Bad response')
  }
  catch (error) { console.warn(`fetch(): ${error}`) }
})
  • Using XMLHttpRequest()
me().on("click", async event => {
  let e = me(event)
  // EXAMPLE 1: Hit an endpoint.
  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/webhook")
  xhr.send()
  // EXAMPLE 2: Get content and replace me()
  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/endpoint")
  xhr.onreadystatechange = () => {
    if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) e.innerHTML = xhr.responseText
  }
  xhr.send()
})

💎 Conventions & Tips

  • Many ideas can be done in HTML / CSS (ex: dropdowns)
  • _ = for temporary or unused variables. Keep it short and sweet!
  • e, el, elt = element
  • e, ev, evt = event
  • f, fn = function

Scope functions inside <script>

  • ⭐ On me()
    • me().hey = (text) => { alert(text) }
    • me().on('click', (ev) => { me(ev).hey("hi") })
  • ⭐ Use a block: { function hey(text) { alert(text) }; me().on('click', ev => { hey("hi") }) }
  • ⭐ Use an event: me().on('click', ev => { /* add and call function here */ })
  • Use an inline module: <script type="module">
    • Note: me() will no longer see parentElement so explicit selectors are required: me(".mybutton")

Select a void element like <input type="text" />

  • Use: me('-') or me('prev') or me('previous')
    • ▶️ <input type="text" /> <script>me('-').value = "hello"</script>
    • Inspired by the CSS "next sibling" combinator + but in reverse -
  • Or, use a relative start.
    • ▶️ <form> <input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script> </form>

Ignore call chain when element is missing.

  • ▶️ me("#i_dont_exist")?.classAdd('active')
  • No warnings: ▶️ me("#i_dont_exist", document, false)?.classAdd('active')

🔌 Your own plugin

Feel free to edit Surreal directly- but if you prefer, you can use plugins to effortlessly merge with new versions.

function pluginHello(e) {
  function hello(e, name="World") {
    console.log(`Hello ${name} from ${e}`)
    return e // Make chainable.
  }
  // Add sugar
  e.hello = (name) => { return hello(e, name) }
}

surreal.plugins.push(pluginHello)

Now use your function like: me().hello("Internet")

  • See the included pluginEffects for a more comprehensive example.
  • Your functions are added globally by globalsAdd() If you do not want this, add it to the restricted list.
  • Refer to an existing function to see how to make yours work with 1 or many elements.

Make an issue or pull request if you think people would like to use it! If it's useful enough we'll want it in core.

⭐ Awesome Surreal examples, plugins, and resources: awesome-surreal !

📚️ Inspired by

  • jQuery for the chainable syntax we all love.
  • BlingBling.js for modern minimalism.
  • Bliss.js for a focus on single elements and extensibility.
  • Hyperscript for Locality of Behavior and awesome ergonomics.
  • Shout out to Umbrella, Cash, Zepto- Not quite as ergonomic. Requires build step to extend.

🌘 Future

  • Always more example.html goodies!
  • Automated browser testing perhaps with: