Thanks to visit codestin.com
Credit goes to github.com

Skip to content
/ sly Public
forked from e280/sly

2.0 版本需要用的 turtle is replaced by @e280/scute slate is replaced by @e280/sly construct is replaced by @e280/lettuce

License

Notifications You must be signed in to change notification settings

2410024100/sly

 
 

Repository files navigation

🦝 sly — mischievous shadow views

testing page https://sly.e280.org/

  • 🍋 web app view library with taste
  • 🥷 leverage shadow-dom and slots
  • 🤯 register any view as a web component
  • 💲 handy little dom multitool
  • 🫛 ops for fancy loading spinners
  • 🧙‍♂️ took many years to get it right
  • 🌅 sly is the successor that replaces @benev/slate
  • 🧑‍💻 project by @e280

🦝 sly and friends

npm install @e280/sly lit

Note

  • 🔥 lit for html rendering
  • ⛏️ @e280/strata for state management (signals, state trees)
  • 🏂 @e280/stz (optional) stz is our ts standard library
  • 🐢 scute (optional) is our buildy-bundly-buddy

🦝 sly views

views are the crown jewel of sly.. shadow-dom'd.. hooks-based.. "ergonomics"..

view(use => () => html`<p>hello world</p>`)
  • views are not web components, but they do have shadow roots and support slots
  • any view can be registered as a web component, perfect for entrypoints or sharing widgets with html authors
  • views are typescript-native and comfy for webdevs building apps
  • views automatically rerender whenever any strata-compatible state changes

🍋 view example

  • import stuff
    import {$, view} from "@e280/sly"
    import {html, css} from "lit"
  • declare a view
    export const CounterView = view(use => (start: number) => {
      use.name("counter")
      use.styles(css`p {color: green}`)
    
      const count = use.signal(start)
      const increment = () => { count.value++ }
    
      return html`
        <p>count ${count()}</p>
        <button @click="${increment}">+</button>
      `
    })
    • each view renders into a <sly-view view="counter"> host (where "counter" is the use.name you provided)
  • inject a view into the dom
    $.render($(".app"), html`
      <h1>my cool counter demo</h1>
    
      ${CounterView(1)}
    `)
  • 🤯 register a view as a web component
    $.register({MyCounter: CounterView.component(1)})
      // <my-counter></my-counter>

🍋 view declaration settings

  • special settings for views at declaration-time
    export const CoolView = view
      .settings({mode: "open", delegatesFocus: true})
      .declare(use => (greeting: string) => {
        return html`😎 ${greeting} <slot></slot>`
      })
    • all attachShadow params (like mode and delegatesFocus) are valid settings
    • note the <slot></slot> we'll use in the next example lol

🍋 view injection options

  • options for views at the template injection site
    $.render($(".app"), html`
      <h2>super cool example</h2>
    
      ${CoolView.props("hello")
        .attr("class", "hero")
        .children(html`<em>spongebob</em>`)
        .render()}
    `)
    • props — provide props and start a view chain
    • attr — set html attributes on the <sly-view> host element
    • children — nested content in the host element, can be slotted
    • render — end the view chain and render the lit directive

🍋 web components

  • build a component directly
    const MyComponent = view.component(use => html`<p>hello world</p>`)
    • notice that direct components don't take props (do use.attrs instead)
  • convert any view into a web component
    const MyCounter = CounterView.component(1)
    • to convert a view to a component, you provide props
    • note that the component instance has a render method like element.render(2) which can take new props at runtime
  • register web components to the dom
    $.register({MyComponent, MyCounter})
      // <my-component></my-component>
      // <my-counter></my-counter>
    • $.register automatically dashes the tag names (MyComponent becomes <my-component>)

🍋 view "use" hooks reference

  • 👮 follow the hooks rules

    just like react hooks, the execution order of sly's use hooks actually matters..
    you must not call these hooks under if conditionals, or for loops, or in callbacks, or after a conditional return statement, or anything like that.. otherwise, heed my warning: weird bad stuff will happen..

  • use.name — set the "view" attr value, eg <sly-view view="squarepants">
    use.name("squarepants")
  • use.styles — attach stylesheets into the view's shadow dom
    use.styles(css1, css2, css3)
  • use.signal — create a strata signal
    const count = use.signal(1)
    
    // read the signal
    count() // 1
    
    // write the signal
    count(2)
  • use.once — run fn at initialization, and return a value
    const whatever = use.once(() => {
      console.log("happens only once")
      return 123
    })
    
    whatever // 123
  • use.mount — setup mount/unmount lifecycle
    use.mount(() => {
      console.log("view mounted")
    
      return () => {
        console.log("view unmounted")
      }
    })
  • use.wake — run fn each time mounted, and return value
    const whatever = use.wake(() => {
      console.log("view mounted")
      return 123
    })
    
    whatever // 123
  • use.life — mount/unmount lifecycle, but also return a value
    const v = use.life(() => {
      console.log("mounted")
      const value = 123
      return [value, () => console.log("unmounted")]
    })
    
    v // 123
  • use.attrs — ergonomic typed html attribute access
    const attrs = use.attrs({
      name: String,
      count: Number,
      active: Boolean,
    })
    attrs.name // "chase"
    attrs.count // 123
    attrs.active // true
    attrs.name = "zenky"
    attrs.count = 124
    attrs.active = false // removes html attr
    attrs.name = undefined // removes the attr
  • use.render — rerender the view (debounced)
    use.render()
  • use.renderNow — rerender the view instantly (not debounced)
    use.renderNow()
  • use.rendered — promise that resolves after the next render
    use.rendered.then(() => {
      const slot = use.shadow.querySelector("slot")
      console.log(slot)
    })
  • use.op — start with an op based on an async fn
    const op = use.op(async() => {
      await nap(5000)
      return 123
    })
  • use.op.promise — start with an op based on a promise
    const op = use.op.promise(doAsyncWork())

🍋 view "use" recipes

  • make a ticker — mount, repeat, and nap
    import {repeat, nap} from "@e280/stz"
    const seconds = use.signal(0)
    
    use.mount(() => repeat(async() => {
      await nap(1000)
      seconds.value++
    }))
  • wake + rendered, to do something after each mount's first render
    use.wake(() => use.rendered.then(() => {
      console.log("after first render")
    }))

🦝 sly dom multitool

"it's not jquery!"

💲 follow the money

  • import the dollarsign
    import {$} from "@e280/sly"

💲 dom queries

  • require an element
    $(".demo")
      // HTMLElement (or throws error)
  • request an element
    $.maybe(".demo")
      // HTMLElement | undefined
  • query all elements
    for (const item of $.all("ul li"))
      console.log(item)
  • specify what element to query under
    $("li", listElement)
      // HTMLElement

💲 dom utilities

  • render content into an element
    $.render(element, html`<p>hello world</p>`)
  • register web components
    $.register({MyComponent, AnotherCoolComponent})
      // <my-component>
      // <another-cool-component>

🦝 sly ops, pods, and loaders

async operations and displaying loading spinners.

import {nap} from "@e280/stz"
import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"

🫛 pods: loading/ready/error

  • a pod represents an async operation in terms of json-serializable data
  • there are three kinds of Pod<V>
    // loading pod
    ["loading"]
    
    // ready pod contains value 123
    ["ready", 123]
    
    // error pod contains an error
    ["error", new Error()]

🫛 podium: helps you work with pods

  • get pod status
    podium.status(["ready", 123])
      // "ready"
  • get pod ready value (or undefined)
    podium.value(["loading"])
      // undefined
    
    podium.value(["ready", 123])
      // 123
  • see more at podium.ts

🫛 ops: nice pod ergonomics

  • an Op<V> wraps a pod with a signal for reactivity
  • create an op
    const op = new Op<number>() // loading status by default
    const op = Op.loading<number>()
    const op = Op.ready<number>(123)
    const op = Op.error<number>(new Error())
  • 🔥 create an op that calls and tracks an async fn
    const op = Op.fn(async() => {
      await nap(4000)
      return 123
    })
  • await for the next ready value (or thrown error)
    await op // 123
  • get pod info
    op.pod // ["loading"]
    op.status // "loading"
    op.value // undefined (or value if ready)
    op.isLoading // true
    op.isReady // false
    op.isError // false
  • select executes a fn based on the status
    const result = op.select({
      loading: () => "it's loading...",
      ready: value => `dude, it's ready! ${value}`,
      error: err => `dude, there's an error!`,
    })
    
    result
      // "dude, it's ready! 123"
  • morph returns a new pod, transforming the value if ready
    op.morph(n => n + 1)
      // ["ready", 124]
  • you can combine a number of ops into a single pod like this
    Op.all(Op.ready(123), Op.loading())
      // ["loading"]
    Op.all(Op.ready(1), Op.ready(2), Op.ready(3))
      // ["ready", [1, 2, 3]]
    • error if any ops are in error, otherwise
    • loading if any ops are in loading, otherwise
    • ready if all the ops are ready

🫛 loaders: animated loading spinners

  • create a loader using makeLoader
    const loader = makeLoader(anims.dots)
    • see all the anims available on the testing page https://sly.e280.org/
    • ngl, i made too many.. i was having fun, okay?
  • use the loader to render your op
    return html`
      <h2>cool stuff</h2>
    
      ${loader(op, value => html`
        <div>${value}</div>
      `)}
    `
    • when the op is loading, the loading spinner will animate
    • when the op is in error, the error will be displayed
    • when the op is ready, your fn is called and given the value

🧑‍💻 sly by e280

reward us with github stars
build with us at https://e280.org/ but only if you're cool

About

2.0 版本需要用的 turtle is replaced by @e280/scute slate is replaced by @e280/sly construct is replaced by @e280/lettuce

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 94.9%
  • CSS 5.1%