From 71c24dca9b82816b6c4156ed5186f19c96f7d06e Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 27 May 2026 23:41:01 +0200 Subject: [PATCH] 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. - `` / `` 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) --- src/uix/eidos/components/words/types.ts | 5 +- .../components/words/words-toolbar.svelte | 5 +- src/uix/eidos/components/words/words.css | 65 ------------------- src/uix/eidos/components/words/words.svelte | 5 +- src/uix/morfo/components/words.ts | 10 +-- src/uix/soma/components/words/exports.ts | 1 - src/uix/soma/components/words/types.ts | 3 - .../components/words/words-provider.svelte.ts | 17 ++--- web/routes/uix/components/words/+page.svelte | 38 ++--------- 9 files changed, 20 insertions(+), 129 deletions(-) diff --git a/src/uix/eidos/components/words/types.ts b/src/uix/eidos/components/words/types.ts index fe35eb948..54ad70734 100644 --- a/src/uix/eidos/components/words/types.ts +++ b/src/uix/eidos/components/words/types.ts @@ -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 — diff --git a/src/uix/eidos/components/words/words-toolbar.svelte b/src/uix/eidos/components/words/words-toolbar.svelte index 60772fd6d..fda78d775 100644 --- a/src/uix/eidos/components/words/words-toolbar.svelte +++ b/src/uix/eidos/components/words/words-toolbar.svelte @@ -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); - + {#snippet child({ props: wordsProps })} - + {#snippet child({ props: toolbarProps })} {@const props = mergeProps(toolbarProps, wordsProps)} {#if outerChild} diff --git a/src/uix/eidos/components/words/words.css b/src/uix/eidos/components/words/words.css index 53464a1f2..e0e801f16 100644 --- a/src/uix/eidos/components/words/words.css +++ b/src/uix/eidos/components/words/words.css @@ -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; diff --git a/src/uix/eidos/components/words/words.svelte b/src/uix/eidos/components/words/words.svelte index 0c9a1065d..ad6c77fbf 100644 --- a/src/uix/eidos/components/words/words.svelte +++ b/src/uix/eidos/components/words/words.svelte @@ -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} - + {#each visibleToolbarGroups as group} - + {#each group as item} {#if isToolbarFamilyConfig(item)} {#if resolvedToolbarLayout === 'inline'} diff --git a/src/uix/morfo/components/words.ts b/src/uix/morfo/components/words.ts index 7d47eab4a..b3f09dd85 100644 --- a/src/uix/morfo/components/words.ts +++ b/src/uix/morfo/components/words.ts @@ -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: [] diff --git a/src/uix/soma/components/words/exports.ts b/src/uix/soma/components/words/exports.ts index 6698ceb4a..4e00d6c2a 100644 --- a/src/uix/soma/components/words/exports.ts +++ b/src/uix/soma/components/words/exports.ts @@ -21,7 +21,6 @@ export type { WordsSlashCommandId, WordsSlashCommandItem, WordsStatus, - WordsToolbarOrientation, WordsProps as ProviderProps, WordsContentProps as ContentProps, WordsPlaceholderProps as PlaceholderProps, diff --git a/src/uix/soma/components/words/types.ts b/src/uix/soma/components/words/types.ts index 6f9781e96..dcf8d8861 100644 --- a/src/uix/soma/components/words/types.ts +++ b/src/uix/soma/components/words/types.ts @@ -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; export type WordsToolbarGroupProps = WithChild<{ id?: string; - orientation?: WordsToolbarOrientation; }> & Without; diff --git a/src/uix/soma/components/words/words-provider.svelte.ts b/src/uix/soma/components/words/words-provider.svelte.ts index b1c5a96a1..ef72d17dd 100644 --- a/src/uix/soma/components/words/words-provider.svelte.ts +++ b/src/uix/soma/components/words/words-provider.svelte.ts @@ -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 }); } diff --git a/web/routes/uix/components/words/+page.svelte b/web/routes/uix/components/words/+page.svelte index 820e8c6fc..5c9614cc3 100644 --- a/web/routes/uix/components/words/+page.svelte +++ b/web/routes/uix/components/words/+page.svelte @@ -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('horizontal'); let toolbarLayout = $state<'grouped' | 'inline'>('grouped'); let size = $state('md'); let variant = $state('surface'); @@ -236,7 +233,7 @@ const somaBodyLines = $derived( [ - ...somaToolbarSnippet(toolbarSnippetGroups, orientation), + ...somaToolbarSnippet(toolbarSnippetGroups), ...(somaUsesCodeLanguagePicker ? somaCodeLanguageSnippet() : []), ...(showLinkEditor && toolbarChoice === 'none' ? somaLinkEditorSnippet() : []), showFindReplace && toolbarChoice === 'none' && ' ', @@ -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 ? [ - ` `, + ' ', ...toolbarGroups.flatMap((group) => [ ' ', ...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} -