bêta
Vector Field <mv-vector-field>
Saisies X/Y, X/Y/Z ou W/H compactes pour les inspecteurs : chaque axe est un spinbutton dans lequel vous pouvez taper, avancer par pas au clavier ou ajuster en faisant glisser sa lettre, avec un filet qui montre où se situe la valeur dans sa plage et un verrouillage facultatif des proportions. La valeur « x,y » est associée aux formulaires.
| Catégorie | Formulaires |
|---|---|
| Type | Web Component (<mv-vector-field>) |
| Statut | bêta |
| Keywords | vector, xy, xyz, size, aspect-ratio, lock, scrub, spinbutton, inspector, form-associated, editor |
When to use
- An inspector edits a position as X/Y or X/Y/Z values side by side
- Width and height must be edited with a lock that keeps the aspect ratio
- A form submits several related numbers as one comma-separated value
Avoid when
- Only one number is edited → use Number Field instead
- The audience is non-technical and expects labeled regular fields → use Number Field instead
Installation
node scripts/add.mjs vector-field --out ./src/marvelousAgent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["vector-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, components/vector-field/vector-field.js, components/vector-field/vector-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="vec-demo">
<style>
#vec-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
#vec-demo .vfd-stage { position: relative; width: 18rem; height: 15rem; overflow: hidden; border-radius: var(--mv-radius-xl); background: radial-gradient(circle at 1px 1px, color-mix(in oklch, var(--mv-fg) 12%, transparent) 1px, transparent 0) 0 0 / 12px 12px, var(--mv-bg); border: 1px solid var(--mv-border); perspective: 600px; }
#vec-demo .vfd-card { position: absolute; left: 0; top: 0; display: grid; place-items: center; border-radius: 14px; color: #fff; font: 600 .7rem var(--mv-font-mono); background: linear-gradient(135deg, oklch(0.72 0.16 250), oklch(0.6 0.2 285)); box-shadow: 0 0 0 1px oklch(1 0 0 / .15) inset, 0 14px 30px -14px oklch(0.4 0.2 270 / .7); outline: 1px dashed color-mix(in oklch, var(--mv-accent) 70%, transparent); outline-offset: 4px; }
#vec-demo .vfd-panel { display: grid; gap: .5rem; width: 17rem; padding: .75rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
#vec-demo .vfd-head { margin: 0; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
#vec-demo .vfd-row { display: grid; gap: .3rem; font-size: .72rem; color: var(--mv-fg-muted); }
</style>
<div class="vfd-stage"><div class="vfd-card" id="vec-demo-card"></div></div>
<div class="vfd-panel" role="group" aria-label="Inspector">
<p class="vfd-head">Layout</p>
<div class="vfd-row"><span>Position</span>
<mv-vector-field id="vec-demo-pos" label="Position" name="position" axes="X,Y" unit="px" value="64,48" min="0,0" max="240,200"></mv-vector-field>
</div>
<div class="vfd-row"><span>Size</span>
<mv-vector-field id="vec-demo-size" label="Size" name="size" axes="W,H" axis-labels="Width,Height" unit="px" value="144,96" min="24" max="280" linkable linked></mv-vector-field>
</div>
<div class="vfd-row"><span>3D rotation</span>
<mv-vector-field id="vec-demo-rot" label="3D rotation" name="rotation" axes="X,Y,Z" unit="°" value="0,-18,0" min="-180" max="180"></mv-vector-field>
</div>
</div>
</div>
<script type="module">
const $ = (id) => document.getElementById(`vec-demo-${id}`);
const apply = () => {
const [x, y] = $("pos").values, [w, hh] = $("size").values, [rx, ry, rz] = $("rot").values;
const c = $("card");
c.style.width = `${w}px`;
c.style.height = `${hh}px`;
c.style.transform = `translate(${x}px, ${y}px) rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`;
c.textContent = `${Math.round(w)} × ${Math.round(hh)}`;
};
document.getElementById("vec-demo").addEventListener("mv-input", apply);
customElements.whenDefined("mv-vector-field").then(() => requestAnimationFrame(apply));
</script>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
value | string | Comma-separated values: “120,48”. Restored on reset. | |
axes | string | X,Y | Short axis labels (2 to 4), shown in each handle. |
axis-labels | string | Accessible axis names (“Width,Height”); otherwise “<label> <axis>”. | |
label | string | Group name (role=group). | |
unit / min / max / step | string | One value for all axes or one per axis (“0,0,-100”). Values are clamped to min/max; a bounded axis shows its position as a hairline. | |
precision | number | step decimals + 1 | Decimals kept, for all axes. |
linkable | boolean | Shows the aspect-ratio lock button. | |
linked | boolean | Aspect ratio locked: changing one axis scales the others (ratio captured when locking). | |
name / disabled / readonly | Standard behavior. |
Properties
| Name | Type | Description |
|---|---|---|
value | string | “x,y[,z]”. |
values | number[] | Numeric values (read/write). Assignable before the element is defined. |
fields | HTMLInputElement[] | The generated axis inputs (role=spinbutton), read-only. |
strings | { lock } | Default texts, for translation (“Lock aspect ratio”). Assignable before the element is defined. |
Methods
| Name | Description |
|---|---|
focus() | Focuses the first axis. |
Events
| Name | Description |
|---|---|
mv-input | On every value change (drag, arrow keys, committed typing). detail: { value, values, axis }. |
mv-change | On commit: drag release, arrow key, Enter, blur. detail: { value, values, axis }. Native input/change events of the inner inputs do not bubble out. |
mv-link | The lock was toggled. detail: { linked }. |
CSS classes
| Name | Description |
|---|---|
mv-vector-field-axis | One axis cell; data-scrubbing while its letter is dragged. |
mv-vector-field-handle / -input / -unit / -meter | Generated parts: draggable letter, input, unit suffix, range hairline. |
mv-vector-field-link | Lock button (aria-pressed). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-vector-field-width | 100% | Group width. |
--mv-vector-field-gap | var(--mv-space-1) | Gap between fields. |
Accessibility
role="group" named by label. Each axis is a text input with role="spinbutton", aria-valuenow/min/max and an aria-valuetext that includes the unit (“64 px”), named by axis-labels or “<label> <axis>”. Keyboard: ↑/↓ step (Shift ×10, Alt ×0.1), PageUp/PageDown ×10, Home/End jump to min/max, type a value then Enter or Tab to commit, Escape to revert the typing or cancel a drag. An unreadable value is flagged with aria-invalid until fixed or left. Dragging the axis letter is a pointer shortcut only (no pointer lock, system cursor kept). The lock is a toggle button (aria-pressed) named “Lock aspect ratio”. Forced colors keep borders, focus, the range hairline and the pressed lock visible.