def()

def() demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.

App.tsx

import './App.css';

import { Component, def } from '@expressive/react';

// `def` is the low-level primitive every other instruction is built on. Its
// factory runs at init and returns a property descriptor; a `set` that
// returns a value rewrites each assignment. Two tiny reusable instructions:

// Keeps a number within range.
function clamped(value: number, min: number, max: number) {
  return def<number>(() => ({
    value,
    set: (next) => Math.min(max, Math.max(min, next))
  }));
}

// Coerces every assignment into a URL-safe slug.
function slug(value = '') {
  return def<string>(() => ({
    value,
    set: (next) => next.toLowerCase().replace(/[^a-z0-9]+/g, '-')
  }));
}

class Profile extends Component {
  volume = clamped(5, 0, 10);
  handle = slug('');

  render() {
    const { volume, handle } = this;

    return (
      <div className="container">
        <h1>Custom Instruction</h1>

        <p>
          Both fields are governed by instructions we defined with{' '}
          <code>def</code> - the same primitive <code>set</code> and{' '}
          <code>get</code> are built on.
        </p>

        <label>
          Volume <small>(clamped to 0–10)</small>
          <div className="stepper">
            <button onClick={() => (this.volume -= 3)}>−3</button>
            <output>{volume}</output>
            <button onClick={() => (this.volume += 3)}>+3</button>
          </div>
        </label>

        <label>
          Handle <small>(slugified on input)</small>
          <input
            value={handle}
            placeholder="Type A Name"
            onChange={(e) => (this.handle = e.target.value)}
          />
        </label>
      </div>
    );
  }
}

export default () => <Profile />;

App.css

.container label {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  width: 100%;
  padding: var(--s4);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.container label small {
  font-weight: 400;
  color: var(--muted);
}

.stepper {
  display: flex;
  align-items: center;
  gap: var(--s3);
}

.stepper button {
  min-width: 3rem;
  font-family: var(--font-mono);
}

.stepper output {
  flex: 1;
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  text-align: center;
  color: var(--accent);
}