@bizapps/design-system-testing 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor.d.cts CHANGED
@@ -164,6 +164,36 @@ interface InsertImageDialogProps {
164
164
  */
165
165
  declare function InsertImageDialog({ open, onOpenChange, labels, url, onUrlChange, alt, onAltChange, file, onFileChange, uploading, onApply, onCancel, removable, onRemove, maxSizeBytes, accept, }: InsertImageDialogProps): react.JSX.Element;
166
166
 
167
+ interface LinkBubbleMenuLabels {
168
+ open: string;
169
+ edit: string;
170
+ remove: string;
171
+ }
172
+ interface LinkBubbleMenuProps {
173
+ editor: Editor | null;
174
+ labels: LinkBubbleMenuLabels;
175
+ /** Opens the consumer's link dialog, pre-filled with the current href. */
176
+ onEdit: () => void;
177
+ /**
178
+ * Follows the link. Left to the consumer for the cases this package cannot
179
+ * judge — routing instead of navigating, or confirming first.
180
+ *
181
+ * Defaults to opening a new tab, which is also what Ctrl/Cmd-click does, so
182
+ * the author never loses unsaved work just by checking a URL.
183
+ */
184
+ onOpen?: (href: string) => void;
185
+ }
186
+ /**
187
+ * A floating bar that appears when the caret sits inside a link, offering to
188
+ * open, edit or unlink it.
189
+ *
190
+ * It exists because an editor cannot let a plain click navigate: clicking is how
191
+ * you place the caret to fix a typo, so `openOnClick` stays false and this gives
192
+ * the author a deliberate way out to the URL. Ctrl/Cmd-click is wired separately,
193
+ * on the editor itself, for people who expect the shortcut.
194
+ */
195
+ declare function LinkBubbleMenu({ editor, labels, onEdit, onOpen }: LinkBubbleMenuProps): react.JSX.Element | null;
196
+
167
197
  /**
168
198
  * Whatever `useEditor` accepts in its `extensions` array.
169
199
  *
@@ -190,6 +220,8 @@ interface RichTextEditorLabels {
190
220
  bullet: string;
191
221
  ordered: string;
192
222
  };
223
+ /** The floating bar shown while the caret sits inside a link. */
224
+ link: LinkBubbleMenuLabels;
193
225
  table: TableMenuLabels;
194
226
  highlight: string;
195
227
  }
@@ -217,13 +249,38 @@ interface UseRichTextEditorOptions {
217
249
  after?: string;
218
250
  items: ToolbarItem[];
219
251
  }[];
252
+ /**
253
+ * Replaces a built-in entry, keyed by its id.
254
+ *
255
+ * For when a product needs a richer version of something the preset already
256
+ * provides — swapping the single highlight button for a palette of shades,
257
+ * say. Replacing rather than adding keeps the toolbar from carrying both.
258
+ */
259
+ replaceToolbar?: Record<string, ToolbarItem>;
220
260
  /** Merged on top of the built-in content styles. */
221
261
  contentClassName?: string;
222
262
  /** Allows data: URIs in images. Off by default: uploads belong in storage. */
223
263
  allowBase64Images?: boolean;
264
+ /**
265
+ * Merged into the editor's `editorProps`. For behaviour only a product can
266
+ * define — clamping pasted tables to a contract's row limit, say.
267
+ *
268
+ * `attributes` and `handleClick` are set by the hook and are not overridable:
269
+ * they carry the accessible name and the link shortcut, which are the
270
+ * defaults consumers were getting wrong by hand.
271
+ */
272
+ editorProps?: Omit<NonNullable<EditorOptions['editorProps']>, 'attributes' | 'handleClick'>;
224
273
  }
225
274
  interface UseRichTextEditorResult {
226
275
  editor: Editor | null;
276
+ /**
277
+ * The floating link bar, ready to render beside `<RichTextEditor />`.
278
+ *
279
+ * Returned as an element rather than left to the consumer because it is easy
280
+ * to forget, and an editor without it gives the author no way to reach or
281
+ * remove a link they have already inserted.
282
+ */
283
+ linkMenu: ReactNode;
227
284
  /** Ready for `<RichTextEditor items={...} />`. */
228
285
  toolbar: ToolbarItem[];
229
286
  /** True when the caret has no selection, so a new link needs its own text. */
@@ -245,7 +302,7 @@ interface UseRichTextEditorResult {
245
302
  * What stays with the consumer is what only it can know: the marks its document
246
303
  * contract allows, and the copy. Both arrive as options.
247
304
  */
248
- declare function useRichTextEditor({ labels, onUpdate, content, onInsertLink, onInsertImage, extraExtensions, extraToolbar, contentClassName, allowBase64Images, }: UseRichTextEditorOptions): UseRichTextEditorResult;
305
+ declare function useRichTextEditor({ labels, onUpdate, content, onInsertLink, onInsertImage, extraExtensions, extraToolbar, replaceToolbar, contentClassName, allowBase64Images, editorProps, }: UseRichTextEditorOptions): UseRichTextEditorResult;
249
306
 
250
307
  /**
251
308
  * One entry in the palette.
@@ -354,36 +411,6 @@ interface InsertLinkDialogProps {
354
411
  */
355
412
  declare function InsertLinkDialog({ open, onOpenChange, labels, url, onUrlChange, text, onTextChange, needsText, onApply, onCancel, }: InsertLinkDialogProps): react.JSX.Element;
356
413
 
357
- interface LinkBubbleMenuLabels {
358
- open: string;
359
- edit: string;
360
- remove: string;
361
- }
362
- interface LinkBubbleMenuProps {
363
- editor: Editor | null;
364
- labels: LinkBubbleMenuLabels;
365
- /** Opens the consumer's link dialog, pre-filled with the current href. */
366
- onEdit: () => void;
367
- /**
368
- * Follows the link. Left to the consumer for the cases this package cannot
369
- * judge — routing instead of navigating, or confirming first.
370
- *
371
- * Defaults to opening a new tab, which is also what Ctrl/Cmd-click does, so
372
- * the author never loses unsaved work just by checking a URL.
373
- */
374
- onOpen?: (href: string) => void;
375
- }
376
- /**
377
- * A floating bar that appears when the caret sits inside a link, offering to
378
- * open, edit or unlink it.
379
- *
380
- * It exists because an editor cannot let a plain click navigate: clicking is how
381
- * you place the caret to fix a typo, so `openOnClick` stays false and this gives
382
- * the author a deliberate way out to the URL. Ctrl/Cmd-click is wired separately,
383
- * on the editor itself, for people who expect the shortcut.
384
- */
385
- declare function LinkBubbleMenu({ editor, labels, onEdit, onOpen }: LinkBubbleMenuProps): react.JSX.Element | null;
386
-
387
414
  interface ResizableImageOptions {
388
415
  /** Already translated: names the resize handle for assistive tech. */
389
416
  resizeLabel: string;
package/dist/editor.d.ts CHANGED
@@ -164,6 +164,36 @@ interface InsertImageDialogProps {
164
164
  */
165
165
  declare function InsertImageDialog({ open, onOpenChange, labels, url, onUrlChange, alt, onAltChange, file, onFileChange, uploading, onApply, onCancel, removable, onRemove, maxSizeBytes, accept, }: InsertImageDialogProps): react.JSX.Element;
166
166
 
167
+ interface LinkBubbleMenuLabels {
168
+ open: string;
169
+ edit: string;
170
+ remove: string;
171
+ }
172
+ interface LinkBubbleMenuProps {
173
+ editor: Editor | null;
174
+ labels: LinkBubbleMenuLabels;
175
+ /** Opens the consumer's link dialog, pre-filled with the current href. */
176
+ onEdit: () => void;
177
+ /**
178
+ * Follows the link. Left to the consumer for the cases this package cannot
179
+ * judge — routing instead of navigating, or confirming first.
180
+ *
181
+ * Defaults to opening a new tab, which is also what Ctrl/Cmd-click does, so
182
+ * the author never loses unsaved work just by checking a URL.
183
+ */
184
+ onOpen?: (href: string) => void;
185
+ }
186
+ /**
187
+ * A floating bar that appears when the caret sits inside a link, offering to
188
+ * open, edit or unlink it.
189
+ *
190
+ * It exists because an editor cannot let a plain click navigate: clicking is how
191
+ * you place the caret to fix a typo, so `openOnClick` stays false and this gives
192
+ * the author a deliberate way out to the URL. Ctrl/Cmd-click is wired separately,
193
+ * on the editor itself, for people who expect the shortcut.
194
+ */
195
+ declare function LinkBubbleMenu({ editor, labels, onEdit, onOpen }: LinkBubbleMenuProps): react.JSX.Element | null;
196
+
167
197
  /**
168
198
  * Whatever `useEditor` accepts in its `extensions` array.
169
199
  *
@@ -190,6 +220,8 @@ interface RichTextEditorLabels {
190
220
  bullet: string;
191
221
  ordered: string;
192
222
  };
223
+ /** The floating bar shown while the caret sits inside a link. */
224
+ link: LinkBubbleMenuLabels;
193
225
  table: TableMenuLabels;
194
226
  highlight: string;
195
227
  }
@@ -217,13 +249,38 @@ interface UseRichTextEditorOptions {
217
249
  after?: string;
218
250
  items: ToolbarItem[];
219
251
  }[];
252
+ /**
253
+ * Replaces a built-in entry, keyed by its id.
254
+ *
255
+ * For when a product needs a richer version of something the preset already
256
+ * provides — swapping the single highlight button for a palette of shades,
257
+ * say. Replacing rather than adding keeps the toolbar from carrying both.
258
+ */
259
+ replaceToolbar?: Record<string, ToolbarItem>;
220
260
  /** Merged on top of the built-in content styles. */
221
261
  contentClassName?: string;
222
262
  /** Allows data: URIs in images. Off by default: uploads belong in storage. */
223
263
  allowBase64Images?: boolean;
264
+ /**
265
+ * Merged into the editor's `editorProps`. For behaviour only a product can
266
+ * define — clamping pasted tables to a contract's row limit, say.
267
+ *
268
+ * `attributes` and `handleClick` are set by the hook and are not overridable:
269
+ * they carry the accessible name and the link shortcut, which are the
270
+ * defaults consumers were getting wrong by hand.
271
+ */
272
+ editorProps?: Omit<NonNullable<EditorOptions['editorProps']>, 'attributes' | 'handleClick'>;
224
273
  }
225
274
  interface UseRichTextEditorResult {
226
275
  editor: Editor | null;
276
+ /**
277
+ * The floating link bar, ready to render beside `<RichTextEditor />`.
278
+ *
279
+ * Returned as an element rather than left to the consumer because it is easy
280
+ * to forget, and an editor without it gives the author no way to reach or
281
+ * remove a link they have already inserted.
282
+ */
283
+ linkMenu: ReactNode;
227
284
  /** Ready for `<RichTextEditor items={...} />`. */
228
285
  toolbar: ToolbarItem[];
229
286
  /** True when the caret has no selection, so a new link needs its own text. */
@@ -245,7 +302,7 @@ interface UseRichTextEditorResult {
245
302
  * What stays with the consumer is what only it can know: the marks its document
246
303
  * contract allows, and the copy. Both arrive as options.
247
304
  */
248
- declare function useRichTextEditor({ labels, onUpdate, content, onInsertLink, onInsertImage, extraExtensions, extraToolbar, contentClassName, allowBase64Images, }: UseRichTextEditorOptions): UseRichTextEditorResult;
305
+ declare function useRichTextEditor({ labels, onUpdate, content, onInsertLink, onInsertImage, extraExtensions, extraToolbar, replaceToolbar, contentClassName, allowBase64Images, editorProps, }: UseRichTextEditorOptions): UseRichTextEditorResult;
249
306
 
250
307
  /**
251
308
  * One entry in the palette.
@@ -354,36 +411,6 @@ interface InsertLinkDialogProps {
354
411
  */
355
412
  declare function InsertLinkDialog({ open, onOpenChange, labels, url, onUrlChange, text, onTextChange, needsText, onApply, onCancel, }: InsertLinkDialogProps): react.JSX.Element;
356
413
 
357
- interface LinkBubbleMenuLabels {
358
- open: string;
359
- edit: string;
360
- remove: string;
361
- }
362
- interface LinkBubbleMenuProps {
363
- editor: Editor | null;
364
- labels: LinkBubbleMenuLabels;
365
- /** Opens the consumer's link dialog, pre-filled with the current href. */
366
- onEdit: () => void;
367
- /**
368
- * Follows the link. Left to the consumer for the cases this package cannot
369
- * judge — routing instead of navigating, or confirming first.
370
- *
371
- * Defaults to opening a new tab, which is also what Ctrl/Cmd-click does, so
372
- * the author never loses unsaved work just by checking a URL.
373
- */
374
- onOpen?: (href: string) => void;
375
- }
376
- /**
377
- * A floating bar that appears when the caret sits inside a link, offering to
378
- * open, edit or unlink it.
379
- *
380
- * It exists because an editor cannot let a plain click navigate: clicking is how
381
- * you place the caret to fix a typo, so `openOnClick` stays false and this gives
382
- * the author a deliberate way out to the URL. Ctrl/Cmd-click is wired separately,
383
- * on the editor itself, for people who expect the shortcut.
384
- */
385
- declare function LinkBubbleMenu({ editor, labels, onEdit, onOpen }: LinkBubbleMenuProps): react.JSX.Element | null;
386
-
387
414
  interface ResizableImageOptions {
388
415
  /** Already translated: names the resize handle for assistive tech. */
389
416
  resizeLabel: string;
package/dist/editor.js CHANGED
@@ -974,6 +974,78 @@ function createResizableImage(options) {
974
974
  }
975
975
  });
976
976
  }
977
+ function LinkActions({ editor, labels, onEdit, onOpen }) {
978
+ const href = useEditorState({
979
+ editor,
980
+ selector: ({ editor: current }) => current.getAttributes("link").href ?? ""
981
+ });
982
+ return /* @__PURE__ */ jsxs(
983
+ "div",
984
+ {
985
+ "data-slot": "link-bubble-menu",
986
+ className: "bg-popover text-popover-foreground flex items-center gap-1 rounded-md border p-1 shadow-md",
987
+ children: [
988
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground max-w-[16rem] truncate px-2 text-xs", children: href }),
989
+ /* @__PURE__ */ jsx(
990
+ Button,
991
+ {
992
+ type: "button",
993
+ size: "sm",
994
+ variant: "ghost",
995
+ title: labels.open,
996
+ "aria-label": labels.open,
997
+ disabled: !href,
998
+ onClick: () => {
999
+ if (!href) return;
1000
+ if (onOpen) {
1001
+ onOpen(href);
1002
+ return;
1003
+ }
1004
+ window.open(href, "_blank", "noopener,noreferrer");
1005
+ },
1006
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ jsx(
1010
+ Button,
1011
+ {
1012
+ type: "button",
1013
+ size: "sm",
1014
+ variant: "ghost",
1015
+ title: labels.edit,
1016
+ "aria-label": labels.edit,
1017
+ onClick: onEdit,
1018
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsx(
1022
+ Button,
1023
+ {
1024
+ type: "button",
1025
+ size: "sm",
1026
+ variant: "ghost",
1027
+ className: "text-destructive",
1028
+ title: labels.remove,
1029
+ "aria-label": labels.remove,
1030
+ onClick: () => editor.chain().focus().extendMarkRange("link").unsetLink().run(),
1031
+ children: /* @__PURE__ */ jsx(Unlink, { className: "h-4 w-4" })
1032
+ }
1033
+ )
1034
+ ]
1035
+ }
1036
+ );
1037
+ }
1038
+ function LinkBubbleMenu({ editor, labels, onEdit, onOpen }) {
1039
+ const shouldShow = useCallback(
1040
+ // Only while the caret is actually in a link — otherwise this bar would
1041
+ // compete with any other bubble menu the consumer adds.
1042
+ ({ editor: current }) => current.isActive("link"),
1043
+ []
1044
+ );
1045
+ const options = useMemo(() => ({ placement: "bottom", offset: 8 }), []);
1046
+ if (!editor) return null;
1047
+ return /* @__PURE__ */ jsx(BubbleMenu, { editor, shouldShow, options, children: /* @__PURE__ */ jsx(LinkActions, { editor, labels, onEdit, onOpen }) });
1048
+ }
977
1049
  function ListMenu({ editor, triggerLabel, icon, options }) {
978
1050
  const active = options.find((option) => editor?.isActive(option.node));
979
1051
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
@@ -1026,8 +1098,10 @@ function useRichTextEditor({
1026
1098
  onInsertImage,
1027
1099
  extraExtensions = [],
1028
1100
  extraToolbar = [],
1101
+ replaceToolbar,
1029
1102
  contentClassName,
1030
- allowBase64Images = false
1103
+ allowBase64Images = false,
1104
+ editorProps
1031
1105
  }) {
1032
1106
  const [linkNeedsText, setLinkNeedsText] = useState(false);
1033
1107
  const editor = useEditor({
@@ -1050,6 +1124,7 @@ function useRichTextEditor({
1050
1124
  // repaint as the selection and attributes change.
1051
1125
  shouldRerenderOnTransaction: true,
1052
1126
  editorProps: {
1127
+ ...editorProps,
1053
1128
  attributes: {
1054
1129
  // ProseMirror's editable div needs its own accessible name: a
1055
1130
  // visible <Label> beside it is decoration, and a screen reader
@@ -1219,7 +1294,9 @@ function useRichTextEditor({
1219
1294
  )
1220
1295
  }
1221
1296
  ];
1222
- let items = builtIn;
1297
+ let items = replaceToolbar ? builtIn.map(
1298
+ (item) => item.kind !== "separator" && replaceToolbar[item.id] ? replaceToolbar[item.id] : item
1299
+ ) : builtIn;
1223
1300
  const appended = [];
1224
1301
  for (const group of extraToolbar) {
1225
1302
  if (!group.after) {
@@ -1236,8 +1313,9 @@ function useRichTextEditor({
1236
1313
  items = [...items.slice(0, at + 1), ...group.items, ...items.slice(at + 1)];
1237
1314
  }
1238
1315
  return [...items, ...appended];
1239
- }, [labels, extraToolbar, onInsertImage, openLink]);
1240
- return { editor, toolbar, linkNeedsText, currentLinkHref, applyLink };
1316
+ }, [labels, extraToolbar, replaceToolbar, onInsertImage, openLink]);
1317
+ const linkMenu = /* @__PURE__ */ jsx(LinkBubbleMenu, { editor, labels: labels.link, onEdit: openLink });
1318
+ return { editor, toolbar, linkMenu, linkNeedsText, currentLinkHref, applyLink };
1241
1319
  }
1242
1320
  function ColorMenu({ editor, labels, icon, swatches }) {
1243
1321
  const active = swatches.find((swatch) => editor?.isActive(swatch.mark));
@@ -1377,78 +1455,6 @@ function InsertLinkDialog({
1377
1455
  ] })
1378
1456
  ] }) });
1379
1457
  }
1380
- function LinkActions({ editor, labels, onEdit, onOpen }) {
1381
- const href = useEditorState({
1382
- editor,
1383
- selector: ({ editor: current }) => current.getAttributes("link").href ?? ""
1384
- });
1385
- return /* @__PURE__ */ jsxs(
1386
- "div",
1387
- {
1388
- "data-slot": "link-bubble-menu",
1389
- className: "bg-popover text-popover-foreground flex items-center gap-1 rounded-md border p-1 shadow-md",
1390
- children: [
1391
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground max-w-[16rem] truncate px-2 text-xs", children: href }),
1392
- /* @__PURE__ */ jsx(
1393
- Button,
1394
- {
1395
- type: "button",
1396
- size: "sm",
1397
- variant: "ghost",
1398
- title: labels.open,
1399
- "aria-label": labels.open,
1400
- disabled: !href,
1401
- onClick: () => {
1402
- if (!href) return;
1403
- if (onOpen) {
1404
- onOpen(href);
1405
- return;
1406
- }
1407
- window.open(href, "_blank", "noopener,noreferrer");
1408
- },
1409
- children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
1410
- }
1411
- ),
1412
- /* @__PURE__ */ jsx(
1413
- Button,
1414
- {
1415
- type: "button",
1416
- size: "sm",
1417
- variant: "ghost",
1418
- title: labels.edit,
1419
- "aria-label": labels.edit,
1420
- onClick: onEdit,
1421
- children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
1422
- }
1423
- ),
1424
- /* @__PURE__ */ jsx(
1425
- Button,
1426
- {
1427
- type: "button",
1428
- size: "sm",
1429
- variant: "ghost",
1430
- className: "text-destructive",
1431
- title: labels.remove,
1432
- "aria-label": labels.remove,
1433
- onClick: () => editor.chain().focus().extendMarkRange("link").unsetLink().run(),
1434
- children: /* @__PURE__ */ jsx(Unlink, { className: "h-4 w-4" })
1435
- }
1436
- )
1437
- ]
1438
- }
1439
- );
1440
- }
1441
- function LinkBubbleMenu({ editor, labels, onEdit, onOpen }) {
1442
- const shouldShow = useCallback(
1443
- // Only while the caret is actually in a link — otherwise this bar would
1444
- // compete with any other bubble menu the consumer adds.
1445
- ({ editor: current }) => current.isActive("link"),
1446
- []
1447
- );
1448
- const options = useMemo(() => ({ placement: "bottom", offset: 8 }), []);
1449
- if (!editor) return null;
1450
- return /* @__PURE__ */ jsx(BubbleMenu, { editor, shouldShow, options, children: /* @__PURE__ */ jsx(LinkActions, { editor, labels, onEdit, onOpen }) });
1451
- }
1452
1458
 
1453
1459
  export { ColorMenu, EDITOR_CONTENT_STYLE, InsertImageDialog, InsertLinkDialog, LinkBubbleMenu, ListMenu, RichTextEditor, RichTextToolbar, TableMenu, createResizableImage, useRichTextEditor };
1454
1460
  //# sourceMappingURL=editor.js.map