@sesamehr/react-design-system 3.5.0 → 3.7.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.
Files changed (25) hide show
  1. package/dist/Data/Table/TableBulkHeader/TableBulkHeader.d.ts +11 -8
  2. package/dist/Display/RichText/RichText.d.ts +31 -0
  3. package/dist/Display/RichText/index.d.ts +26 -0
  4. package/dist/Display/RichText/sanitizeHtml.d.ts +18 -0
  5. package/dist/Forms/Inputs/Calendar/Calendar.d.ts +52 -9
  6. package/dist/Forms/Inputs/Calendar/CalendarRange.d.ts +33 -9
  7. package/dist/Forms/Inputs/DatePicker/DatePicker.d.ts +19 -1
  8. package/dist/Forms/Inputs/DatePicker/DateRangePicker.d.ts +19 -1
  9. package/dist/Forms/Inputs/RichTextEditor/RichTextEditor.d.ts +48 -0
  10. package/dist/Forms/Inputs/RichTextEditor/RichTextToolbar/RichTextToolbar.d.ts +19 -0
  11. package/dist/Forms/Inputs/RichTextEditor/RichTextToolbar/index.d.ts +1 -0
  12. package/dist/Forms/Inputs/RichTextEditor/RichTextToolbarButton/RichTextToolbarButton.d.ts +19 -0
  13. package/dist/Forms/Inputs/RichTextEditor/RichTextToolbarButton/index.d.ts +1 -0
  14. package/dist/Forms/Inputs/RichTextEditor/commands.d.ts +67 -0
  15. package/dist/Forms/Inputs/RichTextEditor/devChecks.d.ts +92 -0
  16. package/dist/Forms/Inputs/RichTextEditor/extensions.d.ts +54 -0
  17. package/dist/Forms/Inputs/RichTextEditor/index.d.ts +7 -0
  18. package/dist/Forms/Inputs/RichTextEditor/useRichTextAdapter.d.ts +56 -0
  19. package/dist/Forms/Inputs/RichTextEditor/useRichTextToolbar.d.ts +26 -0
  20. package/dist/Forms/Inputs/index.d.ts +1 -0
  21. package/dist/main.d.ts +2 -1
  22. package/dist/react-design-system.css +1 -1
  23. package/dist/react-design-system.js +12517 -10985
  24. package/dist/react-design-system.umd.cjs +143 -58
  25. package/package.json +21 -1
@@ -1,13 +1,16 @@
1
1
  import { ReactNode } from 'react';
2
- export interface TableBulkHeaderProps extends React.ComponentPropsWithoutRef<'div'> {
2
+ export interface TableBulkHeaderBarProps {
3
+ selectedCount: number;
4
+ total: number;
5
+ message: string;
6
+ }
7
+ export interface TableBulkHeaderProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {
3
8
  selectedCount?: number;
4
9
  total?: number;
5
- /** Custom feedback content (replaces the default text). */
6
- feedback?: (props: {
7
- selectedCount: number;
8
- total: number;
9
- }) => ReactNode;
10
- /** Action controls rendered on the right. */
11
- actions?: ReactNode;
10
+ /**
11
+ * The whole contents of the bar. A function receives
12
+ * `{ selectedCount, total, message }`; omitted, the bar renders `message`.
13
+ */
14
+ children?: ReactNode | ((props: TableBulkHeaderBarProps) => ReactNode);
12
15
  }
13
16
  export declare const TableBulkHeader: import('react').ForwardRefExoticComponent<TableBulkHeaderProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,31 @@
1
+ export interface RichTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'dangerouslySetInnerHTML'> {
2
+ /**
3
+ * Markup to render as a formatted document.
4
+ *
5
+ * **This is a prop carrying content, which the composition rules forbid, and
6
+ * it is here because the rule has no reading under which this could be
7
+ * `children`.** `children` takes *nodes*; what a caller has is a *string* —
8
+ * HTML out of a database, out of an API, out of a TipTap `getHTML()`. Hand a
9
+ * string to `children` and React renders it as text, escaped. So the string
10
+ * path is a prop, and the component owns the sanitising that makes it safe —
11
+ * which is the other half of why it is a prop at all. A caller reaching for
12
+ * `dangerouslySetInnerHTML` themselves is what this exists to replace, and
13
+ * the one use of it in the package is inside here.
14
+ *
15
+ * `children` is still the composition-first path and the one to reach for
16
+ * when the caller already has nodes: `<OxRichText><h2>…</h2></OxRichText>`
17
+ * needs no sanitising, because those are theirs and never went through a
18
+ * string. Both paths are styled by the same selectors, so neither is a
19
+ * second-class way to use this.
20
+ *
21
+ * Pass one or the other. With `html` set, `children` is not rendered.
22
+ */
23
+ html?: string;
24
+ }
25
+ /**
26
+ * RichText — mirrors `sesame/orxata-core` RichText.
27
+ *
28
+ * Content that was written somewhere else, rendered so it reads as a formatted
29
+ * document. It depends on nothing: no editor, no TipTap, not even installed.
30
+ */
31
+ export declare const RichText: import('react').ForwardRefExoticComponent<RichTextProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,26 @@
1
+ export { RichText } from './RichText.tsx';
2
+ export { sanitizeHtml } from './sanitizeHtml.ts';
3
+ /**
4
+ * Every node and mark the prose stylesheet in `RichText.module.css` draws — the
5
+ * ceiling the editor is allowed to produce, named rather than only documented.
6
+ *
7
+ * It is here, beside the stylesheet, and not beside the editor, because the
8
+ * stylesheet is what decides it. `OxRichTextEditor` reads this list in
9
+ * development and warns about anything in the caller's schema that is missing
10
+ * from it, and `RichText.test.tsx` asserts the other direction: that every name
11
+ * below has a selector in the stylesheet and that no `ox-rich-text` selector
12
+ * styles something absent from it. Between the two, adding an extension to the
13
+ * preset without styling it fails a test, and a consumer adding one of their
14
+ * own gets told at the console.
15
+ *
16
+ * The spellings are ProseMirror's schema names, which is what
17
+ * `editor.schema.nodes` and `editor.schema.marks` are keyed by — not TipTap's
18
+ * package names and not CSS tags. The tag each one renders is in
19
+ * `SELECTOR_FOR`, which only the test needs.
20
+ */
21
+ export declare const RICH_TEXT_SCHEMA: {
22
+ readonly nodes: readonly ["doc", "text", "paragraph", "heading", "bulletList", "orderedList", "listItem", "blockquote", "codeBlock", "horizontalRule", "hardBreak"];
23
+ readonly marks: readonly ["bold", "italic", "strike", "underline", "code", "link"];
24
+ };
25
+ export type RichTextSchemaNode = (typeof RICH_TEXT_SCHEMA.nodes)[number];
26
+ export type RichTextSchemaMark = (typeof RICH_TEXT_SCHEMA.marks)[number];
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The sanitiser itself could not be loaded — a failed chunk request, or a
3
+ * `dompurify` that is somehow not installed.
4
+ *
5
+ * What the caller must do with this is leave the content **empty**, which is
6
+ * what both components do. The one outcome that is not available is rendering
7
+ * the markup anyway: "the sanitiser is unavailable" is not a reason to skip
8
+ * sanitising, it is a reason to show nothing.
9
+ */
10
+ export declare const SANITIZER_UNAVAILABLE_ERROR: string;
11
+ /**
12
+ * Sanitised markup, ready to be written as HTML.
13
+ *
14
+ * Always a promise, including on the native path where nothing has to load. One
15
+ * microtask is not a frame, and a signature that is sometimes a string would
16
+ * make every caller branch on it.
17
+ */
18
+ export declare const sanitizeHtml: (html: string) => Promise<string>;
@@ -1,11 +1,6 @@
1
1
  import { CalendarDayState } from './CalendarDay.tsx';
2
2
  import { CalendarConstraints } from './constraints.ts';
3
3
  import { CalendarNav } from './nav.ts';
4
- /** One day. Falls back to `OxCalendarDay`. */
5
- type DayRenderer = (props: {
6
- date: Date;
7
- state: CalendarDayState;
8
- }) => React.ReactNode;
9
4
  /**
10
5
  * A month of days, and nothing around it. Mirrors `@sesame/orxata-core`
11
6
  * Calendar.
@@ -27,6 +22,43 @@ export interface CalendarProps extends Pick<React.HTMLAttributes<HTMLDivElement>
27
22
  /** The initially selected day, for the uncontrolled case. */
28
23
  defaultValue?: Date;
29
24
  onValueChange?: (value: Date | undefined) => void;
25
+ /**
26
+ * The first month on screen, to follow or drive the view from outside
27
+ * `header`. Any day within the month will do; only its year and month are
28
+ * read.
29
+ *
30
+ * ⚠️ **`visibleMonth` and not `month`, which is already taken** — by the
31
+ * month-name renderer below. Vue writes that as a *slot*, so it can also
32
+ * have a `month` prop; React writes a slot as a prop, so the two would
33
+ * collide on this one interface, and renaming the renderer would break a
34
+ * published API. Vue uses `visibleMonth` too rather than leaving the two
35
+ * packages with different names for the same new prop: props are the one part
36
+ * of the public API no lint rule compares, so a difference here is the kind
37
+ * nothing would catch.
38
+ *
39
+ * The handler is `onVisibleMonthChange`, derived from the prop the way the
40
+ * parity rules derive it (`update:field` → `field` + `onFieldChange`), and
41
+ * deliberately *not* `onMonthChange` — that is `react-day-picker`'s name for
42
+ * its own prop, which is our implementation detail and not our surface.
43
+ *
44
+ * Left out, the calendar picks its own starting month and moves itself.
45
+ * Passed, it still moves itself and reports every move — there is no
46
+ * controlled/uncontrolled split here, because the month is one piece of state
47
+ * that both sides write. See `moveMonth`.
48
+ */
49
+ visibleMonth?: Date;
50
+ onVisibleMonthChange?: (month: Date) => void;
51
+ /**
52
+ * The day the cursor moved to, every time it moves — including between days
53
+ * of one month.
54
+ *
55
+ * Emit-only on purpose, with no `focusedDay` prop to pair it with, which is
56
+ * why it is a plain `onFocusDay` and not a `focusedDay`/`onFocusedDayChange`
57
+ * couple. The focused day is `react-day-picker`'s own, and a second writable
58
+ * prop over it would fight `visibleMonth` for the same state. Vue spells this
59
+ * `@focus-day` for the same reasons.
60
+ */
61
+ onFocusDay?: (day: Date) => void;
30
62
  /** How many month grids to render. */
31
63
  months?: number;
32
64
  /** What can be picked, and where navigation stops. */
@@ -44,14 +76,25 @@ export interface CalendarProps extends Pick<React.HTMLAttributes<HTMLDivElement>
44
76
  month?: (props: {
45
77
  month: Date;
46
78
  }) => React.ReactNode;
47
- /** One day. Falls back to `OxCalendarDay`. */
48
- day?: DayRenderer;
79
+ /**
80
+ * One day. Falls back to `OxCalendarDay`.
81
+ *
82
+ * Written out rather than as the `DayRenderer` alias it is elsewhere in
83
+ * this file, and that is load-bearing: `orxata/max-props` decides a prop
84
+ * is a slot by looking for `ReactNode` in the annotation *text*, and it
85
+ * resolves no aliases. Behind the alias this render prop was counted as
86
+ * configuration, which put the component a prop over the limit it was
87
+ * nowhere near. The pickers already spell it out for the same reason.
88
+ */
89
+ day?: (props: {
90
+ date: Date;
91
+ state: CalendarDayState;
92
+ }) => React.ReactNode;
49
93
  /** How long a month takes to come in, in milliseconds. `0` turns it off. */
50
94
  animationDuration?: number;
51
95
  dataTestid: string;
52
96
  }
53
97
  export declare const Calendar: {
54
- ({ className, value, defaultValue, onValueChange, months, month: renderMonth, constraints, header, day, animationDuration, dataTestid, }: CalendarProps): import("react/jsx-runtime").JSX.Element;
98
+ ({ className, value, defaultValue, onValueChange, visibleMonth, onVisibleMonthChange, onFocusDay, months, month: renderMonth, constraints, header, day, animationDuration, dataTestid, }: CalendarProps): import("react/jsx-runtime").JSX.Element;
55
99
  displayName: string;
56
100
  };
57
- export {};
@@ -1,11 +1,6 @@
1
1
  import { CalendarDayState } from './CalendarDay.tsx';
2
2
  import { CalendarConstraints } from './constraints.ts';
3
3
  import { CalendarNav } from './nav.ts';
4
- /** One day. Falls back to `OxCalendarDay`. */
5
- type DayRenderer = (props: {
6
- date: Date;
7
- state: CalendarDayState;
8
- }) => React.ReactNode;
9
4
  /**
10
5
  * `start` and `end`, not `from` and `to` — those two words are already the
11
6
  * navigation bounds in `constraints`, and one word meaning two things inside
@@ -28,6 +23,24 @@ export interface CalendarRangeProps extends Pick<React.HTMLAttributes<HTMLDivEle
28
23
  /** The initially selected span, for the uncontrolled case. */
29
24
  defaultValue?: CalendarDateRange;
30
25
  onValueChange?: (value: CalendarDateRange) => void;
26
+ /**
27
+ * The first month on screen, to follow or drive the view from outside
28
+ * `header`. Any day within the month will do; only its year and month are
29
+ * read.
30
+ *
31
+ * ⚠️ `visibleMonth` and not `month`: `month` is already the month-name
32
+ * renderer below, because React writes Vue's `month` *slot* as a prop and the
33
+ * two names collide on one interface. Vue uses the same name for the same
34
+ * prop. `OxCalendar` carries the long version.
35
+ */
36
+ visibleMonth?: Date;
37
+ onVisibleMonthChange?: (month: Date) => void;
38
+ /**
39
+ * The day the cursor moved to, every time it moves — including between days
40
+ * of one month. Emit-only, with no `focusedDay` prop to pair it with. See
41
+ * `OxCalendar`.
42
+ */
43
+ onFocusDay?: (day: Date) => void;
31
44
  /** How many month grids to render. */
32
45
  months?: number;
33
46
  /** What can be picked, and where navigation stops. */
@@ -45,12 +58,23 @@ export interface CalendarRangeProps extends Pick<React.HTMLAttributes<HTMLDivEle
45
58
  month?: (props: {
46
59
  month: Date;
47
60
  }) => React.ReactNode;
48
- /** One day. Falls back to `OxCalendarDay`. */
49
- day?: DayRenderer;
61
+ /**
62
+ * One day. Falls back to `OxCalendarDay`.
63
+ *
64
+ * Written out rather than as the `DayRenderer` alias it is elsewhere in
65
+ * this file, and that is load-bearing: `orxata/max-props` decides a prop
66
+ * is a slot by looking for `ReactNode` in the annotation *text*, and it
67
+ * resolves no aliases. Behind the alias this render prop was counted as
68
+ * configuration, which put the component a prop over the limit it was
69
+ * nowhere near. The pickers already spell it out for the same reason.
70
+ */
71
+ day?: (props: {
72
+ date: Date;
73
+ state: CalendarDayState;
74
+ }) => React.ReactNode;
50
75
  dataTestid: string;
51
76
  }
52
77
  export declare const CalendarRange: {
53
- ({ className, value, defaultValue, onValueChange, months, month: renderMonth, constraints, header, day, dataTestid, }: CalendarRangeProps): import("react/jsx-runtime").JSX.Element;
78
+ ({ className, value, defaultValue, onValueChange, visibleMonth, onVisibleMonthChange, onFocusDay, months, month: renderMonth, constraints, header, day, dataTestid, }: CalendarRangeProps): import("react/jsx-runtime").JSX.Element;
54
79
  displayName: string;
55
80
  };
56
- export {};
@@ -24,6 +24,24 @@ export interface DatePickerProps extends Pick<React.ButtonHTMLAttributes<HTMLBut
24
24
  /** The initially chosen day, for the uncontrolled case. */
25
25
  defaultValue?: Date;
26
26
  onValueChange?: (value: Date | undefined) => void;
27
+ /**
28
+ * The first month the panel shows, to follow or drive the view from
29
+ * outside. Handed to `OxCalendar` untouched.
30
+ *
31
+ * ⚠️ `visibleMonth` and not `month`. Nothing on *this* interface is
32
+ * called `month`, so the name was free here — but `OxCalendar` cannot have
33
+ * it, because React writes Vue's `month` slot as a prop and the two
34
+ * collide there. One name across the family, and across both packages,
35
+ * beats a different one on each half of it.
36
+ */
37
+ visibleMonth?: Date;
38
+ onVisibleMonthChange?: (month: Date) => void;
39
+ /**
40
+ * The day the cursor moved to, every time it moves — including between
41
+ * days of one month, where `onVisibleMonthChange` stays silent. Emit-only,
42
+ * with no `focusedDay` prop to pair it with. See `OxCalendar`.
43
+ */
44
+ onFocusDay?: (day: Date) => void;
27
45
  /** How many month grids the popover shows. */
28
46
  months?: number;
29
47
  /** What can be picked, and where navigation stops. Passed straight through. */
@@ -46,6 +64,6 @@ export interface DatePickerProps extends Pick<React.ButtonHTMLAttributes<HTMLBut
46
64
  dataTestid: string;
47
65
  }
48
66
  export declare const DatePicker: {
49
- ({ className, value, defaultValue, onValueChange, months, constraints, disabled, children, icon, day, dataTestid, }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
67
+ ({ className, value, defaultValue, onValueChange, visibleMonth, onVisibleMonthChange, onFocusDay, months, constraints, disabled, children, icon, day, dataTestid, }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
50
68
  displayName: string;
51
69
  };
@@ -25,6 +25,24 @@ export interface DateRangePickerProps extends Pick<React.ButtonHTMLAttributes<HT
25
25
  /** The initially chosen span, for the uncontrolled case. */
26
26
  defaultValue?: CalendarDateRange;
27
27
  onValueChange?: (value: CalendarDateRange) => void;
28
+ /**
29
+ * The first month the panel shows, to follow or drive the view from
30
+ * outside. Handed to `OxCalendarRange` untouched.
31
+ *
32
+ * ⚠️ `visibleMonth` and not `month`. Nothing on *this* interface is
33
+ * called `month`, so the name was free here — but `OxCalendarRange` cannot have
34
+ * it, because React writes Vue's `month` slot as a prop and the two
35
+ * collide there. One name across the family, and across both packages,
36
+ * beats a different one on each half of it.
37
+ */
38
+ visibleMonth?: Date;
39
+ onVisibleMonthChange?: (month: Date) => void;
40
+ /**
41
+ * The day the cursor moved to, every time it moves — including between
42
+ * days of one month, where `onVisibleMonthChange` stays silent. Emit-only,
43
+ * with no `focusedDay` prop to pair it with. See `OxCalendarRange`.
44
+ */
45
+ onFocusDay?: (day: Date) => void;
28
46
  /** How many month grids the popover shows. */
29
47
  months?: number;
30
48
  /** What can be picked, and where navigation stops. Passed straight through. */
@@ -56,6 +74,6 @@ export interface DateRangePickerProps extends Pick<React.ButtonHTMLAttributes<HT
56
74
  dataTestid: string;
57
75
  }
58
76
  export declare const DateRangePicker: {
59
- ({ className, value, defaultValue, onValueChange, months, constraints, disabled, children, icon, aside, day, dataTestid, }: DateRangePickerProps): import("react/jsx-runtime").JSX.Element;
77
+ ({ className, value, defaultValue, onValueChange, visibleMonth, onVisibleMonthChange, onFocusDay, months, constraints, disabled, children, icon, aside, day, dataTestid, }: DateRangePickerProps): import("react/jsx-runtime").JSX.Element;
60
78
  displayName: string;
61
79
  };
@@ -0,0 +1,48 @@
1
+ import { RichTextSource } from './useRichTextAdapter.ts';
2
+ export interface RichTextEditorProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /**
4
+ * The editor, which the **caller** owns — created, configured and destroyed
5
+ * in their component, not in this one.
6
+ *
7
+ * Build it with `useEditor` from `@tiptap/react`:
8
+ *
9
+ * ```ts
10
+ * const editor = useEditor({
11
+ * extensions: oxRichTextExtensions(StarterKit),
12
+ * content: '<p>…</p>',
13
+ * });
14
+ * ```
15
+ *
16
+ * **Never `new Editor()`.** `useEditor` is what destroys the editor when the
17
+ * component unmounts; a hand-built one is never destroyed, so its ProseMirror
18
+ * view, its plugins and its DOM listeners outlive the component that made it.
19
+ * Nothing fails — it leaks, which is worse, because a leak shows up as a page
20
+ * that gets slower rather than as an error anyone can attribute.
21
+ *
22
+ * `undefined` is an ordinary value, not a mistake: `useEditor` returns `null`
23
+ * under `immediatelyRender: false`, and a caller may well render this before
24
+ * they have an editor. The surface renders at its full height meanwhile and
25
+ * the toolbar's buttons are disabled.
26
+ *
27
+ * A `RichTextAdapter` is accepted in place of an editor. That is the seam for
28
+ * an editor that is not TipTap: implement three methods and the toolbar works
29
+ * unchanged. In that case the surface is yours to render — put it in
30
+ * `children` — because an adapter has no DOM for this component to mount.
31
+ */
32
+ editor?: RichTextSource | null;
33
+ }
34
+ /**
35
+ * RichTextEditor — mirrors `sesame/orxata-core` RichTextEditor.
36
+ *
37
+ * `role="group"` is unconditional rather than only inside a field, for the same
38
+ * reason as `OxInputOtp`: this is a `<div>`, and a bare `<div>` cannot take a
39
+ * name from the author, so without the role the `aria-labelledby` is dropped on
40
+ * the floor. Outside an `<OxFieldGroup>` the group has no name, which is correct
41
+ * — this package ships no strings, so there is nothing to call it.
42
+ *
43
+ * It is a group and not a `textbox` because the toolbar's buttons are inside it:
44
+ * there are several focusable things here and one `id` cannot sit on all of
45
+ * them, which is why the wiring is `useFieldControlGroup()` rather than
46
+ * `useFieldControl()`.
47
+ */
48
+ export declare const RichTextEditor: import('react').ForwardRefExoticComponent<RichTextEditorProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,19 @@
1
+ import { Toolbar } from '@radix-ui/react-toolbar';
2
+ export type RichTextToolbarProps = React.ComponentPropsWithoutRef<typeof Toolbar>;
3
+ /**
4
+ * RichTextToolbar — mirrors `sesame/orxata-core` RichTextToolbar.
5
+ *
6
+ * The row above the editable surface. It holds buttons and nothing else of its
7
+ * own.
8
+ *
9
+ * Built on Radix's `Toolbar` rather than a `<div role="toolbar">`, and the
10
+ * difference is the keyboard: a toolbar is **one tab stop**, with the arrow
11
+ * keys moving between its controls. Written by hand that is a roving tabindex,
12
+ * a focus collection and `Home`/`End` handling; Radix already has all three,
13
+ * the same way reka does on the Vue side.
14
+ *
15
+ * No `data-slot`: a toolbar is not a controller, a label, a description, a
16
+ * message or an icon, and the attribute is only for those five roles. The
17
+ * control the field's label names is the `OxRichTextEditor` around this.
18
+ */
19
+ export declare const RichTextToolbar: import('react').ForwardRefExoticComponent<Omit<import('@radix-ui/react-toolbar').ToolbarProps & import('react').RefAttributes<HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ export { RichTextToolbar } from './RichTextToolbar.tsx';
@@ -0,0 +1,19 @@
1
+ import { RichTextCommand } from '../commands.ts';
2
+ export interface RichTextToolbarButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label' | 'aria-pressed' | 'children' | 'disabled' | 'onClick' | 'type'> {
3
+ /** Which command this button toggles. See `RichTextCommand`. */
4
+ command: RichTextCommand;
5
+ }
6
+ /**
7
+ * RichTextToolbarButton — mirrors `sesame/orxata-core` RichTextToolbarButton.
8
+ *
9
+ * One command, drawn as a toggle.
10
+ *
11
+ * `command` is the whole API, which is the point: the icon, the accessible name
12
+ * and the pressed state all come out of `RICH_TEXT_COMMANDS` and the adapter,
13
+ * so there is no way to write a button whose glyph says bold and whose click
14
+ * says italic. Props for those three would put that combination one typo away,
15
+ * and it is the kind of typo nobody catches in review because each line reads
16
+ * correctly on its own — which is also why all six of them are `Omit`ted above
17
+ * rather than left overridable.
18
+ */
19
+ export declare const RichTextToolbarButton: import('react').ForwardRefExoticComponent<RichTextToolbarButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1 @@
1
+ export { RichTextToolbarButton } from './RichTextToolbarButton.tsx';
@@ -0,0 +1,67 @@
1
+ import { ChainedCommands } from '@tiptap/core';
2
+ /**
3
+ * Everything `<OxRichTextToolbarButton command="…" />` accepts.
4
+ *
5
+ * Eleven, and the boundary is not arbitrary: **every one of these is a pure
6
+ * toggle with an exact glyph in `@sesame/orxata-icons`.** The things a toolbar
7
+ * usually also has are missing for two different reasons, and both are worth
8
+ * knowing before reaching for them:
9
+ *
10
+ * - **Undo, redo, a code block and a horizontal rule have no icon.** The
11
+ * package has `icon-arrow-back-up` and `icon-corner-up-right-double`, which
12
+ * are an arrow and a pair of arrows rather than the history glyphs; drawing
13
+ * undo with either would be a different control wearing the wrong sign. A
14
+ * code *block* has only `icon-code`, already spoken for by inline code, and
15
+ * a rule has only `icon-minus`. The commands themselves are all in the
16
+ * preset — `undoRedo` and `horizontalRule` are StarterKit extensions, and
17
+ * the keyboard shortcuts work — so what is missing is the buttons, not the
18
+ * behaviour.
19
+ * - **A link is not a toggle.** `toggleLink` needs an `href`, and a button
20
+ * whose whole API is `command` has nowhere to get one. Wiring a URL prompt
21
+ * in here would be deciding what that prompt looks like for everybody. The
22
+ * toolbar takes arbitrary children instead, so a caller puts their own
23
+ * control in it and calls `editor.chain().setLink(…)` — which is also how
24
+ * anything else outside this list gets into a toolbar. The mark is styled
25
+ * either way; see `RICH_TEXT_SCHEMA`.
26
+ */
27
+ export type RichTextCommand = 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'heading1' | 'heading2' | 'heading3' | 'bulletList' | 'orderedList' | 'blockquote';
28
+ export interface RichTextCommandSpec {
29
+ /**
30
+ * The asset itself, not a name. `@sesame/orxata-icons` exports each icon as
31
+ * the raw SVG string and `<Icon :icon="…">` takes it that way, and importing
32
+ * the eleven by subpath is what `orxata/icon-import-per-icon` asks for — the
33
+ * barrel would pull all 511 into the graph to draw eleven.
34
+ */
35
+ icon: string;
36
+ /**
37
+ * Lokalise key for the button's accessible name. The glyph is the whole
38
+ * control, so this is the only name a screen reader gets.
39
+ */
40
+ label: string;
41
+ /**
42
+ * What the command turns on, spelled the way `editor.isActive()` asks for
43
+ * it: a ProseMirror schema name, plus the attributes that distinguish one
44
+ * heading level from another.
45
+ */
46
+ schema: {
47
+ name: string;
48
+ attrs?: Record<string, unknown>;
49
+ };
50
+ /**
51
+ * Applied to a chain. A closure rather than a method name and an argument
52
+ * list, so the TipTap command names live in exactly one place and stay type
53
+ * checked — a union of method names would type the call as the intersection
54
+ * of their parameters, which `toggleHeading` and `toggleBold` do not share.
55
+ */
56
+ apply: (chain: ChainedCommands) => ChainedCommands;
57
+ }
58
+ /**
59
+ * The toolbar's whole vocabulary, in the order a toolbar reads: marks, then
60
+ * headings, then blocks.
61
+ *
62
+ * A record and not a function, because the button resolves its own icon, label
63
+ * and pressed state from it — the only thing a caller writes is `command`.
64
+ */
65
+ export declare const RICH_TEXT_COMMANDS: Record<RichTextCommand, RichTextCommandSpec>;
66
+ /** Every command, in toolbar order. */
67
+ export declare const RICH_TEXT_COMMAND_NAMES: RichTextCommand[];
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Checks that only run in development, kept out of the component so they can be
3
+ * tested without mounting anything and so the component reads as a component.
4
+ *
5
+ * Both replace an error that names nothing. That is the whole bar for putting a
6
+ * check here: not "this could go wrong", but "when this goes wrong, the message
7
+ * the developer gets does not say what happened".
8
+ *
9
+ * This file is identical in both packages. Nothing in it is framework-specific,
10
+ * which is the point — the schema, the extension preset and these checks port
11
+ * across unchanged because `@tiptap/vue-3` and `@tiptap/react` sit on the same
12
+ * `@tiptap/core`. Only the mount layer differs.
13
+ */
14
+ /**
15
+ * The shape both checks need from `editor.schema`, written structurally rather
16
+ * than imported from `prosemirror-model`.
17
+ *
18
+ * Importing the real `Schema` type would be harmless — types are erased — but
19
+ * writing it out is what lets a test call these functions with a plain object
20
+ * instead of standing up an editor.
21
+ */
22
+ export interface SchemaShape {
23
+ nodes: Record<string, unknown>;
24
+ marks: Record<string, unknown>;
25
+ }
26
+ /**
27
+ * Node and mark names in the editor's schema that the stylesheet does not draw.
28
+ *
29
+ * This is the ceiling made to tell you rather than only be documented. A
30
+ * consumer may pass any extension list they like — that is the point of owning
31
+ * the editor — and an extension the stylesheet has never heard of renders as
32
+ * unstyled markup: a task list with no checkboxes, a table with no cell
33
+ * borders, each looking like a bug in the design system rather than a gap in
34
+ * it.
35
+ *
36
+ * Comparing names rather than rendering is what makes this cheap enough to run
37
+ * on every schema change. The other direction — a styled name no extension
38
+ * produces — is a test's job rather than a runtime warning: see
39
+ * `RichText.test.*`.
40
+ */
41
+ export declare const uncoveredSchemaNames: (schema: SchemaShape) => string[];
42
+ export declare const schemaCoverageWarning: (uncovered: string[]) => string;
43
+ /**
44
+ * A dynamic import of one of the optional peers came back rejected.
45
+ *
46
+ * Which, for an optional peer, is the ordinary consequence of not installing
47
+ * it — so the one thing this must not be is an unhandled rejection reading
48
+ * "Failed to fetch dynamically imported module". Logged rather than thrown: the
49
+ * editor then renders its surface with nothing in it, which degrades visibly
50
+ * instead of taking the page down, and the rest of the design system does not
51
+ * depend on TipTap at all.
52
+ */
53
+ export declare const missingTipTapError: (specifier: string) => string;
54
+ /**
55
+ * A class, as far as `instanceof` is concerned. `abstract` so a class with a
56
+ * protected constructor still satisfies it.
57
+ */
58
+ type AnyClass = abstract new (..._args: never[]) => unknown;
59
+ /** What the editable component hands the probe, once its dynamic import lands. */
60
+ export interface DuplicateProbe {
61
+ /** `@tiptap/core`'s `Editor`, from the same import as the mount binding. */
62
+ Editor?: AnyClass;
63
+ }
64
+ export declare const DUPLICATE_TIPTAP_ERROR: string;
65
+ /**
66
+ * Two copies of TipTap in the dependency tree.
67
+ *
68
+ * ProseMirror reports it as `RangeError: Invalid content`, thrown from inside a
69
+ * transaction, pointing at a minified vendor frame and naming neither the
70
+ * package nor the version. Near-undebuggable the first time you meet it, and
71
+ * completely obvious once named — which is exactly the trade this makes.
72
+ *
73
+ * The probe is an identity test on `@tiptap/core`'s `Editor`, which arrives
74
+ * through the same dynamic import as the mount binding: if the editor we were
75
+ * handed is not an instance of it, it was built by a different copy of
76
+ * `@tiptap/core`, and the binding that is about to mount it reaches into
77
+ * `editor.view` and the renderer internals of the wrong one.
78
+ *
79
+ * `Editor` and not `prosemirror-model`'s `Schema`, which was the original
80
+ * brief. Two reasons: `@tiptap/vue-3` and `@tiptap/react` both subclass the
81
+ * core `Editor`, so a `useEditor` editor and a hand-built one both pass a
82
+ * healthy check; and mismatched `@tiptap/*` versions are the duplication npm
83
+ * actually produces, which is the one a developer can act on. A `Schema` probe
84
+ * would need `prosemirror-model` resolved from the *consumer's* tree to mean
85
+ * anything, which is a fourth optional peer for a narrower answer.
86
+ *
87
+ * `Editor` may be `undefined` — the import is dynamic and the first render
88
+ * happens before it lands — and an absent probe reports nothing rather than
89
+ * guessing.
90
+ */
91
+ export declare const duplicateCopyError: (editor: object, probe: DuplicateProbe) => string | undefined;
92
+ export {};
@@ -0,0 +1,54 @@
1
+ import { Extensions } from '@tiptap/core';
2
+ import { default as StarterKit } from '@tiptap/starter-kit';
3
+ /**
4
+ * The extension list both ends of a rich text field agree on.
5
+ *
6
+ * **The silent failure this exists to stop:** content written with an extension
7
+ * the renderer does not know does not error — ProseMirror drops those nodes on
8
+ * the way in, and what comes back is the same document with the headings turned
9
+ * into paragraphs and nothing anywhere saying so. One list, used by whatever
10
+ * writes and whatever renders, is the only way the two cannot drift.
11
+ *
12
+ * A caller may pass a subset. The ceiling is what the stylesheet draws, which
13
+ * is `RICH_TEXT_SCHEMA`, and `OxRichTextEditor` warns in development about
14
+ * anything in the editor's schema that is not in it.
15
+ *
16
+ * ---
17
+ *
18
+ * **Why this takes StarterKit as an argument instead of importing it.** The
19
+ * intended design was a plain exported array. It cannot be one, and the reason
20
+ * is worth writing down because the signature otherwise looks like ceremony:
21
+ *
22
+ * `oxRichTextExtensions` is published from `src/main.ts`, the barrel every
23
+ * consumer imports — including the one who only wants `OxButton`. A static
24
+ * `import StarterKit from '@tiptap/starter-kit'` anywhere in that graph becomes
25
+ * a top-level import in the built `orxata-core.js`, and a bundler resolves
26
+ * every import it finds before it decides what to tree-shake. For a consumer
27
+ * who has not installed TipTap that is a module-resolution failure in their
28
+ * build — webpack calls it an error, Rollup leaves a bare specifier that fails
29
+ * at runtime — over a feature they never touched. "TipTap is an optional peer
30
+ * dependency" would be false the moment anything imported it eagerly.
31
+ *
32
+ * An `async` factory that `import()`s it was the other option, and it is worse
33
+ * where it matters: `useEditor` needs its extensions synchronously, so every
34
+ * caller would need a top-level `await` and a `<Suspense>` around their own
35
+ * component in order to use a text field.
36
+ *
37
+ * So the caller hands over the StarterKit they already have — one import, in a
38
+ * file that is already importing `useEditor` from TipTap — and this package
39
+ * contributes the part that is actually its own: the configuration. The
40
+ * parameter's type is `typeof StarterKit`, which is erased, so this module has
41
+ * no runtime dependency on TipTap at all.
42
+ *
43
+ * ```ts
44
+ * import StarterKit from '@tiptap/starter-kit';
45
+ * import { useEditor } from '@tiptap/vue-3';
46
+ * import { oxRichTextExtensions } from '@sesame/orxata-core';
47
+ *
48
+ * const editor = useEditor({
49
+ * extensions: oxRichTextExtensions(StarterKit),
50
+ * content: '<p>…</p>',
51
+ * });
52
+ * ```
53
+ */
54
+ export declare const oxRichTextExtensions: (starterKit: typeof StarterKit) => Extensions;
@@ -0,0 +1,7 @@
1
+ export { RichTextEditor } from './RichTextEditor.tsx';
2
+ export { RichTextToolbar } from './RichTextToolbar/index.ts';
3
+ export { RichTextToolbarButton } from './RichTextToolbarButton/index.ts';
4
+ export { RICH_TEXT_COMMANDS, RICH_TEXT_COMMAND_NAMES, type RichTextCommand, type RichTextCommandSpec, } from './commands.ts';
5
+ export { oxRichTextExtensions } from './extensions.ts';
6
+ export { type RichTextAdapter, type RichTextSource, isRichTextAdapter, useRichTextAdapter, } from './useRichTextAdapter.ts';
7
+ export { useRichTextToolbar } from './useRichTextToolbar.ts';