refactor(words): drop vertical toolbar orientation

The companion drawer + new floating overlays (image bar, block handle,
block inserter) assume a horizontal toolbar above the content. The
vertical layout was a CSS-grid two-column repositioning that fights
that geometry and was the cause of the new design breaking when the
user switched to vertical.

`horizontal` is the only orientation now. Cuts:

Morfo (`morfo/components/words.ts`)
  - Toolbar + ToolbarGroup parts: `data-orientation` becomes a literal
    `'horizontal'` (no longer reads from `propRef('orientation')`).
  - Same for the toolbar's `aria-orientation`.

Soma (`soma/components/words/types.ts`, `exports.ts`, provider)
  - Removed `WordsToolbarOrientation` type entirely.
  - Removed `orientation?` from WordsToolbarProps and WordsToolbarGroupProps.
  - Provider opts no longer read or pass through `orientation`.

Eidos (`eidos/components/words/{types,words.svelte,words-toolbar.svelte,words.css}`)
  - `WordsProps.toolbarOrientation` removed.
  - `<Words.Toolbar>` / `<Words.ToolbarGroup>` no longer accept or
    forward `orientation`.
  - Six chunks of CSS that targeted `[data-orientation='vertical']`
    deleted (toolbar layout, content grid placement, status, find-replace,
    link-editor, placeholder).

Demo (`web/routes/uix/components/words/+page.svelte`)
  - `orientations` const, `orientation` state, the chip-control UI,
    the `toolbarOrientation={orientation}` prop, and the corresponding
    line in the generated code snippet all removed.
  - Recipe-selector entry for `[data-orientation]` deleted.
  - A11y row text simplified.

Verified: `npm run check` clean (only pre-existing 6 errors unrelated);
soma words tests 152/152 pass. Eidos vertical CSS is gone end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent a62b23538d
commit 71c24dca9b

@ -20,8 +20,7 @@ import type {
WordsHeadingLevel,
WordsListKind,
WordsSlashCommandId,
WordsSlashCommandItem,
WordsToolbarOrientation
WordsSlashCommandItem
} from '$soma/components/words';
import type { ButtonRounded, ButtonSize, ButtonVariant } from '$uix/eidos/components/button';
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
@ -141,8 +140,6 @@ export type WordsProps = ProviderProps & {
color?: WordsColor;
/** Optional default toolbar renderer. Manual compound children still win for custom UI. */
toolbar?: WordsToolbarConfig;
/** Orientation for the toolbar rendered from the `toolbar` prop. @default 'horizontal' */
toolbarOrientation?: WordsToolbarOrientation;
/**
* Toolbar layout strategy (`grouped` family popovers vs `inline` flat
* commands). Accepts ResponsiveProp for breakpoint-aware switching —

@ -5,7 +5,6 @@
import type { WordsToolbarProps } from './types';
let {
orientation = 'horizontal',
'aria-label': ariaLabel = 'Formatting toolbar',
children,
child: outerChild,
@ -15,9 +14,9 @@
const toolbarAriaLabel = $derived(ariaLabel ?? undefined);
</script>
<Words.Toolbar {...rest} {orientation} aria-label={toolbarAriaLabel}>
<Words.Toolbar {...rest} aria-label={toolbarAriaLabel}>
{#snippet child({ props: wordsProps })}
<Toolbar {orientation} aria-label={toolbarAriaLabel} size="sm" variant="ghost">
<Toolbar aria-label={toolbarAriaLabel} size="sm" variant="ghost">
{#snippet child({ props: toolbarProps })}
{@const props = mergeProps(toolbarProps, wordsProps)}
{#if outerChild}

@ -125,12 +125,6 @@
opacity: var(--words-disabled-opacity);
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
grid-template-rows: auto auto;
}
/* ── Companion Drawer (right-rail) ───────────────────────────────────────
Layout: when the editor host contains a [data-words-drawer], place
the drawer in a dedicated right column that spans toolbar+content+
@ -1022,10 +1016,6 @@
color: var(--words-threat-text);
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']):has([data-words-find-replace]) {
grid-template-rows: auto auto auto;
}
[data-words-toolbar] {
/* Horizontal layout: single row with horizontal scroll on narrow
viewports. Was previously `flex-wrap: wrap` which produced 2-3
@ -1063,20 +1053,6 @@
if the toolbar's intrinsic content fits, the mask still applies but
is harmless (it fades empty space). Keeping for simplicity. */
[data-words-toolbar][data-orientation='vertical'] {
grid-column: 1;
grid-row: 1;
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
inline-size: max-content;
border-block-end: 0;
border-inline-end: var(--words-border-width) solid var(--words-toolbar-border);
overflow-x: visible;
overflow-y: auto;
mask-image: none;
}
[data-words-toolbar-group] {
display: inline-flex;
flex-wrap: nowrap;
@ -1086,12 +1062,6 @@
flex-shrink: 0;
}
[data-words-toolbar-group][data-orientation='vertical'] {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
}
[data-words-bubble-menu] {
--_words-bubble-offset: var(--words-toolbar-py-md);
--_words-bubble-y-shift: calc(-100% - var(--_words-bubble-offset));
@ -1677,15 +1647,6 @@
font-size: var(--words-status-font-size);
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) [data-words-link-editor] {
grid-template-columns: minmax(0, 1fr) auto;
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']):has([data-words-link-editor])
[data-words-status] {
grid-row: 3;
}
[data-words-placeholder] {
display: block;
padding-block-start: var(--_words-content-py);
@ -1694,12 +1655,6 @@
pointer-events: none;
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) [data-words-placeholder] {
grid-column: 2;
grid-row: 1;
z-index: 1;
}
[data-words-placeholder][hidden],
[data-words-hidden-input][hidden] {
display: none;
@ -1723,11 +1678,6 @@
-webkit-user-select: text;
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) [data-words-content] {
grid-column: 2;
grid-row: 1;
}
[data-words-placeholder]:not([hidden]) + [data-words-content] {
padding-block-start: var(--words-content-py-after-placeholder);
}
@ -2161,11 +2111,6 @@
font-size: var(--words-status-font-size);
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) [data-words-status] {
grid-column: 1 / -1;
grid-row: 2;
}
[data-words-status][data-status='error'] {
color: var(--words-status-error-color);
}
@ -2198,16 +2143,6 @@
color: var(--_words-accent-text);
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']) [data-words-find-replace] {
grid-column: 1 / -1;
grid-row: 2;
}
[data-words]:has([data-words-toolbar][data-orientation='vertical']):has([data-words-find-replace])
[data-words-status] {
grid-row: 3;
}
[data-words-find-row],
[data-words-replace-row] {
display: flex;

@ -32,7 +32,6 @@
variant = 'surface',
color = 'primary',
toolbar = false,
toolbarOrientation = 'horizontal',
toolbarLayout = 'grouped',
value = $bindable(),
selection = $bindable(null),
@ -302,9 +301,9 @@
{#snippet children(snippetProps)}
{#if visibleToolbarGroups.length}
<Toolbar orientation={toolbarOrientation} aria-label="Formatting toolbar">
<Toolbar aria-label="Formatting toolbar">
{#each visibleToolbarGroups as group}
<ToolbarGroup orientation={toolbarOrientation}>
<ToolbarGroup>
{#each group as item}
{#if isToolbarFamilyConfig(item)}
{#if resolvedToolbarLayout === 'inline'}

@ -381,13 +381,13 @@ export const wordsMorfo = {
data: [
{
attr: 'data-orientation',
values: ['horizontal', 'vertical'],
value: v.propRef('orientation')
values: ['horizontal'],
value: v.literal('horizontal')
},
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }
],
aria: [
{ attr: 'aria-orientation', value: v.propRef('orientation') },
{ attr: 'aria-orientation', value: v.literal('horizontal') },
{
attr: 'aria-label',
value: v.translationRef('#?components.words.toolbar|Editor toolbar'),
@ -406,8 +406,8 @@ export const wordsMorfo = {
data: [
{
attr: 'data-orientation',
values: ['horizontal', 'vertical'],
value: v.propRef('orientation')
values: ['horizontal'],
value: v.literal('horizontal')
}
],
aria: []

@ -21,7 +21,6 @@ export type {
WordsSlashCommandId,
WordsSlashCommandItem,
WordsStatus,
WordsToolbarOrientation,
WordsProps as ProviderProps,
WordsContentProps as ContentProps,
WordsPlaceholderProps as PlaceholderProps,

@ -24,7 +24,6 @@ import type {
export type WordsSelectionKind = 'none' | 'collapsed' | 'range';
export type WordsStatus = 'idle' | 'error';
export type WordsToolbarOrientation = 'horizontal' | 'vertical';
export type WordsCommitReason = 'programmatic' | 'blur' | 'button';
export type WordsBubbleMenuSide = 'top' | 'bottom';
export type WordsSlashCommandId =
@ -313,13 +312,11 @@ export type WordsPlaceholderProps = WithChild<{ id?: string }> &
export type WordsToolbarProps = WithChild<{
id?: string;
orientation?: WordsToolbarOrientation;
}> &
Without<PrimitiveDivAttributes, { role?: unknown; 'aria-orientation'?: unknown }>;
export type WordsToolbarGroupProps = WithChild<{
id?: string;
orientation?: WordsToolbarOrientation;
}> &
Without<PrimitiveDivAttributes, { role?: unknown }>;

@ -22,8 +22,7 @@ import type {
WordsSelectionKind,
WordsSlashCommandId,
WordsSlashCommandItem,
WordsStatus,
WordsToolbarOrientation
WordsStatus
} from './types';
import {
actionFromBeforeInput,
@ -98,10 +97,8 @@ interface WordsOpts
interface WordsContentOpts extends WithRefOpts {}
interface WordsPlaceholderOpts extends WithRefOpts {}
interface WordsToolbarOpts
extends WithRefOpts, ActiveProps<{ orientation: WordsToolbarOrientation }> {}
interface WordsToolbarGroupOpts
extends WithRefOpts, ActiveProps<{ orientation: WordsToolbarOrientation }> {}
interface WordsToolbarOpts extends WithRefOpts {}
interface WordsToolbarGroupOpts extends WithRefOpts {}
interface WordsBubbleMenuOpts extends WithRefOpts, ActiveProps<{ side: WordsBubbleMenuSide }> {}
interface WordsSlashMenuOpts extends WithRefOpts {}
interface WordsCommandButtonOpts
@ -1833,7 +1830,6 @@ export class WordsToolbarProvider {
ref: opts.ref,
owner: this,
props: {
orientation: () => this.opts.orientation.current,
disabled: () => this.provider.isDisabled
},
syncAttrs: true
@ -1843,8 +1839,7 @@ export class WordsToolbarProvider {
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'toolbar' as const,
'aria-orientation': this.opts.orientation.current
role: 'toolbar' as const
} as const)
);
}
@ -1865,9 +1860,7 @@ export class WordsToolbarGroupProvider {
id: opts.id,
ref: opts.ref,
owner: this,
props: {
orientation: () => this.opts.orientation.current
},
props: {},
syncAttrs: true
});
}

@ -18,8 +18,7 @@
WordsExportFormat,
WordsInline,
WordsImportFormat,
WordsSelection,
WordsToolbarOrientation
WordsSelection
} from '$soma/components/words';
import {
parseWordsHtml,
@ -92,7 +91,6 @@
'threat',
'loss'
];
const orientations: WordsToolbarOrientation[] = ['horizontal', 'vertical'];
const presets: DocumentPreset[] = ['sample', 'empty', 'long'];
const exportFormats: WordsExportFormat[] = ['json', 'text', 'html', 'markdown'];
const importFormats: WordsImportFormat[] = ['json', 'text', 'html', 'markdown'];
@ -193,7 +191,6 @@
let showSlashMenu = $state(true);
let showStatus = $state(true);
let showDrawer = $state(true);
let orientation = $state<WordsToolbarOrientation>('horizontal');
let toolbarLayout = $state<'grouped' | 'inline'>('grouped');
let size = $state<WordsSize>('md');
let variant = $state<WordsVariant>('surface');
@ -236,7 +233,7 @@
const somaBodyLines = $derived(
[
...somaToolbarSnippet(toolbarSnippetGroups, orientation),
...somaToolbarSnippet(toolbarSnippetGroups),
...(somaUsesCodeLanguagePicker ? somaCodeLanguageSnippet() : []),
...(showLinkEditor && toolbarChoice === 'none' ? somaLinkEditorSnippet() : []),
showFindReplace && toolbarChoice === 'none' && ' <Words.FindReplace />',
@ -297,7 +294,6 @@
color !== 'primary' && ` color="${color}"`,
toolbarChoice !== 'none' && ` toolbar={${JSON.stringify(toolbarSnippetGroups)}}`,
toolbarChoice === 'none' && ' toolbar={false}',
orientation !== 'horizontal' && ` toolbarOrientation="${orientation}"`,
disabled && ' disabled',
readonly && ' readonly',
required && ' required',
@ -333,12 +329,6 @@
{ selector: '[data-words][data-focused]', source: 'morfo', kind: 'soma', note: 'focus ring' },
{ selector: '[data-words][data-invalid]', source: 'morfo', kind: 'soma', note: 'risk border' },
{ selector: '[data-words-toolbar]', source: 'morfo', kind: 'soma', note: 'toolbar shell' },
{
selector: '[data-words-toolbar][data-orientation]',
source: 'morfo',
kind: 'soma',
note: 'toolbar axis'
},
{
selector: '[data-words-command-button][data-state]',
source: 'morfo',
@ -782,10 +772,7 @@
return JSON.stringify(value);
}
function somaToolbarSnippet(
groups: readonly WordsToolbarGroupConfig[],
toolbarOrientation: WordsToolbarOrientation
): string[] {
function somaToolbarSnippet(groups: readonly WordsToolbarGroupConfig[]): string[] {
if (groups.length === 0) return [];
const expandedGroups = groups.map((group) => group.flatMap(expandToolbarItem));
const toolbarGroups = expandedGroups
@ -795,7 +782,7 @@
return [
...(toolbarGroups.length
? [
` <Words.Toolbar orientation="${toolbarOrientation}">`,
' <Words.Toolbar>',
...toolbarGroups.flatMap((group) => [
' <Words.ToolbarGroup>',
...group.flatMap((command) => somaCommandSnippet(command)),
@ -1037,7 +1024,6 @@
{variant}
{color}
toolbar={toolbarConfig}
toolbarOrientation={orientation}
{toolbarLayout}
onValueCommit={(_, reason) => (lastCommit = reason)}
>
@ -1181,20 +1167,6 @@
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>toolbar orientation</span>
<span data-uix-chips role="radiogroup">
{#each orientations as item}
<button
data-uix-chip
data-active={orientation === item}
onclick={() => (orientation = item)}
>
{item}
</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>export format</span>
<span data-uix-chips role="radiogroup">
@ -1787,7 +1759,7 @@
>
<tr
><td class="name">Toolbar</td><td
><code>role="toolbar"</code> with orientation propagated to ARIA.</td
><code>role="toolbar"</code> with <code>aria-orientation="horizontal"</code>.</td
></tr
>
<tr

Loading…
Cancel
Save

Powered by TurnKey Linux.