fix(words): close audit P0 + quiet focus event (FASE 1)

Cierre de los hallazgos P0/P1 del audit Codex sobre Words + fix del
evento contact-focus molesto reportado por el usuario.

F1.1 — Fix evento foco (UX):

El handler onfocus disparaba `runtime.trigger('contact-focus')` cada vez
que el editor recibía foco, generando sound + haptic + visual stamp en
cada re-focus desde popovers internos (bubble menu / link editor / find
panel / slash menu / toolbar dropdowns). En sesión normal de edición el
foco entra/sale del content docenas de veces, lo que producía fatiga
perceptual.

Cambios en words-provider.svelte.ts:
- onfocus: emit solo en transición real (was-not-focused → focused).
  Re-focus desde popover interno NO emite.
- onblur: si el related target es interno al Words tool tree
  (isInsideWordsTool), mantener focused=true (Words se trata como un
  solo scope de foco). Skip commit('blur') también para evitar
  commits espurios entre popover hops.

F1.2 — Declarar 8 data-* hardcoded en morfo (audit P0-1):

Añadidos al Content part:
- data-words-find-match, data-words-find-active (mark highlights)
- data-words-indent (list items con indent > 0)
- data-language (alias parsing-only para interop con HTML externo
  emitido por Prism/highlight.js)

Nuevas 6 parts compound (Eidos wrappers):
- HeadingPicker + HeadingPickerPanel (data-words-heading-picker /
  -panel)
- CodeLanguagePicker + CodeLanguagePanel
- ToolbarFamilyMenu + ToolbarFamilyPanel

F1.3 — Rename i18n keys a kebab-case (audit P0-1):

4 keys camelCase eran ilegales según contracts.test.ts. Renamed:
- components.words.bubbleMenu → bubble-menu
- components.words.slashMenu → slash-menu
- components.words.linkEditor → link-editor
- components.words.findReplace → find-replace

Propagado a: morfo declarations (8 refs), provider tslangs calls
(4 refs), catalog langs/components/words.ts (4 keys).

F1.4 — Canonizar 9 event names a forma {family}-{verb}[-{variant}]
(audit P2-1):

Renombrados para parsear con el morfo:vocabulary script. El verb
declarado en cada semantic ahora aparece explícito en el name:

- commit-content       → commit-save-content (verb: save)
- commit-format        → commit-set-format (verb: set)
- commit-link          → commit-set-link
- commit-unlink        → commit-reset-link (verb: reset)
- commit-slash-command → commit-set-slash
- commit-check         → commit-set-check
- commit-history       → commit-restore-history (verb: restore)
- commit-clear         → commit-reset-content (verb: reset)
- signal-invalid-input → signal-warn-invalid (verb: warn)

Propagado a: sema cascade selectors, soma provider trigger calls,
provider + content tests, demo route. 0 conflictos con event names
de otros componentes (file-upload/tag-group/etc. usan namespaces
propios sin colisión con words).

Tests: 161/161 pass en src/uix/{contracts,soma/components/words}.
Suite full eidos+morfo+soma+sema pendiente de verificar tras commit.
contracts.test.ts ahora verde — cierra P0 audit Codex.
morfo:vocabulary 0 event-name warnings (los 107 data-attr enum
warnings son pre-existentes, separate scope).

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

@ -17,19 +17,19 @@ export const wordsLangs = {
es: 'Barra del editor', es: 'Barra del editor',
en: 'Editor toolbar' en: 'Editor toolbar'
}, },
bubbleMenu: { 'bubble-menu': {
es: 'Barra de seleccion', es: 'Barra de seleccion',
en: 'Selection toolbar' en: 'Selection toolbar'
}, },
slashMenu: { 'slash-menu': {
es: 'Insertar bloque', es: 'Insertar bloque',
en: 'Insert block' en: 'Insert block'
}, },
linkEditor: { 'link-editor': {
es: 'Editar enlace', es: 'Editar enlace',
en: 'Edit link' en: 'Edit link'
}, },
findReplace: { 'find-replace': {
es: 'Buscar y reemplazar', es: 'Buscar y reemplazar',
en: 'Find and replace' en: 'Find and replace'
}, },

@ -11,10 +11,10 @@ export const wordsMorfo = {
content: '#?components.words.content|Editor content', content: '#?components.words.content|Editor content',
placeholder: '#?components.words.placeholder|Start writing...', placeholder: '#?components.words.placeholder|Start writing...',
toolbar: '#?components.words.toolbar|Editor toolbar', toolbar: '#?components.words.toolbar|Editor toolbar',
bubbleMenu: '#?components.words.bubbleMenu|Selection toolbar', bubbleMenu: '#?components.words.bubble-menu|Selection toolbar',
slashMenu: '#?components.words.slashMenu|Insert block', slashMenu: '#?components.words.slash-menu|Insert block',
linkEditor: '#?components.words.linkEditor|Edit link', linkEditor: '#?components.words.link-editor|Edit link',
findReplace: '#?components.words.findReplace|Find and replace', findReplace: '#?components.words.find-replace|Find and replace',
status: '#?components.words.status|Editor status' status: '#?components.words.status|Editor status'
}, },
events: [ events: [
@ -28,7 +28,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-content', name: 'commit-save-content',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'save', verb: 'save',
@ -38,7 +38,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-format', name: 'commit-set-format',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'set', verb: 'set',
@ -48,7 +48,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-link', name: 'commit-set-link',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'set', verb: 'set',
@ -58,7 +58,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-unlink', name: 'commit-reset-link',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'reset', verb: 'reset',
@ -78,7 +78,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-slash-command', name: 'commit-set-slash',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'set', verb: 'set',
@ -88,7 +88,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-check', name: 'commit-set-check',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'set', verb: 'set',
@ -98,7 +98,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-history', name: 'commit-restore-history',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'restore', verb: 'restore',
@ -108,7 +108,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'commit-clear', name: 'commit-reset-content',
semantic: { semantic: {
family: 'commit', family: 'commit',
verb: 'reset', verb: 'reset',
@ -118,7 +118,7 @@ export const wordsMorfo = {
} }
}, },
{ {
name: 'signal-invalid-input', name: 'signal-warn-invalid',
semantic: { semantic: {
family: 'signal', family: 'signal',
verb: 'warn', verb: 'warn',
@ -264,7 +264,24 @@ export const wordsMorfo = {
severity: 'optional' severity: 'optional'
}, },
{ attr: 'data-words-check-toggle', severity: 'optional' }, { attr: 'data-words-check-toggle', severity: 'optional' },
{ attr: 'data-words-checked', severity: 'optional' } { attr: 'data-words-checked', severity: 'optional' },
// Find/replace match highlights — emitted by the headless
// render around <mark> nodes wrapping search matches.
// `find-active` differentiates the currently-selected match
// from the rest of the highlights so Eidos can paint it.
{ attr: 'data-words-find-match', severity: 'optional' },
{ attr: 'data-words-find-active', severity: 'optional' },
// List indentation level (0..8) emitted on list items when
// indent > 0 (Tab / Shift+Tab in lists, plus increase/decrease
// indent commands).
{ attr: 'data-words-indent', severity: 'optional' },
// Interop alias parsed by the HTML deserializer: external
// editors (Prism, highlight.js, GitHub-flavored snippets)
// commonly emit `data-language` on <pre>/<code>. The internal
// canonical attr stays `data-words-code-language`; this
// declaration acknowledges the interop read so the soma-attrs
// contract guard accepts the parser reference.
{ attr: 'data-language', severity: 'optional' }
], ],
aria: [ aria: [
{ attr: 'contenteditable', value: v.propRef('editable') }, { attr: 'contenteditable', value: v.propRef('editable') },
@ -297,8 +314,8 @@ export const wordsMorfo = {
{ key: 'ArrowUp', action: 'prev-item' }, { key: 'ArrowUp', action: 'prev-item' },
{ key: 'Home', action: 'first-item' }, { key: 'Home', action: 'first-item' },
{ key: 'End', action: 'last-item' }, { key: 'End', action: 'last-item' },
{ key: 'Enter', action: 'commit-slash-command' }, { key: 'Enter', action: 'commit-set-slash' },
{ key: 'Tab', action: 'commit-slash-command' }, { key: 'Tab', action: 'commit-set-slash' },
{ key: 'Escape', action: 'close' }, { key: 'Escape', action: 'close' },
{ key: 'Enter', action: 'insert-paragraph' }, { key: 'Enter', action: 'insert-paragraph' },
{ key: 'Enter', action: 'insert-line-break-in-code-block' }, { key: 'Enter', action: 'insert-line-break-in-code-block' },
@ -387,7 +404,7 @@ export const wordsMorfo = {
{ attr: 'aria-orientation', value: v.literal('horizontal') }, { attr: 'aria-orientation', value: v.literal('horizontal') },
{ {
attr: 'aria-label', attr: 'aria-label',
value: v.translationRef('#?components.words.bubbleMenu|Selection toolbar'), value: v.translationRef('#?components.words.bubble-menu|Selection toolbar'),
severity: 'recommended' severity: 'recommended'
} }
] ]
@ -410,7 +427,7 @@ export const wordsMorfo = {
aria: [ aria: [
{ {
attr: 'aria-label', attr: 'aria-label',
value: v.translationRef('#?components.words.slashMenu|Insert block'), value: v.translationRef('#?components.words.slash-menu|Insert block'),
severity: 'recommended' severity: 'recommended'
} }
] ]
@ -473,7 +490,7 @@ export const wordsMorfo = {
aria: [ aria: [
{ {
attr: 'aria-label', attr: 'aria-label',
value: v.translationRef('#?components.words.linkEditor|Edit link'), value: v.translationRef('#?components.words.link-editor|Edit link'),
severity: 'recommended' severity: 'recommended'
} }
] ]
@ -495,11 +512,80 @@ export const wordsMorfo = {
aria: [ aria: [
{ {
attr: 'aria-label', attr: 'aria-label',
value: v.translationRef('#?components.words.findReplace|Find and replace'), value: v.translationRef('#?components.words.find-replace|Find and replace'),
severity: 'recommended' severity: 'recommended'
} }
] ]
}, },
{
name: 'HeadingPicker',
kebab: 'heading-picker',
archetype: 'group',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
{ attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' }
],
aria: []
},
{
name: 'HeadingPickerPanel',
kebab: 'heading-picker-panel',
archetype: 'content',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
aria: []
},
{
name: 'CodeLanguagePicker',
kebab: 'code-language-picker',
archetype: 'group',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
{ attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' }
],
aria: []
},
{
name: 'CodeLanguagePanel',
kebab: 'code-language-panel',
archetype: 'content',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
aria: []
},
{
name: 'ToolbarFamilyMenu',
kebab: 'toolbar-family',
archetype: 'group',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [
{ attr: 'data-family', severity: 'optional' },
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }
],
aria: []
},
{
name: 'ToolbarFamilyPanel',
kebab: 'toolbar-family-panel',
archetype: 'content',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
aria: []
},
{ {
name: 'HiddenInput', name: 'HiddenInput',
kebab: 'hidden-input', kebab: 'hidden-input',

@ -33,17 +33,17 @@ export const wordsSema: Sema = {
haptic: { kind: 'tick', intensity: 0.12, duration: 6 } haptic: { kind: 'tick', intensity: 0.12, duration: 6 }
}, },
{ {
selector: onProvider({ eventName: 'commit-content' }), selector: onProvider({ eventName: 'commit-save-content' }),
sound: soundTuning('form.commit.soft'), sound: soundTuning('form.commit.soft'),
haptic: { kind: 'success' } haptic: { kind: 'success' }
}, },
{ {
selector: onCommandButton({ eventName: 'commit-format' }), selector: onCommandButton({ eventName: 'commit-set-format' }),
sound: soundTuning('form.commit.subtle'), sound: soundTuning('form.commit.subtle'),
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onSlashMenu({ eventName: 'commit-slash-command' }), selector: onSlashMenu({ eventName: 'commit-set-slash' }),
sound: soundTuning('form.commit.soft', { sound: soundTuning('form.commit.soft', {
gain: { op: 'multiply', factor: 0.85 }, gain: { op: 'multiply', factor: 0.85 },
pitch: { op: 'add', value: 30 } pitch: { op: 'add', value: 30 }
@ -51,7 +51,7 @@ export const wordsSema: Sema = {
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onLinkEditor({ eventName: 'commit-link' }), selector: onLinkEditor({ eventName: 'commit-set-link' }),
sound: soundTuning('form.commit.soft', { sound: soundTuning('form.commit.soft', {
gain: { op: 'multiply', factor: 0.9 }, gain: { op: 'multiply', factor: 0.9 },
pitch: { op: 'add', value: 20 } pitch: { op: 'add', value: 20 }
@ -59,7 +59,7 @@ export const wordsSema: Sema = {
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onLinkEditor({ eventName: 'commit-unlink' }), selector: onLinkEditor({ eventName: 'commit-reset-link' }),
sound: soundTuning('form.commit.subtle', { sound: soundTuning('form.commit.subtle', {
gain: { op: 'multiply', factor: 0.8 }, gain: { op: 'multiply', factor: 0.8 },
contour: 'descending' contour: 'descending'
@ -74,14 +74,14 @@ export const wordsSema: Sema = {
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onContent({ eventName: 'commit-check' }), selector: onContent({ eventName: 'commit-set-check' }),
sound: soundTuning('form.commit.subtle', { sound: soundTuning('form.commit.subtle', {
gain: { op: 'multiply', factor: 0.85 } gain: { op: 'multiply', factor: 0.85 }
}), }),
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onProvider({ eventName: 'commit-history' }), selector: onProvider({ eventName: 'commit-restore-history' }),
sound: soundTuning('form.commit.subtle', { sound: soundTuning('form.commit.subtle', {
gain: { op: 'multiply', factor: 0.85 }, gain: { op: 'multiply', factor: 0.85 },
contour: 'descending' contour: 'descending'
@ -89,7 +89,7 @@ export const wordsSema: Sema = {
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onProvider({ eventName: 'commit-clear' }), selector: onProvider({ eventName: 'commit-reset-content' }),
sound: soundTuning('form.commit.subtle', { sound: soundTuning('form.commit.subtle', {
contour: 'descending', contour: 'descending',
pitch: { op: 'add', value: -80 } pitch: { op: 'add', value: -80 }
@ -97,7 +97,7 @@ export const wordsSema: Sema = {
haptic: { kind: 'tap' } haptic: { kind: 'tap' }
}, },
{ {
selector: onContent({ eventName: 'signal-invalid-input' }), selector: onContent({ eventName: 'signal-warn-invalid' }),
sound: soundTuning('form.commit.subtle', { sound: soundTuning('form.commit.subtle', {
contour: 'descending', contour: 'descending',
pitch: { op: 'add', value: -120 } pitch: { op: 'add', value: -120 }

@ -198,7 +198,7 @@ export class WordsProvider {
content: () => this.opts.contentId.current || undefined content: () => this.opts.contentId.current || undefined
}, },
events: { events: {
'commit-content': () => { 'commit-save-content': () => {
this.opts.onValueCommit.current?.(this.document, this.pendingCommitReason); this.opts.onValueCommit.current?.(this.document, this.pendingCommitReason);
} }
} }
@ -517,7 +517,7 @@ export class WordsProvider {
commit(reason: WordsCommitReason = 'programmatic', target?: HTMLElement) { commit(reason: WordsCommitReason = 'programmatic', target?: HTMLElement) {
this.closeTypingBatch(); this.closeTypingBatch();
this.pendingCommitReason = reason; this.pendingCommitReason = reason;
void this.runtime.trigger('commit-content', { void this.runtime.trigger('commit-save-content', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
} }
@ -526,7 +526,7 @@ export class WordsProvider {
if (this.isDisabled || this.isReadonly || !this.canUndo) return; if (this.isDisabled || this.isReadonly || !this.canUndo) return;
this.closeTypingBatch(); this.closeTypingBatch();
this.publishHistory(undoWords(this.history)); this.publishHistory(undoWords(this.history));
void this.runtime.trigger('commit-history', { void this.runtime.trigger('commit-restore-history', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
} }
@ -535,7 +535,7 @@ export class WordsProvider {
if (this.isDisabled || this.isReadonly || !this.canRedo) return; if (this.isDisabled || this.isReadonly || !this.canRedo) return;
this.closeTypingBatch(); this.closeTypingBatch();
this.publishHistory(redoWords(this.history)); this.publishHistory(redoWords(this.history));
void this.runtime.trigger('commit-history', { void this.runtime.trigger('commit-restore-history', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
} }
@ -596,10 +596,10 @@ export class WordsProvider {
if (!changed) return; if (!changed) return;
const event = const event =
name === 'clear-document' name === 'clear-document'
? 'commit-clear' ? 'commit-reset-content'
: name === 'unlink' : name === 'unlink'
? 'commit-unlink' ? 'commit-reset-link'
: 'commit-format'; : 'commit-set-format';
void this.runtime.trigger(event, { void this.runtime.trigger(event, {
fallbackTarget: opts.target ?? this.opts.ref.current ?? undefined fallbackTarget: opts.target ?? this.opts.ref.current ?? undefined
}); });
@ -643,7 +643,7 @@ export class WordsProvider {
if (target) this.signalInvalidInput('invalid-link', target); if (target) this.signalInvalidInput('invalid-link', target);
return false; return false;
} }
void this.runtime.trigger('commit-link', { void this.runtime.trigger('commit-set-link', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
return true; return true;
@ -652,7 +652,7 @@ export class WordsProvider {
setCodeLanguage(language?: string, target?: HTMLElement): boolean { setCodeLanguage(language?: string, target?: HTMLElement): boolean {
const changed = this.applyCommandWithOptions({ type: 'setCodeLanguage', language }); const changed = this.applyCommandWithOptions({ type: 'setCodeLanguage', language });
if (!changed) return false; if (!changed) return false;
void this.runtime.trigger('commit-format', { void this.runtime.trigger('commit-set-format', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
return true; return true;
@ -661,7 +661,7 @@ export class WordsProvider {
unlink(target?: HTMLElement): boolean { unlink(target?: HTMLElement): boolean {
const changed = this.applyCommandWithOptions({ type: 'unlink' }); const changed = this.applyCommandWithOptions({ type: 'unlink' });
if (!changed) return false; if (!changed) return false;
void this.runtime.trigger('commit-unlink', { void this.runtime.trigger('commit-reset-link', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
return true; return true;
@ -677,20 +677,38 @@ export class WordsProvider {
} }
readonly onfocus = (e: SomaFocusEvent<HTMLElement>) => { readonly onfocus = (e: SomaFocusEvent<HTMLElement>) => {
// Only fire the perceptual contact-focus on a real transition
// (was-not-focused → focused). Re-focus from internal popovers
// (bubble menu, link editor, find panel, slash menu, toolbar
// dropdowns) does NOT count as a transition because `focused`
// stays true across that bounce — see onblur below. Without this
// guard every focus return inside a normal editing session
// triggers sound + haptic + a visual stamp, fatiguing the user.
const wasFocused = this.focused;
this.focused = true; this.focused = true;
this.ensureSelection(); this.ensureSelection();
void this.runtime.trigger('contact-focus', { fallbackTarget: e.currentTarget as HTMLElement }); if (!wasFocused) {
void this.runtime.trigger('contact-focus', { fallbackTarget: e.currentTarget as HTMLElement });
}
}; };
readonly onblur = (e: SomaFocusEvent<HTMLElement>) => { readonly onblur = (e: SomaFocusEvent<HTMLElement>) => {
this.isSelecting = false; this.isSelecting = false;
const related = e.relatedTarget as HTMLElement | null; const related = e.relatedTarget as HTMLElement | null;
if (!related || !this.isInsideWordsTool(related)) { const goingInternal = !!related && this.isInsideWordsTool(related);
if (!goingInternal) {
this.syncSelectionFromDom({ source: 'blur' }); this.syncSelectionFromDom({ source: 'blur' });
} }
this.closeTypingBatch(); this.closeTypingBatch();
this.focused = false; // Treat the Words component as a single focus scope: blur into an
if (this.opts.commitOnBlur.current) this.commit('blur'); // internal tool (popover / bubble menu / link editor / find panel /
// slash menu) keeps `focused` true, so the next focus-back to the
// content is not a fresh transition and doesn't re-emit the
// perceptual contact-focus event.
if (!goingInternal) {
this.focused = false;
if (this.opts.commitOnBlur.current) this.commit('blur');
}
}; };
readonly onkeydown = (e: SomaKeyboardEvent<HTMLElement>) => { readonly onkeydown = (e: SomaKeyboardEvent<HTMLElement>) => {
@ -758,7 +776,7 @@ export class WordsProvider {
e.preventDefault(); e.preventDefault();
const changed = this.applyCommandWithOptions({ type: 'toggleCheckItem' }); const changed = this.applyCommandWithOptions({ type: 'toggleCheckItem' });
if (changed) { if (changed) {
void this.runtime.trigger('commit-check', { void this.runtime.trigger('commit-set-check', {
fallbackTarget: e.currentTarget as HTMLElement fallbackTarget: e.currentTarget as HTMLElement
}); });
} }
@ -848,7 +866,7 @@ export class WordsProvider {
if (!path) return; if (!path) return;
const changed = this.applyCommandWithOptions({ type: 'toggleCheckItem', path }); const changed = this.applyCommandWithOptions({ type: 'toggleCheckItem', path });
if (!changed) return; if (!changed) return;
void this.runtime.trigger('commit-check', { void this.runtime.trigger('commit-set-check', {
fallbackTarget: e.currentTarget as HTMLElement fallbackTarget: e.currentTarget as HTMLElement
}); });
return; return;
@ -1210,7 +1228,7 @@ export class WordsProvider {
this.slashMenuActiveIndex = 0; this.slashMenuActiveIndex = 0;
this.publishHistory(next); this.publishHistory(next);
void tick().then(() => this.restoreDomSelection()); void tick().then(() => this.restoreDomSelection());
void this.runtime.trigger('commit-slash-command', { void this.runtime.trigger('commit-set-slash', {
fallbackTarget: target ?? this.opts.ref.current ?? undefined fallbackTarget: target ?? this.opts.ref.current ?? undefined
}); });
return true; return true;
@ -1482,7 +1500,7 @@ export class WordsProvider {
this.soma.logger.warn('soma.words', `Ignored unsupported editor input: ${reason}`, { this.soma.logger.warn('soma.words', `Ignored unsupported editor input: ${reason}`, {
context: { reason } context: { reason }
}); });
void this.runtime.trigger('signal-invalid-input', { fallbackTarget: target }); void this.runtime.trigger('signal-warn-invalid', { fallbackTarget: target });
} }
} }
@ -1712,7 +1730,7 @@ export class WordsBubbleMenuProvider {
...this.runtimePart.props, ...this.runtimePart.props,
role: 'toolbar' as const, role: 'toolbar' as const,
'aria-orientation': 'horizontal' as const, 'aria-orientation': 'horizontal' as const,
'aria-label': this.provider.soma.langs.ts('#?components.words.bubbleMenu|Selection toolbar'), 'aria-label': this.provider.soma.langs.ts('#?components.words.bubble-menu|Selection toolbar'),
'data-open': boolToEmptyStrOrUndef(this.open), 'data-open': boolToEmptyStrOrUndef(this.open),
'data-positioned': boolToEmptyStrOrUndef(this.positioned), 'data-positioned': boolToEmptyStrOrUndef(this.positioned),
'data-side': this.opts.side.current, 'data-side': this.opts.side.current,
@ -1801,7 +1819,7 @@ export class WordsSlashMenuProvider {
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props, ...this.runtimePart.props,
role: 'listbox' as const, role: 'listbox' as const,
'aria-label': this.provider.soma.langs.ts('#?components.words.slashMenu|Insert block'), 'aria-label': this.provider.soma.langs.ts('#?components.words.slash-menu|Insert block'),
'data-open': boolToEmptyStrOrUndef(this.open), 'data-open': boolToEmptyStrOrUndef(this.open),
'data-query': this.provider.slashMenuQuery, 'data-query': this.provider.slashMenuQuery,
'data-active-index': String(this.provider.slashMenuActiveIndex), 'data-active-index': String(this.provider.slashMenuActiveIndex),
@ -2072,7 +2090,7 @@ export class WordsLinkEditorProvider {
readonly props = $derived.by(() => readonly props = $derived.by(() =>
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props, ...this.runtimePart.props,
'aria-label': this.provider.soma.langs.ts('#?components.words.linkEditor|Edit link'), 'aria-label': this.provider.soma.langs.ts('#?components.words.link-editor|Edit link'),
'data-active-link': boolToEmptyStrOrUndef(Boolean(this.selectedLink)), 'data-active-link': boolToEmptyStrOrUndef(Boolean(this.selectedLink)),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled || this.provider.isReadonly), 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled || this.provider.isReadonly),
'data-invalid': boolToEmptyStrOrUndef(Boolean(this.error)), 'data-invalid': boolToEmptyStrOrUndef(Boolean(this.error)),
@ -2175,7 +2193,7 @@ export class WordsFindReplaceProvider {
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props, ...this.runtimePart.props,
role: 'search' as const, role: 'search' as const,
'aria-label': this.provider.soma.langs.ts('#?components.words.findReplace|Find and replace'), 'aria-label': this.provider.soma.langs.ts('#?components.words.find-replace|Find and replace'),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
'data-readonly': boolToEmptyStrOrUndef(this.provider.isReadonly), 'data-readonly': boolToEmptyStrOrUndef(this.provider.isReadonly),
'data-invalid': boolToEmptyStrOrUndef(!this.provider.findQueryValid), 'data-invalid': boolToEmptyStrOrUndef(!this.provider.findQueryValid),

Loading…
Cancel
Save

Powered by TurnKey Linux.