lexical_image 1.5.0 copy "lexical_image: ^1.5.0" to clipboard
lexical_image: ^1.5.0 copied to clipboard

Image nodes for lexical_core, with a resizable Flutter widget: drag handles with configurable minimum and maximum size, aspect-ratio preservation and optional captions.

lexical_image

lexical_image #

Images for lexical_core, with a resizable Flutter widget: drag handles, a configurable minimum and maximum, aspect-ratio preservation and optional captions.

dependencies:
  lexical_image: ^1.0.0
final editor = LexicalEditor(nodes: imageNodes);
registerImage(editor);

LexicalEditable(
  editor: editor,
  theme: theme,
  decoratorBuilders: imageDecoratorBuilders(
    editor: editor,
    limits: const ImageSizeLimits(minWidth: 80, maxWidth: 640),
  ),
);

editor.dispatchCommand(
  insertImageCommand,
  const ImageAttributes(src: 'https://…/flowers.jpg', altText: 'Blumen'),
);

Compatible with the Lexical playground #

The wire format matches the playground's ImageNode field for field, including two conventions that are easy to get wrong:

  • width and height are 0 for "as large as it comes" — not null, not absent. Upstream keeps 'inherit' internally and writes 0.
  • maxWidth travels in the document. It is arguably presentation policy, but dropping it would lose data on a round trip, so it is stored and ImageSizeLimits narrows it per application.

image is not part of any published @lexical/* package — it lives in the playground — so this shape is transcribed from that source rather than generated by gen_fixtures.mjs like the core types. That is why the test suite writes a playground document out in full and asserts a fixed point on it: the compatibility claim is checked, not assumed.

A GIF is an image. Lexical has no separate node for one, and neither does this: the playground's "GIF" entry dispatches the ordinary insert command with a .gif source. Flutter animates it without being asked.

Captions #

Upstream's caption is a nested editor — a second editor instance per image, with its own history and selection. This port has no nested editors, so a caption is kept verbatim as the state it arrived as and exposed as plain text through captionText.

What that means in practice: a caption written on the web keeps its formatting through any number of round trips here, and editing one in Flutter replaces it with plain text. Losing formatting in a caption somebody deliberately edited is a fair trade; losing it by merely opening the document would not be.

Resizing #

resizeImage is a pure function and tested as one — the arithmetic is the part that goes wrong, not the gesture:

  • corner drags follow whichever axis the pointer moved further along, so a diagonal drag tracks the pointer instead of only its horizontal part;
  • edge drags derive the other axis, so an image never distorts;
  • clamping scales both axes by the tightest constraint. Clamping them separately stretches the picture the moment one hits its limit, which looks like a bug because it is one.

A drag writes to the document once, when it ends. Resizing has to be one undo step, not one per pointer move.

A stored size is a size somebody chose on their screen, so an image wider than the column it lands in is drawn scaled down — proportionally, and for display only. The document keeps what it was given, and the image is full size again on a screen with room for it.

Where an image sits #

Inside a paragraph. DecoratorNode.isInline() is true upstream and the playground's ImageNode does not override it, so root > paragraph > image is the shape every Lexical client writes and root > image is not one. The insert command mirrors upstream's: put the image at the caret, and wrap it in a paragraph if that turns out to be the root.

Markdown #

final transformers = defaultMarkdownTransformers.extend(
  textMatches: [imageTransformer],
);

Opt-in, exactly as upstream: @lexical/markdown ships no image rule and the playground adds its own to the list it passes in. Only the alt text and the source survive ![alt](src) — size, caption and maxWidth have no markdown spelling, and upstream loses the same fields. Use the JSON wire format or lexical_file to move a document without losing anything.

Sources are untrusted #

defaultImageResolver accepts http(s):, data: and asset paths, and returns null for everything else — a file: URL in a document from someone else has no business reading the local disk. Replace it with your own resolver to add a cache, an allow-list of hosts or an offline placeholder.

Licence #

MIT. Derived from Lexical, © Meta Platforms, Inc., also MIT. See NOTICE.

0
likes
0
points
513
downloads

Publisher

verified publisherahmadre.com

Weekly Downloads

Image nodes for lexical_core, with a resizable Flutter widget: drag handles with configurable minimum and maximum size, aspect-ratio preservation and optional captions.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, lexical_core, lexical_markdown, meta

More

Packages that depend on lexical_image