Repository navigation
v6.5.0
Highlights
-
@byline/richtext-lexical— An inline image can now carry a click-through link. The image dialog gained a Link tab offering a Byline document or an arbitrary URL, using the same discriminated union the link extension already uses, so an editor picks a target the same way in both places. The target is a second, independent relation: the flatDocumentRelationon the node still addresses the source media document, while the new nestedlinkaddresses where a reader goes, which is why it is nested rather than flattened. An internal target is refreshed on every save and read by a newinlineImageLinkVisitor, exported from@byline/richtext-lexical/serveralongside its two siblings for hosts that compose their own visitor list;runLexicalPopulatehydrates both relations in one batched pass, one read per collection. When a target is deleted the walker marks it unresolved and the renderer drops the anchor while keeping the image, matching how a text link degrades.linkrides throughexportJSON/importJSON, so copying an inline image with its caption and pasting it elsewhere keeps the link — that is the path a clipboard actually takes between two editors sharing a namespace. The HTML round-trip carries the relation asdata-link-*attributes on the<img>rather than an<a>wrapper, because an anchor there would make Lexical's own link conversion produce a link node wrapping an inline-image node on re-import. Markdown export emits[](href). The field is additive and optional: the serialized node version is unchanged and existing images import untouched. -
@byline/richtext-lexical— The link and inline-image target pickers are now one shared sub-form. Both modals previously carried their own copy of the "document or URL" controls. The decision layer moved into a React-freelink-target-statemodule — empty/from/to/validate — with a single JSX shell above it, which is what lets the inline image offer a "No link" choice the link node cannot. The picker itself was rebuilt to match the admin relation field: a chosen document is a bordered tile with edit and remove icon buttons rather than a text button whose label changed with the state, and the frame uses the shared--field-bordertoken, darkened on dark surfaces. The inline-image dialog is now tabbed (Image, Link) with a reserved panel height, so choosing a link type no longer shifts the dialog under the cursor; a validation error switches to the tab that owns it rather than letting Save appear to do nothing. -
@byline/ui,@byline/admin,@byline/i18n— The password reveal toggle is a real button, and every password field has one. It was a bare<div>with anonClickwrapping anaria-hiddenicon: unreachable by keyboard and silent to screen readers, so the only way to reveal a password was a mouse. It is now a<button type="button">with a translated accessible name and anaria-pressedstate, swapping only the inner icon so its DOM node stays stable across re-renders, and preventing default onmousedownso activating it does not blur the field or disturb the caret. Sign-in and admin user creation used a plain input with no toggle at all and now useInputPassword; a repository-wide check finds no remaining unmanaged password inputs.common.actions.showPasswordandcommon.actions.hidePasswordwere added to all eight bundled locales for the button's accessible name —@byline/uiitself stays i18n-agnostic and accepts the strings as optional props with English defaults.
Bug Fixes
-
@byline/ui—InputPasswordsilently ignored its owntype. Caller props were spread after the computedtype, so a caller passing the obvioustype="password"won: the eye icon swapped on click while the field stayed masked forever. Props now spread first.typeandendAdornmentare additionally removed from the public props type, so the mistake is a compile error rather than a bug someone has to notice — see Breaking Changes. This was not hypothetical: both password fields converted in this cycle were passingtype="password", and converting them without this fix would have shipped exactly that symptom. -
@byline/cli(public renderer templates) — A linked image inside a text link no longer emits nested anchors.<a>inside<a>is un-nested by the HTML parser, which silently restructures the document and drags the surrounding layout with it. The shape is reachable in production rather than theoretical: markdown[](url)imports as alinknode containing aninline-imagenode, and the image dialog can then give that image a link of its own. The serializer now tracks whether an anchor is actually being rendered — a link node whose target is unresolved emits no anchor, and suppressing the inner link in that case would cost a working link for nothing — and a link node reached while already inside an anchor renders its children plain. The anchor context travels into a caption's nested serialize pass too, since a caption can hold its own links. -
@byline/cli(public renderer templates) — An inline image's "Open in new tab" setting is now honoured. The shared text-link helper forcestarget="_blank"for any external href regardless of the node's ownnewTab, which suits a text link — they also get a visible external-link affordance — but made the image dialog's checkbox a no-op for exactly the URLs an editor is most likely to tick it for. The image now derivestargetfrom its own setting and statesrel="noopener"alongside it. The equivalent inconsistency in text links is unchanged and deliberate; altering it would silently change every existing external link. -
@byline/richtext-lexical— Removing an inline image's link now works.InlineImageNode.update()guards every field with!= null, a convention that cannot express "remove this".linkis keyed on the presence of the property instead, so clearing a click target is a real operation rather than a silent no-op.
Chores
- monorepo — Dependencies updated across the workspace, including the Lexical toolchain to 0.51 — see Breaking Changes.
@byline/richtext-lexical— The internal-link hydration rules (title lookup,buildDocumentPathwith its generic fallback, the unresolved-target branch) are shared by the link and inline-image visitors through onecreateInternalLinkHydrationfactory rather than duplicated per node type.- monorepo —
SerializedLexicalNodeandinlineImageLinkVisitorare recorded in the public-export baseline as deliberate API: a host writing its own rich-text renderer needs the node type, and a host composing a custom visitor list cannot do so without the visitor export.
Breaking Changes
-
@byline/richtext-lexical— Lexical moved to 0.51, and two of its types changed shape.SerializedEditorStateno longer takes a type parameter, soSerializedEditorState<SerializedLexicalNode>no longer compiles — drop the argument. A nested editor's children (an inline image's caption, for instance) are now typedSerializedPartialNode[], whoseversionand$slotsrelax because a compact export omits what parsing restores on its own; a renderer whose node type intersects Lexical's strictSerializedLexicalNodewill reject them, and the fix is to stop intersecting rather than to cast at each call site. Separately,@lexical/codeis now a thin facade over@lexical/code-coreand no longer re-exports the Prism tokenizer or language tables:registerCodeHighlighting,CODE_LANGUAGE_FRIENDLY_NAME_MAP,CODE_LANGUAGE_MAPandgetLanguageFriendlyNamecome from@lexical/code-prism, which is now a direct dependency of this package. Any application carrying its own rich-text serializer should expect to make these edits when upgrading. -
@byline/ui—InputPasswordno longer acceptstypeorendAdornment. Both are omitted fromInputPasswordProps, because both are what the component exists to control and passing either previously broke the reveal toggle without any error. A caller passing them will now fail to compile; remove the prop. Two new optional props,showPasswordLabelandhidePasswordLabel, supply the toggle's accessible name and default to English.
All other @byline/* packages bumped to 6.5.0 in lockstep with no behavioural changes this cycle.