PointsEditor
同じ領域の中で 1 つずつドラッグできる、点の集まりのエディタ。
点はコンテナの中の位置で置かれるので、値は各軸 0..1 の { x, y } で、y は下に向かって増えます。点は ctrl / meta で選択するか、<PointsEditor.SelectionBox /> をドラッグして囲むことで、まとめて動かせます。
点をドラッグするか、クリックして矢印キーで動かします。ホイールは、エディタの上ならどこからでもフォーカス中の点を動かし、shift で x 軸に切り替わります。
- React
- Svelte
- Vue
function App() { const [points, setPoints] = useState({ 'p-0': { x: 0, y: 0.5 }, 'p-1': { x: 0.35, y: 0.15 }, 'p-2': { x: 0.7, y: 0.85 }, 'p-3': { x: 1, y: 0.5 }, }) return ( <div> <PointsEditor.Root className={pointsEditorTheme.root} selectable> <PointsEditor.Background style={{ background: '#f2f4f5', borderRadius: 4 }} /> <PointsEditor.Container> {Object.entries(points).map(([id, point]) => ( <PointsEditor.Point className={pointsEditorTheme.point} key={id} id={id} value={point} onChange={(value) => setPoints((prev) => ({ ...prev, [id]: value })) } /> ))} <PointsEditor.SelectionBox className={pointsEditorTheme.selectionBox} /> </PointsEditor.Container> </PointsEditor.Root> <p> {Object.entries(points) .map(([id, { x, y }]) => `${id}: (${x}, ${y})`) .join(' / ')} </p> </div> ) }
/** The demo theme for `PointsEditor`, as a CSS Module: one class per part, applied by* the caller. Copy this file into your project and edit it.*/.root {width: 200px;height: 100px;}.point {--color: oklch(59.5% 0.17548 266.373);width: 16px;height: 16px;border-radius: 50%;outline: none;cursor: grab;background-color: var(--color);&[data-dragging] {cursor: inherit;}/* Dragging does nothing while readonly, so do not offer to. */&[data-readonly] {cursor: default;}/* An outline rather than a box-shadow: focus is usually drawn with ashadow, and a selection that looks the same is a selection nobody cantell from focus. */&[data-selected] {outline: 2px solid var(--color);outline-offset: 2px;}&:focus-within {box-shadow: 0 0 0 3px color-mix(in srgb, var(--color) 20%, transparent);}}/* The selection box, while a drag on empty space is running. */.selectionBox {--color: oklch(59.5% 0.17548 266.373);border: 1px solid var(--color);background-color: color-mix(in srgb, var(--color) 12%, transparent);}
<script lang="ts">
import { PointsEditor } from '@tremolo-ui/svelte'
import type { PointPosition } from '@tremolo-ui/dom'
// Copy this file from the Styling page into your own project.
import pointsEditorTheme from './PointsEditor.module.css'
let points: Record<string, PointPosition> = $state({
'p-0': { x: 0, y: 0.5 },
'p-1': { x: 0.35, y: 0.15 },
'p-2': { x: 0.7, y: 0.85 },
'p-3': { x: 1, y: 0.5 },
})
</script>
<PointsEditor.Root class={pointsEditorTheme.root} selectable>
<PointsEditor.Background style="background: #f2f4f5; border-radius: 4px" />
<PointsEditor.Container>
{#each Object.keys(points) as id (id)}
<PointsEditor.Point
class={pointsEditorTheme.point}
{id}
value={points[id]}
onChange={(value) => (points[id] = value)}
/>
{/each}
<PointsEditor.SelectionBox class={pointsEditorTheme.selectionBox} />
</PointsEditor.Container>
</PointsEditor.Root>
<script setup lang="ts">
import { reactive } from 'vue'
import type { PointPosition } from '@tremolo-ui/dom'
import {
PointsEditor,
PointsEditorBackground,
PointsEditorContainer,
PointsEditorPoint,
PointsEditorSelectionBox,
} from '@tremolo-ui/vue'
// Copy this file from the Styling page into your own project.
import pointsEditorTheme from './PointsEditor.module.css'
const points = reactive<Record<string, PointPosition>>({
'p-0': { x: 0, y: 0.5 },
'p-1': { x: 0.35, y: 0.15 },
'p-2': { x: 0.7, y: 0.85 },
'p-3': { x: 1, y: 0.5 },
})
</script>
<template>
<PointsEditor :class="pointsEditorTheme.root" selectable>
<PointsEditorBackground style="background: #f2f4f5; border-radius: 4px" />
<PointsEditorContainer>
<PointsEditorPoint
v-for="(_, id) in points"
:id="id"
:key="id"
v-model="points[id]"
:class="pointsEditorTheme.point"
/>
<PointsEditorSelectionBox :class="pointsEditorTheme.selectionBox" />
</PointsEditorContainer>
</PointsEditor>
</template>
インポート
- React
- Svelte
- Vue
import { PointsEditor } from '@tremolo-ui/react'
import { PointsEditor } from '@tremolo-ui/svelte'
import {
PointsEditor,
PointsEditorBackground,
PointsEditorContainer,
PointsEditorPoint,
PointsEditorSelectionBox,
} from '@tremolo-ui/vue'
API リファレンス
各パートには、そのパート独自の props を載せています。これに加えて、描画する要素の属性(className や style など)も受け取ります。
Svelte / Vue でも props は同じで、書き方だけがそれぞれの流儀になります(className ではなく class、bind:value や v-model、中身は snippet やスロットなど)。Svelte と Vue を参照してください。
状態は data 属性として要素に乗っているので、セレクタ 1 つで届きます(スタイリングを参照)。属性はその状態が on の間だけ付きます。
Root
選択と、すべての点が共有する設定を持ちます。
| Prop | 型 | 既定値 |
|---|---|---|
disabled | boolean | — |
readOnly | boolean | — |
dragCursor | Cursor | 'grabbing' |
wheel | ModifierValue<InputEventOption> | null | ['normalized', 0.01] |
keyboard | ModifierValue<InputEventOption> | null | { default: ['normalized', 0.01], shift: ['normalized', 0.001] } |
dragSensitivity | ModifierValue<number> | { default: 1, shift: 0.1 } |
selectable | boolean | false |
selection | string[] | — |
defaultSelection | string[] | — |
onSelectionChange | (selection: string[]) => void | — |
children* | ReactNode | — |
すべての説明: PointsEditorProps
エディタの幅と高さは CSS または style で指定します。
| Data 属性 | 値 |
|---|---|
[data-disabled] | disabled のときに付きます |
[data-readonly] | readonly のときに付きます |
Background
点の後ろに描くもの。グラフ、キャンバス、画像など、点を重ねる下地をこの中に置きます。
独自の props は無く、<div> の属性を受け取ります。
Container
点を置く領域。空いている場所でドラッグすると、選択の枠を描きます。PointsEditor.Point はこの中に置きます。
独自の props は無く、<div> の属性を受け取ります。
Point
ドラッグ・矢印キー・ホイールで動かす 1 つの点。中にある軸ごとの 2 つの視覚的に隠れた range input が、フォーカスを受け取り ARIA を持ちます。子は点の中に描かれ、省略するとテーマの点がそのまま出ます。
| Prop | 型 | 既定値 |
|---|---|---|
value* | T | — |
id | string | — |
min | Partial<PointPosition> | — |
max | Partial<PointPosition> | — |
color | string | — |
disabled | boolean | — |
readOnly | boolean | — |
wheel | ModifierValue<InputEventOption> | null | — |
keyboard | ModifierValue<InputEventOption> | null | — |
aria-label | string | Partial<Record<"x" | "y", string>> | — |
aria-valuetext | string | Partial<Record<"x" | "y", string>> | — |
onChange | (value: PointPosition) => void | — |
onChangeStart | (value: PointPosition, source: ChangeSource) => void | — |
onChangeEnd | (value: PointPosition, source: ChangeSource) => void | — |
changeEndDelay | number | 500 |
すべての説明: PointsEditorPointProps
| カスタムプロパティ | 書き込む prop | 決めるもの |
|---|---|---|
--color | color | 点の色。 |
点の幅と高さは CSS または style で指定します。
| Data 属性 | 値 |
|---|---|
[data-disabled] | 点か Root が disabled のときに付きます |
[data-readonly] | 点か Root が readonly のときに付きます |
[data-dragging] | 点をドラッグしている間に付きます |
[data-selected] | 点が選択に含まれている間に付きます |
SelectionBox
空いている場所でのドラッグが描く枠で、枠が覆った点が選択されます。ドラッグしていない間は、子も含めて何も描きません。
独自の props は無く、<div> の属性を受け取ります。