beta

Scrub Field <mv-scrub-field>

Campo numérico de inspetor: arraste o rótulo, o ícone ou o valor para os lados para ajustar, com bloqueio de ponteiro opcional, aceleração conforme a velocidade, um badge que conta a variação e um trilho que mostra onde o valor está dentro do intervalo. Ele continua sendo um campo de texto de verdade: digite um número ou uma expressão (“12*2”, “+=4”), ajuste com as teclas de seta, e uma unidade aparece como sufixo.

CategoriaFormulários
TipoWeb Component (<mv-scrub-field>)
Statusbeta
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

Instalação

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

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["scrub-field"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): 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.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis 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

NameTipoDefaultDescription
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

NameTipoDescription
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.

Esta página foi traduzida com IA. Informar um problema de tradução