sinuous-bind Svelte Themes

Sinuous Bind

Give your Sinuous app Svelte-like bindings!

sinuous-bind

About

sinuous-bind brings the bind element directives of Svelte to Sinuous.

Don't do

`
<input value=${state} oninput=${e => state(e.target.value)} />
`

Do

`
<input bind=${state} />
`
size esm cdn
sinuous-bind https://unpkg.com/sinuous-bind@latest/dist/all.min.js
sinuous-bind/bind https://unpkg.com/sinuous-bind@latest/dist/bind.min.js
sinuous-bind/bindArea https://unpkg.com/sinuous-bind@latest/dist/bindArea.min.js
sinuous-bind/bindGroup https://unpkg.com/sinuous-bind@latest/dist/bindGroup.min.js
sinuous-bind/bindMedia https://unpkg.com/sinuous-bind@latest/dist/bindMedia.min.js

Installation

There are two ways to consume sinuous-bind:

ESM

ESM Example CodeSandbox

Run the following inside your project directory to get all the bind packages:

npm install sinuous-bind

Setup

Import all the packages into the root of your project like so:

import "sinuous-bind";

Alternatively, bring in any or all of the packages, depending on what you need:

import "sinuous-bind/bind";
import "sinuous-bind/bindGroup";
import "sinuous-bind/bindArea";
import "sinuous-bind/bindMedia";

Gotchas

These packages are not tree-shakeable. If you import the submodule, you will be stuck with it in your bundle.

ESM Example CodeSandbox

CDN

CDN Example CodeSandbox

Put this in your html to get all the packages:

<script src="https://unpkg.com/sinuous-bind@latest/dist/all.min.js"></script>

Alternatively, you can bring in just what you need (though you must include the top one):

<script src="https://unpkg.com/sinuous-bind@latest/dist/bind.min.js"></script>
<script src="https://unpkg.com/sinuous-bind@latest/dist/bindArea.min.js"></script>
<script src="https://unpkg.com/sinuous-bind@latest/dist/bindGroup.min.js"></script>
<script src="https://unpkg.com/sinuous-bind@latest/dist/bindMedia.min.js"></script>

You must also bring in Sinuous:

<script src="https://unpkg.com/sinuous/dist/all.js"></script>

Setup

If you have brought in sinuous-bind/dist/all.min.js, setup is something like what follows:

window.bindAll.enableBind(window.S);

Alternatively, if you brought in only sinuous-bind/dist/bind.min.js, setup is as follows:

window.bind.enableBind(window.S);

If you have brought in more than one package, setup is something like this:

let w = window;
let { register, enableBind } = w.bind;
register(w.bindArea.getBindArea);
register(w.bindGroup.getBindGroup);
register(w.bindMedia.getBindMedia);
enableBind(w.S);

Gotchas

Unlike the esm format, the cdn format requires the basic bind package (sinuous-bind/dist/bind.min.js) for any of the others to be enabled.

CDN Example CodeSandbox

sinuous-bind/bind

bind can take two kinds of arguments

  1. bind=${<observable>}
  2. bind=${[<observable> [, <attribute> [, <event> [, <boolean>]]]]}
    • <attribute> defaults to "value"
    • <event> defaults to "oninput"
    • <boolean>
      • Pass in true to set the attribute with a Sinuous subscribe. This is necessary for things like a contentEditable binding to textContent or innerHTML.

Generally, bind=${state} is sufficient.

bind will attempt to coerce values to numerical values, but will not attempt to coerce booleans or the empty string.

<input> (implicit)

function component() {
  const state = o("");

  return html`
    <input bind=${state} />
  `;
}

<input> (explicit)

function component() {
  const state = o("");

  return html`
    <input bind=${[state, "value", "oninput"]} />
  `;
}

<input type="range">

function component() {
  const state = o(5);

  return html`
    <input type="range" bind=${state} min="0" max="10" />
  `;
}

contentEditable

function component() {
  const state = o("");

  return html`
    <div contentEditable bind=${[state, "textContent", , true]} />
  `;
}

Takes "textContent" or "innerHTML" as the second element of the array. Requires true as the fourth element.

<input type="checkbox">

function component() {
  const state = o("");

  return html`
    <input type="checkbox" bind=${[state, "checked", "onchange"]} />
  `;
}

<input type="radio">

function component() {
  const state = o("");

  return html`
    <input type="radio" bind=${[state, "checked", "onchange"]} />
  `;
}

<select>

function component() {
  const state = o(1);

  return html`
    <select bind=${state}>
      <option>1</option>
      <option>2</option>
      <option>3</option>
    </select>
  `;
}

Gotchas: It won't work with the multiple attribute. Also, it won't sync until user action. If you need either of these things, use sinuous-bind/bindGroup

ref

function component() {
  const ref = o();

  return html`
    <div ref=${ref}></div>
  `;
}

Gotchas: The value will not be passed into the ref observable until the call to html is made. The DOM node has to be created before it can be passed to ref.

sinuous-bind/bindArea

Both bind:area and bindArea work.

All bind:area bindings are read-only.

bind:area accepts an object of observables, with any of the following keys: clientWidth, clientHeight, offsetWidth, offsetHeight, scrollLeft, scrollTop

All these keys can be updated whenever the element is resized. Additionally, scrollLeft and scrollTop are updated whenever the element is scrolled.

Effects: Set a function on the object passed to bind:area with the key effect. The effect will be called on resize and scroll. Set a function with any other key, and it will be called on resize, only.

Gotchas: bind:area sets position: relative on the bound element. Do not set the style on the element unless you set position.

single property

function component() {
  const clientWidth = o();

  return html`
    <div bind:area=${{ clientWidth }}></div>
  `;
}

multiple properties

function component() {
  const clientWidth = o();
  const clientHeight = o();
  const offsetHeight = o();

  return html`
    <div bind:area=${{ clientWidth, clientHeight, offsetHeight }}></div>
  `;
}

multiple properties with scrolling

function component() {
  const clientWidth = o();
  const clientHeight = o();
  const scrollLeft = o();
  const scrollTop = o();

  return html`
    <div
      bind:area=${{ clientWidth, clientHeight, scrollLeft, scrollTop }}
    ></div>
  `;
}

multiple properties with effects

function component() {
  const clientWidth = o();
  const arbitraryEffect = () => console.log("resizing only");
  const scrollLeft = o();
  const effect = () => console.log("resizing and/or scrolling");

  return html`
    <div
      bind:area=${{ clientWidth, arbitraryEffect, scrollLeft, effect }}
    ></div>
  `;
}

sinuous-bind/bindGroup

Both bind:group and bindGroup work.

Like bind, bind:group will attempt to coerce values to numerical values, but will not attempt to coerce booleans or the empty string.

select

function component() {
  const state = o(1);

  return html`
    <select bind:group=${state}>
      <option>1</option>
      <option>2</option>
      <option>3</option>
    </select>
  `;
}

select multiple

function component() {
  const state = o([1, 2]);

  return html`
    <select bind:group=${state} multiple>
      <option>1</option>
      <option>2</option>
      <option>3</option>
    </select>
  `;
}

radio group

function component() {
  const state = o(1);

  return html`
    <input type="radio" bind:group=${state} value="1" />
    <input type="radio" bind:group=${state} value="2" />
    <input type="radio" bind:group=${state} value="3" />
  `;
}

checkbox group

function component() {
  const state = o([1, 2]);

  return html`
    <input type="checkbox" bind:group=${state} value="1" />
    <input type="checkbox" bind:group=${state} value="2" />
    <input type="checkbox" bind:group=${state} value="3" />
  `;
}

sinuous-bind/bindMedia

Both bind:media and bindMedia work.

Read-only Properties: duration, buffered, seekable, seeking, played, ended

Read/Write Properties: currentTime, playbackRate, paused, volume

Video-only Properties videoWidth, videoHeight

video

function component() {
  const currentTime = o();
  const playbackRate = o();
  const paused = o();
  const volume = o();
  const duration = o();
  const buffered = o();
  const seekable = o();
  const seeking = o();
  const played = o();
  const ended = o();
  const videoWidth = o();
  const videoHeight = o();

  const state = {
    currentTime,
    playbackRate,
    paused,
    volume,
    duration,
    buffered,
    seekable,
    seeking,
    played,
    ended,
    videoWidth,
    videoHeight
  };

  return html`
    <video controls bind:media=${state}>
      <source src=http://video.com type=video/webm />
    </video>
  `;
}

audio

function component() {
  const currentTime = o();
  const playbackRate = o();
  const paused = o();
  const volume = o();
  const duration = o();
  const buffered = o();
  const seekable = o();
  const seeking = o();
  const played = o();
  const ended = o();

  const state = {
    currentTime,
    playbackRate,
    paused,
    volume,
    duration,
    buffered,
    seekable,
    seeking,
    played,
    ended
  };

  return html`
    <audio controls bind:media=${state}>
      <source src=http://audio.com type=audio/webm />
    </audio>
  `;
}

Acknowledgments and Thanks

Wesley Luyten

Rich Harris and the rest of the Svelte team

  • These packages are a port of much of the bind directive functionality present in the Svelte framework

Top categories

Loading Svelte Themes