bêta

Scrub Field <mv-scrub-field>

Champ numérique d’inspecteur : faites glisser son libellé, son icône ou sa valeur latéralement pour ajuster, avec verrouillage du pointeur facultatif, accélération selon la vitesse, un badge qui compte la variation et un rail qui montre où se situe la valeur dans sa plage. Il reste un vrai champ texte : tapez un nombre ou une expression (« 12*2 », « +=4 »), avancez par pas avec les flèches, et une unité s’affiche en suffixe.

CatégorieFormulaires
TypeWeb Component (<mv-scrub-field>)
Statutbêta
Keywordsscrub, number, inspector, spinbutton, pointer-lock, expression, unit, form-associated, editor

When to use

  • A design-tool inspector needs compact numeric fields for size, spacing or opacity that can be dragged
  • Users type expressions like 12*2 or +=4 and see a unit suffix such as px or ms
  • Many numeric properties must fit in a dense side panel

Avoid when

  • The form targets general users who expect visible - and + buttons → use Number Field instead
  • Several linked values like X/Y or width/height are edited together → use Vector Field instead
  • It is a shopping quantity with stock limits → use Quantity instead

Installation

node scripts/add.mjs scrub-field --out ./src/marvelous

Agent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["scrub-field"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/form.js, core/motion.js, components/scrub-field/scrub-field.js, components/scrub-field/scrub-field.css.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<div id="scrub-demo">
  <style>
    #scrub-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
    #scrub-demo .sfd-stage { display: grid; place-items: center; width: 16rem; height: 16rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: repeating-linear-gradient(0deg, transparent 0 15px, color-mix(in oklch, var(--mv-fg) 5%, transparent) 15px 16px), repeating-linear-gradient(90deg, transparent 0 15px, color-mix(in oklch, var(--mv-fg) 5%, transparent) 15px 16px), var(--mv-bg); overflow: hidden; }
    #scrub-demo .sfd-layer { display: grid; align-content: end; width: 8.5rem; height: 8.5rem; padding: .8rem; color: #fff; font: 600 .8rem/1.25 var(--mv-font-sans); background: linear-gradient(150deg, oklch(0.74 0.14 205), oklch(0.56 0.19 262)); }
    #scrub-demo .sfd-layer small { font: 500 .65rem var(--mv-font-mono); opacity: .85; }
    #scrub-demo .sfd-panel { width: 16.5rem; padding: .75rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
    #scrub-demo .sfd-head { display: flex; justify-content: space-between; align-items: baseline; margin: 0 0 .65rem; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
    #scrub-demo .sfd-head span { text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--mv-fg-muted); }
    #scrub-demo .sfd-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .375rem; }
    #scrub-demo .sfd-grid mv-scrub-field { width: 100%; }
    #scrub-demo .sfd-wide { grid-column: 1 / -1; }
    #scrub-demo .sfd-hint { margin: .7rem 0 0; font-size: .7rem; line-height: 1.55; color: var(--mv-fg-muted); }
    #scrub-demo .sfd-hint kbd { padding: 0 .25rem; border: 1px solid var(--mv-border); border-radius: 3px; background: var(--mv-bg-muted); font: 500 .65rem var(--mv-font-mono); }
  </style>
  <div class="sfd-stage"><div class="sfd-layer" id="scrub-demo-layer">Frame<small id="scrub-demo-out"></small></div></div>
  <div class="sfd-panel" role="group" aria-label="Inspector">
    <p class="sfd-head">Appearance <span>Frame 12</span></p>
    <div class="sfd-grid">
      <mv-scrub-field id="scrub-demo-radius" label="Radius" unit="px" value="24" min="0" max="72"></mv-scrub-field>
      <mv-scrub-field id="scrub-demo-opacity" label="Opacity" unit="%" value="100" min="0" max="100"></mv-scrub-field>
      <mv-scrub-field id="scrub-demo-rotate" aria-label="Rotation" unit="°" value="-8" min="-180" max="180" wrap wheel>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 12a8 8 0 1 1-2.34-5.66"/><path d="M20 4v4h-4"/></svg>
      </mv-scrub-field>
      <mv-scrub-field id="scrub-demo-scale" aria-label="Scale" unit="×" value="1" min="0.25" max="2" step="0.05" precision="2">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4h6v6M10 20H4v-6M20 4l-6 6M4 20l6-6"/></svg>
      </mv-scrub-field>
      <mv-scrub-field id="scrub-demo-blur" class="sfd-wide" label="Shadow blur" unit="px" value="28" min="0" max="96"></mv-scrub-field>
      <mv-scrub-field id="scrub-demo-duration" class="sfd-wide" label="Duration (locked)" unit="ms" value="240" step="10" readonly></mv-scrub-field>
    </div>
    <p class="sfd-hint">Drag a label or a value sideways (<kbd>Shift</kbd> ×10, <kbd>Alt</kbd> ×0.1, <kbd>Esc</kbd> cancels). Click to type: <kbd>12*2</kbd>, <kbd>+=4</kbd>, <kbd>/2</kbd>.</p>
  </div>
</div>
<script type="module">
  const $ = (id) => document.getElementById(`scrub-demo-${id}`);
  const layer = $("layer");
  const apply = () => {
    const r = $("radius").value, o = $("opacity").value, rot = $("rotate").value, s = $("scale").value, b = $("blur").value;
    layer.style.borderRadius = `${r}px`;
    layer.style.opacity = String(o / 100);
    layer.style.transform = `rotate(${rot}deg) scale(${s})`;
    layer.style.boxShadow = `0 ${b / 3}px ${b}px -${b / 4}px oklch(0.4 0.16 262 / .5)`;
    $("out").textContent = `${r}px · ${rot}° · ${s}×`;
  };
  $("radius").closest("#scrub-demo").addEventListener("mv-input", apply);
  customElements.whenDefined("mv-scrub-field").then(() => requestAnimationFrame(apply));
</script>

API

Attributes

NameTypeDefaultDescription
valuenumber0 (or min)Initial value, restored on form reset.
labelstringShort label shown in the handle (a <label> linked to the field). Without a label: a child icon (<svg>) and aria-label on the host.
unitstringDisplayed suffix: px, %, °, ms, ×… Ignored when typing (“12px” = 12).
min / maxnumberBounds (clamped, or modulo with wrap).
stepnumber1Step for dragging and arrow keys: Shift ×10, Alt ×0.1; PageUp/PageDown ×10.
precisionnumberstep decimals + 1Number of decimals kept and displayed.
sensitivitynumber2Drag pixels per step (before acceleration).
wrapbooleanThe value loops between min and max (angles).
wheelbooleanA horizontal trackpad swipe scrubs the value; vertical scrolling stays free.
no-lockbooleanDisables pointer lock while dragging. Without lock the system cursor stays visible (ew-resize) and travel stops at the screen edge.
no-accelbooleanDisables acceleration based on gesture speed.
localestringdocument langDisplayed decimal separator (input accepts both “,” and “.”).
name / disabled / readonly / requiredStandard form field behavior.

Properties

NameTypeDescription
valuenumberCurrent value.
formattedstringValue formatted with its unit (“12px”).
editingbooleanTrue while an uncommitted edit is in progress.

Methods

NameDescription
stepUp(n) / stepDown(n)Increments or decrements by n steps.
evaluate(text, current)Module export: evaluates the safe expression (no eval) and returns null if it can’t be fully parsed.
scrubGesture(el, options)Module export: the horizontal drag gesture (threshold, optional pointer lock with a virtual cursor, Escape to cancel) for other controls.

Events

NameDescription
mv-inputOn every change (drag, arrows, committed input). detail: { value, formatted }.
mv-changeOn commit: drag release, Enter, arrow key, blur.
mv-scrubstart / mv-scrubendStart and end of a gesture (a single undo step); detail.cancelled after Escape.

Content structure

NameDescription
child <svg>Icon placed in the handle (aria-hidden).

CSS classes

NameDescription
mv-scrub-field-handle / -label / -input / -unit / -rail / -deltaGenerated parts (the rail appears when both min and max are set).
mv-scrub-field-cursorVirtual cursor appended to <body> only while the pointer is locked.

CSS variables

NameDefaultDescription
--mv-scrub-field-width6.5remWidth.
--mv-scrub-field-heightvar(--mv-control-sm)Height.
--mv-scrub-field-bgField background.
--mv-scrub-field-radiusvar(--mv-radius-sm)Radius.

Accessibility

The value is a real text input with role="spinbutton", aria-valuenow/min/max and an aria-valuetext that includes the unit. It is named by its label (<label for>), aria-label or an external <label>. Keyboard: ↑/↓ (Shift ×10, Alt ×0.1), PageUp/PageDown, Home/End when not typing, Enter to commit, Escape to cancel an edit or a drag. An invalid expression is flagged with aria-invalid. The system cursor is never hidden unless the browser grants pointer lock, when a virtual cursor replaces it. Dragging the value itself is for mouse and pen; on touch, drag the handle and tap the value to type. Motion (step nudge, error shake) is skipped under reduced motion, and forced-colors mode uses system colors for the frame, rail, badge and cursor.

Cette page a été traduite par IA. Signaler un problème de traduction