refactor(vue): remove deprecated panel APIs

- Remove FillPickerRoot and useFillPicker from source, exports, and localized docs
- Replace the final PanelRow consumer with accessible auto-layout controls
- Document the canonical panel and fill composition contracts
This commit is contained in:
Danila Poyarkov 2026-07-14 18:13:20 +03:00
parent 27fdef2ea4
commit a62cec00f8
57 changed files with 92 additions and 808 deletions

View file

@ -268,6 +268,7 @@ Self-review checklist:
- `src/components/Shell/**` is for app shell chrome and global app services rendered as components (menu bar, toast viewport, update/status chrome). Shell components may use app shell/editor stores.
- `src/components/properties/**`, `src/components/chat/**`, `src/components/LayerTree/**`, `src/components/Toolbar/**`, and similar folders are feature/domain component namespaces. Keep feature-specific controls there unless they are genuinely reusable UI primitives.
- Treat existing root-level picker/input/control components as migration candidates when touched; do not expand that pattern.
- Property-panel composition uses `PanelGrid`, `PanelFieldGroup`, `PanelItemRow`, and `PropertyItemRow`; do not reintroduce generic row wrappers such as the removed `PanelRow`. Variable-capable fields compose `BindableValue` providers, and fill UIs compose `FillRoot` / `FillSwatch` with a consumer-owned popover rather than rebuilding a combined picker wrapper.
- Test locators follow Playwright's user-facing priority: role/name, label, and text first. Multi-part components expose scoped `data-slot` anatomy; app concepts use semantic attributes such as `data-property`, `data-command`, and `data-node-id` when accessible identity is insufficient. Reserve `data-test-id` for rare integration boundaries such as the canvas/editor host, never add `testId`/`testHook` props, and do not manufacture globally unique compound IDs inside shared components.
- Use reka-ui for UI components (Splitter, ContextMenu, DropdownMenu, etc.)

View file

@ -24,6 +24,7 @@
- Add accessible color-channel sliders, binding-aware fill primitives, and keyboard-operable gradient stops while separating fill state from popover composition.
- Rebuild Fill, Stroke, Effects, and Export controls with shared compact item rows, semantic actions, binding-aware paint fields, and reversible color-picker edits.
- Finish the Design panel migration with compact labeled Typography, Variant, Mask, and Page controls plus semantic empty-section states.
- Remove the deprecated `FillPickerRoot`, `useFillPicker()`, and `PanelRow` compatibility APIs after migrating consumers to the canonical fill and panel primitives.
- Upgrade Vue SDK documentation with shared Tailwind demos, source-generated component API tables, and type-aware Twoslash examples in VitePress.
- Add desktop image drag-and-drop into the Tauri app window.
- Add open-document discovery for live CLI and MCP automation so agents can target the intended document and page.

View file

@ -17,7 +17,6 @@ const SDK_COMPONENT_PAGES = [
{ text: 'ChannelSlider', slug: 'channel-slider', canonical: true },
{ text: 'FillRoot', slug: 'fill-root', canonical: true },
{ text: 'FillSwatch', slug: 'fill-swatch', canonical: true },
{ text: 'FillPickerRoot', slug: 'fill-picker-root', canonical: true },
{ text: 'FontPickerRoot', slug: 'font-picker-root' },
{ text: 'GradientEditorRoot', slug: 'gradient-editor-root' },
{ text: 'GradientEditorBar', slug: 'gradient-editor-bar' },
@ -58,7 +57,6 @@ const SDK_ADVANCED_PAGES = [
{ text: 'useSceneComputed', slug: 'use-scene-computed' },
{ text: 'useColorVariableBinding', slug: 'use-color-variable-binding' },
{ text: 'useColorBindingProvider', slug: 'use-color-binding-provider', canonical: true },
{ text: 'useFillPicker', slug: 'use-fill-picker', canonical: true },
{ text: 'useGradientStops', slug: 'use-gradient-stops' },
{ text: 'useFontPicker', slug: 'use-font-picker' },
{ text: 'usePropScrub', slug: 'use-prop-scrub' },

View file

@ -16,7 +16,6 @@ Diese APIs sind öffentlich, aber spezialisierter als die Haupt-Komponenten- und
## Picker, Variablen, Locale und Editor-Interna
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Niedrigstufen-Füllungs-Kategorie- und Konvertierungs-Hilfsmittel.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` gibt abgeleiteten Füllungs-Kategorie-Zustand sowie Konvertierungs-Hilfsmittel zum Wechseln zwischen einfarbigen, Verlaufs- und Bildfüllungen zurück.
Verwenden Sie es beim Erstellen benutzerdefinierter Füllungs-Picker über `FillPickerRoot` hinaus.
## Verwandte APIs
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Headless popover-basiertes Füllungsauswahl-Primitiv.
---
# FillPickerRoot
`FillPickerRoot` ist ein headless popover-basiertes Füllungsauswahl-Primitiv für einfarbige, Verlaufs- und Bildfüllungen.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Aktueller Füllungswert.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Optionale Klasse für den Popover-Inhalt.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Optionale Klasse für die Standard-Trigger-Schaltfläche.' }
]"
/>
## Ereignisse
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Ausgelöst, wenn sich die Füllung ändert.' }
]"
/>
## Slots
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'Farbmuster-Stil', description: 'Benutzerdefinierter Trigger mit Farbmuster-Hintergrundstil.' },
{ name: 'default', props: 'Füllungszustand + Konvertierungs-Hilfsmittel', description: 'Haupt-Füllungs-Editor-Inhalt.' }
]"
/>
### Trigger-Slot-Props
```ts
{
style: Record<string, string>
}
```
### Standard-Slot-Props
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Beispiel
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Einfarbig</button>
<button @click="toGradient">Verlauf</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## Verwandte APIs
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Komponentenreferenz für headless Vue-Primitive in @open-pencil/vue
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Popover-basiertes Farbauswahl-Primitiv." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Headless Farb-Eingabe-Hilfsmittel." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Popover-basiertes Füllungsauswahl-Primitiv." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Durchsuchbares Schriftauswahl-Primitiv." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Wurzel-Primitiv für Verlaufsbearbeitung." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Ziehbare Verlaufsbalken-Primitiv." />

View file

@ -16,7 +16,6 @@ Estas APIs son públicas, pero son más especializadas que la superficie princip
## Selector, variables, idioma e internos del editor
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Helper de bajo nivel para la categoría de relleno y la conversión.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` devuelve el estado derivado de la categoría del relleno más helpers de conversión para cambiar entre rellenos sólidos, degradados e imágenes.
Úsalo cuando construyas selectores de relleno personalizados más allá de `FillPickerRoot`.
## APIs relacionadas
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Primitivo headless de selector de relleno basado en popover.
---
# FillPickerRoot
`FillPickerRoot` es un primitivo headless de selector de relleno basado en popover para rellenos sólidos, degradados e imágenes.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Valor de relleno actual.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Clase opcional para el contenido del popover.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Clase opcional para el botón trigger por defecto.' }
]"
/>
## Eventos
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Emitido cuando el relleno cambia.' }
]"
/>
## Slots
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'swatch style', description: 'Trigger personalizado con estilo de fondo de muestra.' },
{ name: 'default', props: 'fill state + conversion helpers', description: 'Contenido principal del editor de relleno.' }
]"
/>
### Props del slot trigger
```ts
{
style: Record<string, string>
}
```
### Props del slot default
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Ejemplo
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Sólido</button>
<button @click="toGradient">Degradado</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## APIs relacionadas
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Referencia de componentes para los primitivos headless de Vue en @o
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Primitivo de selector de color basado en popover." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Helper headless de input de color." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Primitivo de selector de relleno basado en popover." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Primitivo de selector de fuente con búsqueda." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Primitivo raíz para la edición de degradados." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Primitivo de barra de degradado arrastrable." />

View file

@ -16,7 +16,6 @@ Ces API sont publiques, mais elles sont plus spécialisées que la surface princ
## Sélecteurs, variables, locale et internals d'éditeur
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Helper de bas niveau pour la catégorie de remplissage et la conversion.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` retourne l'état de catégorie de remplissage dérivé ainsi que des helpers de conversion pour basculer entre les remplissages solides, dégradés et images.
Utilisez-le pour construire des sélecteurs de remplissage personnalisés au-delà de `FillPickerRoot`.
## API associées
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Primitive headless de sélecteur de remplissage basé sur un popover.
---
# FillPickerRoot
`FillPickerRoot` est une primitive headless de sélecteur de remplissage basé sur un popover pour les remplissages solides, dégradés et images.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Valeur de remplissage courante.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Classe optionnelle pour le contenu du popover.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Classe optionnelle pour le bouton déclencheur par défaut.' }
]"
/>
## Événements
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Émis quand le remplissage change.' }
]"
/>
## Slots
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'style de l\'échantillon', description: 'Déclencheur personnalisé avec le style de fond de l\'échantillon.' },
{ name: 'default', props: 'état du remplissage + helpers de conversion', description: 'Contenu principal de l\'éditeur de remplissage.' }
]"
/>
### Props du slot déclencheur
```ts
{
style: Record<string, string>
}
```
### Props du slot par défaut
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Exemple
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Solide</button>
<button @click="toGradient">Dégradé</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## API associées
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Référence des composants pour les primitives Vue headless dans @o
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Primitive de sélecteur de couleur basé sur un popover." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Helper headless pour la saisie de couleur." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Primitive de sélecteur de remplissage basé sur un popover." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Primitive de sélecteur de police avec recherche." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Primitive racine pour l'édition de dégradés." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Primitive de barre de dégradé déplaçable." />

View file

@ -16,7 +16,6 @@ Queste API sono pubbliche, ma sono più specializzate rispetto alla superficie p
## Picker, variabili, locale e funzionamento interno dell'editor
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Helper di basso livello per la categoria del riempimento e la conversione.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` restituisce lo stato della categoria di riempimento derivato più gli helper di conversione per passare tra riempimenti solidi, a gradiente e immagine.
Usalo quando costruisci selettori di riempimento personalizzati oltre a `FillPickerRoot`.
## API correlate
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Primitiva popover headless per la selezione del riempimento.
---
# FillPickerRoot
`FillPickerRoot` è una primitiva popover headless per riempimenti solidi, a gradiente e immagine.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Valore riempimento corrente.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Classe opzionale per il contenuto del popover.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Classe opzionale per il pulsante trigger predefinito.' }
]"
/>
## Eventi
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Emesso quando il riempimento cambia.' }
]"
/>
## Slot
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'stile campione', description: 'Trigger personalizzato con stile di sfondo campione.' },
{ name: 'default', props: 'stato riempimento + helper di conversione', description: 'Contenuto principale dell\'editor del riempimento.' }
]"
/>
### Slot prop del trigger
```ts
{
style: Record<string, string>
}
```
### Slot prop predefiniti
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Esempio
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Solido</button>
<button @click="toGradient">Gradiente</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## API correlate
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Riferimento ai componenti per le primitive Vue headless in @open-pe
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Primitiva popover per la selezione del colore." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Helper headless per l'input del colore." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Primitiva popover per la selezione del riempimento." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Primitiva searchable per la selezione del font." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Primitiva root per l'editor del gradiente." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Primitiva barra trascinabile per il gradiente." />

View file

@ -16,7 +16,6 @@ Te API są publiczne, ale są bardziej wyspecjalizowane niż główna powierzchn
## Selektor, zmienne, język i wewnętrzności edytora
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Niskopoziomowy pomocnik kategorii wypełnienia i konwersji.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` zwraca pochodny stan kategorii wypełnienia plus pomocniki konwersji do przełączania między wypełnieniami jednolitymi, gradientowymi i obrazkowymi.
Użyj go przy budowaniu niestandardowych selektorów wypełnień wykraczających poza `FillPickerRoot`.
## Powiązane API
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Bezstanowy prymityw selektora wypełnień oparty na popover.
---
# FillPickerRoot
`FillPickerRoot` to bezstanowy prymityw selektora wypełnień oparty na popover dla wypełnień jednolitych, gradientowych i obrazkowych.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Bieżąca wartość wypełnienia.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Opcjonalna klasa dla zawartości popover.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Opcjonalna klasa dla domyślnego przycisku wyzwalacza.' }
]"
/>
## Zdarzenia
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Emitowane gdy wypełnienie się zmienia.' }
]"
/>
## Sloty
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'styl próbki', description: 'Niestandardowy wyzwalacz ze stylem tła próbki.' },
{ name: 'default', props: 'stan wypełnienia + pomocniki konwersji', description: 'Główna zawartość edytora wypełnień.' }
]"
/>
### Właściwości slotu trigger
```ts
{
style: Record<string, string>
}
```
### Właściwości slotu default
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Przykład
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Jednolite</button>
<button @click="toGradient">Gradient</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## Powiązane API
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Dokumentacja komponentów bezstanowych prymitywów Vue w @open-penc
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Prymityw selektora kolorów oparty na popover." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Bezstanowy pomocnik pola wejściowego koloru." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Prymityw selektora wypełnień oparty na popover." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Przeszukiwalny prymityw selektora czcionek." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Prymityw korzenia do edycji gradientów." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Przeciągany prymityw paska gradientu." />

View file

@ -17,7 +17,6 @@ These APIs are public, but they are more specialized than the main component and
- [useColorVariableBinding](./use-color-variable-binding)
- [useColorBindingProvider](./use-color-binding-provider)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](../components/fill-root)

View file

@ -1,20 +0,0 @@
---
title: useFillPicker
description: Low-level fill-category and conversion helper.
---
# useFillPicker
::: warning Deprecated
Use `useFill()` or the [FillRoot](../components/fill-root) primitive. `useFillPicker()` remains as a
migration wrapper until the picker cleanup slice.
:::
`useFillPicker(fill, onUpdate)` returns legacy fill category state plus conversion helpers for
switching between solid, gradient, and image fills.
## Related APIs
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,78 +0,0 @@
---
title: FillPickerRoot
description: Headless popover-based fill picker primitive.
---
# FillPickerRoot
::: warning Deprecated
Compose [FillRoot](./fill-root), [FillSwatch](./fill-swatch), and your application's popover instead.
`FillPickerRoot` remains temporarily available for migration compatibility.
:::
`FillPickerRoot` is the legacy popover-based fill picker for solid, gradient, and image fills.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Current fill value.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Optional class for the popover content.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Optional class for the default trigger button.' }
]"
/>
## Events
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Emitted when the fill changes.' }
]"
/>
## Slots
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'swatch style', description: 'Custom trigger with swatch background style.' },
{ name: 'default', props: 'fill state + conversion helpers', description: 'Main fill editor content.' }
]"
/>
### Trigger slot props
```ts
{
style: Record<string, string>
}
```
### Default slot props
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Example
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Solid</button>
<button @click="toGradient">Gradient</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## Related APIs
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -40,7 +40,6 @@ description: Component reference for headless Vue primitives in @open-pencil/vue
<SdkCard title="ChannelSlider" to="/programmable/sdk/api/components/channel-slider" description="Accessible scalar slider for OkHCL channels." />
<SdkCard title="FillRoot" to="/programmable/sdk/api/components/fill-root" description="Fill category state and conversion actions." />
<SdkCard title="FillSwatch" to="/programmable/sdk/api/components/fill-swatch" description="Binding-aware semantic fill preview." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Deprecated popover-based fill picker compatibility primitive." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Searchable font picker primitive." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Root primitive for gradient editing." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Draggable gradient bar primitive." />

View file

@ -16,7 +16,6 @@ description: Низкоуровневые и специализированны
## Пикеры, переменные, локаль и внутренние части редактора
- [useColorVariableBinding](./use-color-variable-binding)
- [useFillPicker](./use-fill-picker)
- [useGradientStops](./use-gradient-stops)
- [useFontPicker](./use-font-picker)
- [useOkHCL](./use-okhcl)

View file

@ -22,4 +22,4 @@ const strokeBinding = useColorVariableBinding('strokes')
- [useFillControls](../composables/use-fill-controls)
- [useStrokeControls](../composables/use-stroke-controls)
- [FillPickerRoot](../components/fill-picker-root)
- [FillRoot](/programmable/sdk/api/components/fill-root)

View file

@ -1,16 +0,0 @@
---
title: useFillPicker
description: Низкоуровневый хелпер категорий заливки и конверсии.
---
# useFillPicker
`useFillPicker(fill, onUpdate)` возвращает производное состояние категории заливки и хелперы конверсии для переключения между сплошными, градиентными и графическими заливками.
Используйте его при создании кастомных пикеров заливки за пределами `FillPickerRoot`.
## Связанные API
- [FillPickerRoot](../components/fill-picker-root)
- [useFillControls](../composables/use-fill-controls)
- [useGradientStops](./use-gradient-stops)

View file

@ -1,73 +0,0 @@
---
title: FillPickerRoot
description: Headless-примитив пикера заливки с поповером.
---
# FillPickerRoot
`FillPickerRoot` — headless-примитив пикера заливки на основе поповера для сплошных, градиентных и графических заливок.
## Props
<SdkPropsTable
:rows="[
{ name: 'fill', type: 'Fill', description: 'Текущее значение заливки.', required: true },
{ name: 'contentClass', type: 'string | undefined', description: 'Опциональный класс для содержимого поповера.' },
{ name: 'swatchClass', type: 'string | undefined', description: 'Опциональный класс для кнопки триггера по умолчанию.' }
]"
/>
## Events
<SdkEventsTable
:rows="[
{ name: 'update', payload: 'fill: Fill', description: 'Генерируется при изменении заливки.' }
]"
/>
## Slots
<SdkSlotsTable
:rows="[
{ name: 'trigger', props: 'swatch style', description: 'Кастомный триггер со стилем фона свотча.' },
{ name: 'default', props: 'fill state + conversion helpers', description: 'Основное содержимое редактора заливки.' }
]"
/>
### Пропы слота trigger
```ts
{
style: Record<string, string>
}
```
### Пропы слота default
```ts
{
fill: Fill
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
toSolid: () => void
toGradient: () => void
toImage: () => void
update: (fill: Fill) => void
}
```
## Пример
```vue
<FillPickerRoot :fill="fill" @update="fill = $event">
<template #default="{ fill, category, toSolid, toGradient, update }">
<div>{{ category }}</div>
<button @click="toSolid">Сплошная</button>
<button @click="toGradient">Градиент</button>
<MyFillEditor :fill="fill" @change="update" />
</template>
</FillPickerRoot>
```
## Связанные API
- [GradientEditorRoot](./gradient-editor-root)

View file

@ -35,7 +35,6 @@ description: Справочник компонентов — headless-прими
<SdkCardGroup>
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Headless-примитив пикера цвета с поповером." />
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Headless-хелпер для ввода цвета." />
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Headless-примитив пикера заливки с поповером." />
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Headless-примитив пикера шрифта с поиском." />
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Корневой примитив редактора градиента." />
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Перетаскиваемый примитив полосы градиента." />

View file

@ -86,7 +86,6 @@ Main structural primitives include:
- `SegmentedControlRoot`
- `ToolbarRoot`
- `ColorPickerRoot`
- `FillPickerRoot`
- `FontPickerRoot`
- `NumberFieldRoot` / `NumberFieldInput` / `NumberFieldValue`
- `BindableValueRoot` / `BindableValueTrigger` / `BindableValuePicker`
@ -185,7 +184,6 @@ These exports are intentionally public, but they are lower-level or more special
- `useBindingProvider()`
- `useNumberBindingProvider()`
- `useFill()`
- `useFillPicker()` (deprecated migration wrapper)
- `useGradientStops()`
- `useFontPicker()`
- `useOkHCL()`

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "Cursor anderer Teilnehmer",
"direction": "Richtung",
"flow": "Fluss",
"layoutHorizontal": "Horizontales Layout",
"layoutVertical": "Vertikales Layout",
"layoutGrid": "Rasterlayout",
"layoutWrap": "Layout umbrechen",
"auto": "Automatisch",
"columns": "Spalten",
"rows": "Zeilen",

View file

@ -96,6 +96,10 @@
"multiplayerCursors": "Cursores de otros participantes",
"direction": "Dirección",
"flow": "Flujo",
"layoutHorizontal": "Diseño horizontal",
"layoutVertical": "Diseño vertical",
"layoutGrid": "Diseño de cuadrícula",
"layoutWrap": "Ajustar diseño",
"auto": "Auto",
"columns": "Columnas",
"rows": "Filas",

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "Curseurs des participants",
"direction": "Direction",
"flow": "Flux",
"layoutHorizontal": "Disposition horizontale",
"layoutVertical": "Disposition verticale",
"layoutGrid": "Disposition en grille",
"layoutWrap": "Retour à la ligne",
"auto": "Auto",
"columns": "Colonnes",
"rows": "Lignes",

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "Cursori degli altri partecipanti",
"direction": "Direzione",
"flow": "Flusso",
"layoutHorizontal": "Layout orizzontale",
"layoutVertical": "Layout verticale",
"layoutGrid": "Layout a griglia",
"layoutWrap": "Layout a capo",
"auto": "Auto",
"columns": "Colonne",
"rows": "Righe",

View file

@ -96,6 +96,10 @@
"multiplayerCursors": "他の参加者のカーソル",
"direction": "方向",
"flow": "フロー",
"layoutHorizontal": "横方向レイアウト",
"layoutVertical": "縦方向レイアウト",
"layoutGrid": "グリッドレイアウト",
"layoutWrap": "折り返しレイアウト",
"auto": "自動",
"columns": "列",
"rows": "行",

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "Kursory innych uczestników",
"direction": "Kierunek",
"flow": "Przepływ",
"layoutHorizontal": "Układ poziomy",
"layoutVertical": "Układ pionowy",
"layoutGrid": "Układ siatki",
"layoutWrap": "Zawijanie układu",
"auto": "Auto",
"columns": "Kolumny",
"rows": "Wiersze",

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "Курсоры участников",
"direction": "Направление",
"flow": "Поток",
"layoutHorizontal": "Горизонтальная раскладка",
"layoutVertical": "Вертикальная раскладка",
"layoutGrid": "Сетка",
"layoutWrap": "Перенос элементов",
"auto": "Авто",
"columns": "Столбцы",
"rows": "Строки",

View file

@ -83,6 +83,10 @@
"multiplayerCursors": "协作光标",
"direction": "方向",
"flow": "流向",
"layoutHorizontal": "水平布局",
"layoutVertical": "垂直布局",
"layoutGrid": "网格布局",
"layoutWrap": "自动换行",
"auto": "自动",
"columns": "列",
"rows": "行",

View file

@ -171,6 +171,10 @@ export const panelMessageDefaults = {
multiplayerCursors: 'Multiplayer cursors',
direction: 'Direction',
flow: 'Flow',
layoutHorizontal: 'Horizontal layout',
layoutVertical: 'Vertical layout',
layoutGrid: 'Grid layout',
layoutWrap: 'Wrap layout',
gapAuto: 'Auto gap',
horizontalGap: 'Horizontal gap',
verticalGap: 'Vertical gap',

View file

@ -114,7 +114,6 @@ export { useVariablesDialogState } from '#vue/variables/dialog/use'
export { useVariablesEditor } from '#vue/variables/editor/use'
export { useVariablesTable } from '#vue/variables/table/use'
export { usePageList } from '#vue/primitives/PageList/usePageList'
export { useFillPicker } from '#vue/primitives/FillPicker/useFillPicker'
export {
fillCategory,
fillIsTransparent,
@ -149,7 +148,6 @@ export type {
FillSwatchSlotProps,
FillSwatchSlots
} from '#vue/primitives/Fill'
export { FillPickerRoot } from '#vue/primitives/FillPicker'
export { FontPickerRoot } from '#vue/primitives/FontPicker'
export type { FontFamilyOption, FontPickerUI } from '#vue/primitives/FontPicker'
export {

View file

@ -1,47 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
import { useFillPicker } from '#vue/primitives/FillPicker/useFillPicker'
import type { Fill } from '@open-pencil/scene-graph'
export interface FillPickerUI {
content?: string
swatch?: string
}
const { fill, ui } = defineProps<{
fill: Fill
ui?: FillPickerUI
}>()
const emit = defineEmits<{ update: [fill: Fill] }>()
const { category, swatchBg, toSolid, toGradient, toImage } = useFillPicker(
computed(() => fill),
(updated) => emit('update', updated)
)
</script>
<template>
<PopoverRoot>
<PopoverTrigger as-child>
<slot name="trigger" :style="{ background: swatchBg }">
<button :class="ui?.swatch" :style="{ background: swatchBg }" />
</slot>
</PopoverTrigger>
<PopoverPortal>
<PopoverContent :class="ui?.content" :side-offset="4" side="left">
<slot
:fill="fill"
:category="category"
:to-solid="toSolid"
:to-gradient="toGradient"
:to-image="toImage"
/>
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>

View file

@ -1,2 +0,0 @@
/** @deprecated Compose `FillRoot`, `FillSwatch`, and a popover in the consumer. */
export { default as FillPickerRoot } from '#vue/primitives/FillPicker/FillPickerRoot.vue'

View file

@ -1,17 +0,0 @@
import type { Ref } from 'vue'
import type { Fill } from '@open-pencil/scene-graph'
import { useFill } from '#vue/primitives/Fill/useFill'
/** @deprecated Use `useFill()` and compose popover behavior in the consumer. */
export function useFillPicker(fill: Ref<Fill>, onUpdate: (fill: Fill) => void) {
const model = useFill(fill, onUpdate)
return {
category: model.category,
swatchBg: model.swatchBackground,
toSolid: model.toSolid,
toGradient: model.toGradient,
toImage: model.toImage
}
}

View file

@ -1,27 +1,27 @@
<script setup lang="ts">
import IconButton from '@/components/ui/IconButton.vue'
import PanelRow from '@/components/ui/panel/PanelRow.vue'
import { computed } from 'vue'
import { useI18n, useLayoutControlsContext } from '@open-pencil/vue'
import IconButton from '@/components/ui/IconButton.vue'
import type { LayoutMode } from '@open-pencil/scene-graph'
const ctx = useLayoutControlsContext()
const { panels } = useI18n()
const layoutModes: { mode: LayoutMode; test: string }[] = [
{ mode: 'HORIZONTAL', test: 'horizontal' },
{ mode: 'VERTICAL', test: 'vertical' },
{ mode: 'GRID', test: 'grid' }
]
const layoutModes = computed<Array<{ mode: LayoutMode; label: string }>>(() => [
{ mode: 'HORIZONTAL', label: panels.value.layoutHorizontal },
{ mode: 'VERTICAL', label: panels.value.layoutVertical },
{ mode: 'GRID', label: panels.value.layoutGrid }
])
</script>
<template>
<PanelRow>
<div class="flex items-center gap-panel">
<IconButton
v-if="ctx.node.layoutMode === 'NONE'"
:label="panels.addAutoLayout"
data-test-id="layout-add-auto"
@click="ctx.editor.setLayoutMode(ctx.node.id, 'VERTICAL')"
>
<icon-lucide-plus class="size-3.5" />
@ -29,34 +29,38 @@ const layoutModes: { mode: LayoutMode; test: string }[] = [
<IconButton
v-else
:label="panels.removeAutoLayout"
data-test-id="layout-remove-auto"
@click="ctx.editor.setLayoutMode(ctx.node.id, 'NONE')"
>
<icon-lucide-minus class="size-3.5" />
</IconButton>
</PanelRow>
</div>
<PanelRow v-if="ctx.node.layoutMode !== 'NONE'" class="mt-1.5" gap="sm">
<div
v-if="ctx.node.layoutMode !== 'NONE'"
class="mt-1.5 flex items-center gap-1"
role="toolbar"
:aria-label="panels.flow"
>
<IconButton
v-for="dir in layoutModes"
:key="dir.mode"
v-for="direction in layoutModes"
:key="direction.mode"
:label="direction.label"
size="md"
:active="dir.mode === 'GRID' ? ctx.isGrid : ctx.node.layoutMode === dir.mode"
:data-test-id="`layout-direction-${dir.test}`"
@click="ctx.editor.setLayoutMode(ctx.node.id, dir.mode)"
:active="direction.mode === 'GRID' ? ctx.isGrid : ctx.node.layoutMode === direction.mode"
@click="ctx.editor.setLayoutMode(ctx.node.id, direction.mode)"
>
<icon-lucide-arrow-right v-if="dir.mode === 'HORIZONTAL'" class="size-3.5" />
<icon-lucide-arrow-down v-else-if="dir.mode === 'VERTICAL'" class="size-3.5" />
<icon-lucide-arrow-right v-if="direction.mode === 'HORIZONTAL'" class="size-3.5" />
<icon-lucide-arrow-down v-else-if="direction.mode === 'VERTICAL'" class="size-3.5" />
<icon-lucide-layout-grid v-else class="size-3.5" />
</IconButton>
<IconButton
v-if="ctx.isFlex"
:label="panels.layoutWrap"
size="md"
:active="ctx.node.layoutWrap === 'WRAP'"
data-test-id="layout-direction-wrap"
@click="ctx.updateProp('layoutWrap', ctx.node.layoutWrap === 'WRAP' ? 'NO_WRAP' : 'WRAP')"
>
<icon-lucide-wrap-text class="size-3.5" />
</IconButton>
</PanelRow>
</div>
</template>

View file

@ -1,33 +0,0 @@
<script lang="ts">
import type { VNode } from 'vue'
import type { ClassValue } from 'tailwind-variants'
import type { PanelRowTheme } from '@/theme/panel/row'
export interface PanelRowProps {
cols?: keyof PanelRowTheme['variants']['columns']
gap?: keyof PanelRowTheme['variants']['gap']
class?: ClassValue
}
export interface PanelRowSlots {
default(): VNode[]
}
</script>
<script setup lang="ts">
import { tv } from 'tailwind-variants'
import theme from '@/theme/panel/row'
/** @deprecated Prefer PanelGrid for new property-panel layouts. */
const { cols = 'auto', gap = 'md', class: className } = defineProps<PanelRowProps>()
defineSlots<PanelRowSlots>()
const panelRow = tv(theme)
</script>
<template>
<div :class="panelRow({ columns: cols, gap, class: className })">
<slot />
</div>
</template>

View file

@ -3,5 +3,4 @@ export { default as PanelGrid } from './PanelGrid.vue'
export { default as PanelHeader } from './PanelHeader.vue'
export { default as PanelItemRow } from './PanelItemRow.vue'
export { default as PanelRail } from './PanelRail.vue'
export { default as PanelRow } from './PanelRow.vue'
export { default as PanelSection } from './PanelSection.vue'

View file

@ -1,21 +0,0 @@
const panelRowTheme = {
base: 'flex items-center',
variants: {
columns: {
auto: '',
two: '[&>*]:min-w-0 [&>*]:flex-1',
fill: '[&>*:first-child]:min-w-0 [&>*:first-child]:flex-1'
},
gap: {
sm: 'gap-0.5',
md: 'gap-panel'
}
},
defaultVariants: {
columns: 'auto' as const,
gap: 'md' as const
}
}
export type PanelRowTheme = typeof panelRowTheme
export default panelRowTheme

View file

@ -54,14 +54,27 @@ test('Shift+A wraps selection in auto-layout frame', async () => {
canvas.assertNoErrors()
})
test('direction button toggles to VERTICAL', async () => {
test('direction controls switch between vertical grid and horizontal layout', async () => {
await selectFrame()
const layout = propertySection(page, 'Auto layout')
await page.getByTestId('layout-direction-vertical').click()
await layout.getByRole('button', { name: 'Vertical layout' }).click()
await canvas.waitForRender()
expect(expectDefined(await getNodeById(page, frameId), 'frame').layoutMode).toBe('VERTICAL')
const frame = await getNodeById(page, frameId)
expect(expectDefined(frame, 'frame').layoutMode).toBe('VERTICAL')
await layout.getByRole('button', { name: 'Grid layout' }).click()
await canvas.waitForRender()
expect(expectDefined(await getNodeById(page, frameId), 'frame').layoutMode).toBe('GRID')
await layout.getByRole('button', { name: 'Horizontal layout' }).click()
await canvas.waitForRender()
expect(expectDefined(await getNodeById(page, frameId), 'frame').layoutMode).toBe('HORIZONTAL')
await layout.getByRole('button', { name: 'Vertical layout' }).click()
await propertyField(page, 'width').getByRole('combobox', { name: 'Width' }).click()
await page.getByRole('option', { name: 'Hug' }).click()
await canvas.waitForRender()
expect(expectDefined(await getNodeById(page, frameId), 'frame').layoutMode).toBe('VERTICAL')
canvas.assertNoErrors()
})
@ -102,7 +115,7 @@ test('gap menu sets auto space-between alignment', async () => {
test('wrap mode exposes cross-axis gap control', async () => {
await selectFrame()
await page.getByTestId('layout-direction-wrap').click()
await propertySection(page, 'Auto layout').getByRole('button', { name: 'Wrap layout' }).click()
await canvas.waitForRender()
const before = await getNodeById(page, frameId)
@ -253,7 +266,9 @@ test('alignment grid center sets CENTER alignment', async () => {
test('remove auto-layout sets layoutMode to NONE', async () => {
await selectFrame()
await page.getByTestId('layout-remove-auto').click()
await propertySection(page, 'Auto layout')
.getByRole('button', { name: 'Remove auto layout' })
.click()
await canvas.waitForRender()
const frame = await getNodeById(page, frameId)