@sesamehr/react-design-system 3.6.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.
- package/dist/Display/RichText/RichText.d.ts +31 -0
- package/dist/Display/RichText/index.d.ts +26 -0
- package/dist/Display/RichText/sanitizeHtml.d.ts +18 -0
- package/dist/Forms/Inputs/Calendar/Calendar.d.ts +52 -9
- package/dist/Forms/Inputs/Calendar/CalendarRange.d.ts +33 -9
- package/dist/Forms/Inputs/DatePicker/DatePicker.d.ts +19 -1
- package/dist/Forms/Inputs/DatePicker/DateRangePicker.d.ts +19 -1
- package/dist/Forms/Inputs/RichTextEditor/RichTextEditor.d.ts +48 -0
- package/dist/Forms/Inputs/RichTextEditor/RichTextToolbar/RichTextToolbar.d.ts +19 -0
- package/dist/Forms/Inputs/RichTextEditor/RichTextToolbar/index.d.ts +1 -0
- package/dist/Forms/Inputs/RichTextEditor/RichTextToolbarButton/RichTextToolbarButton.d.ts +19 -0
- package/dist/Forms/Inputs/RichTextEditor/RichTextToolbarButton/index.d.ts +1 -0
- package/dist/Forms/Inputs/RichTextEditor/commands.d.ts +67 -0
- package/dist/Forms/Inputs/RichTextEditor/devChecks.d.ts +92 -0
- package/dist/Forms/Inputs/RichTextEditor/extensions.d.ts +54 -0
- package/dist/Forms/Inputs/RichTextEditor/index.d.ts +7 -0
- package/dist/Forms/Inputs/RichTextEditor/useRichTextAdapter.d.ts +56 -0
- package/dist/Forms/Inputs/RichTextEditor/useRichTextToolbar.d.ts +26 -0
- package/dist/Forms/Inputs/index.d.ts +1 -0
- package/dist/main.d.ts +2 -1
- package/dist/react-design-system.css +1 -1
- package/dist/react-design-system.js +12348 -10905
- package/dist/react-design-system.umd.cjs +143 -58
- package/package.json +21 -1
|
@@ -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
|
-
/**
|
|
48
|
-
|
|
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
|
-
/**
|
|
49
|
-
|
|
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';
|