beez-ui 0.6.2 → 0.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/CHANGELOG.md +150 -96
- package/README.md +33 -6
- package/dist/components/account-menu.d.ts +53 -0
- package/dist/components/account-menu.js +405 -0
- package/dist/components/alert.js +1 -1
- package/dist/components/animated-collapse.d.ts +19 -0
- package/dist/components/animated-collapse.js +81 -0
- package/dist/components/animated-count.d.ts +13 -0
- package/dist/components/animated-count.js +105 -0
- package/dist/components/animated-list-item.d.ts +25 -0
- package/dist/components/animated-list-item.js +132 -0
- package/dist/components/avatar.d.ts +2 -1
- package/dist/components/avatar.js +4 -3
- package/dist/components/bouncing-dots-loader.d.ts +19 -0
- package/dist/components/bouncing-dots-loader.js +47 -0
- package/dist/components/calendar.js +102 -0
- package/dist/components/carousel.js +61 -48
- package/dist/components/checkbox.js +1 -1
- package/dist/components/confirm-delete-button.d.ts +29 -0
- package/dist/components/confirm-delete-button.js +254 -0
- package/dist/components/data-table.js +34 -4
- package/dist/components/dropdown-menu.js +5 -2
- package/dist/components/empty-state.d.ts +24 -0
- package/dist/components/empty-state.js +37 -0
- package/dist/components/error-state.d.ts +20 -0
- package/dist/components/error-state.js +145 -0
- package/dist/components/external-browser-handoff.d.ts +27 -0
- package/dist/components/external-browser-handoff.js +306 -0
- package/dist/components/file-upload.d.ts +78 -0
- package/dist/components/file-upload.js +678 -0
- package/dist/components/filter-query-bar.js +11 -6
- package/dist/components/form.d.ts +5 -0
- package/dist/components/form.js +11 -9
- package/dist/components/info-popover.d.ts +23 -0
- package/dist/components/info-popover.js +158 -0
- package/dist/components/input-group.js +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/month-calendar-header.d.ts +65 -0
- package/dist/components/month-calendar-header.js +337 -0
- package/dist/components/month-grid.d.ts +59 -0
- package/dist/components/month-grid.js +301 -0
- package/dist/components/notification-bell.d.ts +50 -0
- package/dist/components/notification-bell.js +338 -0
- package/dist/components/notification-panel.d.ts +56 -0
- package/dist/components/notification-panel.js +281 -0
- package/dist/components/open-in-browser-cta.d.ts +22 -0
- package/dist/components/open-in-browser-cta.js +61 -0
- package/dist/components/pagination.d.ts +1 -0
- package/dist/components/pagination.js +9 -4
- package/dist/components/presence-swap.d.ts +22 -0
- package/dist/components/presence-swap.js +162 -0
- package/dist/components/progress-ring.d.ts +20 -0
- package/dist/components/progress-ring.js +53 -0
- package/dist/components/pwa-update-control.d.ts +18 -0
- package/dist/components/pwa-update-control.js +119 -0
- package/dist/components/radio-group.js +1 -1
- package/dist/components/reaction-button.d.ts +26 -0
- package/dist/components/reaction-button.js +114 -0
- package/dist/components/rich-link-editor.d.ts +25 -0
- package/dist/components/rich-link-editor.js +320 -0
- package/dist/components/rich-markdown-content.d.ts +10 -0
- package/dist/components/rich-markdown-content.js +56 -0
- package/dist/components/rich-text-content.d.ts +19 -0
- package/dist/components/rich-text-content.js +26 -0
- package/dist/components/select.js +1 -1
- package/dist/components/sheet.js +12 -9
- package/dist/components/sidebar.js +9 -5
- package/dist/components/skeleton.d.ts +6 -0
- package/dist/components/skeleton.js +7 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/textarea.js +1 -1
- package/dist/components/typing-animation.js +42 -15
- package/dist/emoji-picker.d.ts +27 -0
- package/dist/emoji-picker.js +283 -0
- package/dist/hooks/use-horizontal-swipe.d.ts +23 -0
- package/dist/hooks/use-horizontal-swipe.js +80 -0
- package/dist/hooks/use-is-hydrated.d.ts +6 -0
- package/dist/hooks/use-is-hydrated.js +20 -0
- package/dist/hooks/use-minute-clock.d.ts +23 -0
- package/dist/hooks/use-minute-clock.js +147 -0
- package/dist/hooks/use-month-transition-direction.d.ts +21 -0
- package/dist/hooks/use-month-transition-direction.js +110 -0
- package/dist/hooks/use-rich-link-editor.d.ts +55 -0
- package/dist/hooks/use-rich-link-editor.js +515 -0
- package/dist/hooks/use-viewer-time-zone.d.ts +6 -0
- package/dist/hooks/use-viewer-time-zone.js +20 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +43 -0
- package/dist/lib/browser-clipboard.d.ts +13 -0
- package/dist/lib/browser-clipboard.js +48 -0
- package/dist/lib/browser-navigation.d.ts +21 -0
- package/dist/lib/browser-navigation.js +42 -0
- package/dist/lib/column-filter-value.d.ts +12 -0
- package/dist/lib/column-filter-value.js +52 -0
- package/dist/lib/file-acceptance.d.ts +43 -0
- package/dist/lib/file-acceptance.js +107 -0
- package/dist/lib/format-file-size.d.ts +16 -0
- package/dist/lib/format-file-size.js +34 -0
- package/dist/lib/fuzzy-search.d.ts +17 -0
- package/dist/lib/fuzzy-search.js +183 -0
- package/dist/lib/horizontal-swipe.d.ts +23 -0
- package/dist/lib/horizontal-swipe.js +29 -0
- package/dist/lib/in-app-browser.d.ts +35 -0
- package/dist/lib/in-app-browser.js +80 -0
- package/dist/lib/month-grid.d.ts +42 -0
- package/dist/lib/month-grid.js +74 -0
- package/dist/lib/name-initials.d.ts +7 -0
- package/dist/lib/name-initials.js +11 -0
- package/dist/lib/rich-text/link-markdown-constants.d.ts +147 -0
- package/dist/lib/rich-text/link-markdown-constants.js +458 -0
- package/dist/lib/rich-text/link-markdown-types.d.ts +53 -0
- package/dist/lib/rich-text/link-markdown-types.js +1 -0
- package/dist/lib/rich-text/link-markdown.d.ts +193 -0
- package/dist/lib/rich-text/link-markdown.js +592 -0
- package/dist/lib/rich-text/rich-markdown.d.ts +41 -0
- package/dist/lib/rich-text/rich-markdown.js +105 -0
- package/dist/lib/rich-text/rich-text-editor-dom.d.ts +26 -0
- package/dist/lib/rich-text/rich-text-editor-dom.js +79 -0
- package/dist/motion/glide-indicator.d.ts +37 -3
- package/dist/motion/glide-indicator.js +37 -3
- package/dist/motion/motion-slot.js +47 -18
- package/dist/motion/surface-keyframes.js +18 -6
- package/dist/motion/tokens.d.ts +41 -2
- package/dist/motion/tokens.js +40 -1
- package/dist/providers/beez-ui-provider.js +49 -17
- package/dist/styles.css +1 -1
- package/package.json +212 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as _c } from "react/compiler-runtime";
|
|
3
|
+
/** Shares one minute-aligned clock across components, hydration-safe and with exact wake-ups. */
|
|
4
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
5
|
+
const MILLISECONDS_PER_MINUTE = 6e4;
|
|
6
|
+
const NO_WAKE_UP_TIMES = [];
|
|
7
|
+
const clockListeners = new Set();
|
|
8
|
+
/**
|
|
9
|
+
* Latest instant the clock settled on. It only moves forward: minute ticks
|
|
10
|
+
* raise it to the current minute and wake-ups (or an instant the server
|
|
11
|
+
* confirmed) raise it to that exact instant. Null while nobody observes the
|
|
12
|
+
* clock, so a later mount starts again from the real time.
|
|
13
|
+
*/
|
|
14
|
+
let settledClockTime = null;
|
|
15
|
+
let minuteTickTimeoutId = null;
|
|
16
|
+
function truncateToMinute(time) {
|
|
17
|
+
return Math.floor(time / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;
|
|
18
|
+
}
|
|
19
|
+
function notifyClockListeners() {
|
|
20
|
+
for (const listener of clockListeners) {
|
|
21
|
+
listener();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Moves the clock forward to `instantTime` (epoch ms) and re-renders every
|
|
26
|
+
* consumer. Earlier instants are ignored, so the clock never goes back. Use
|
|
27
|
+
* it when a scheduled boundary is reached or the server proved that an
|
|
28
|
+
* instant already passed (for example an occurrence it reported as ended
|
|
29
|
+
* while the local clock still lagged behind).
|
|
30
|
+
*
|
|
31
|
+
* @param instantTime - Instant the clock must reach, in epoch milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
export function advanceMinuteClockTo(instantTime) {
|
|
34
|
+
if (settledClockTime !== null && instantTime <= settledClockTime) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
settledClockTime = instantTime;
|
|
38
|
+
notifyClockListeners();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Schedules the next tick on the next minute boundary. Each tick measures the
|
|
42
|
+
* remaining time again instead of relying on a fixed interval, so the clock
|
|
43
|
+
* never lags by the mount offset and a timer that fires a little early or
|
|
44
|
+
* late re-aligns on the following boundary.
|
|
45
|
+
*/
|
|
46
|
+
function scheduleNextMinuteTick() {
|
|
47
|
+
const currentTime = Date.now();
|
|
48
|
+
const millisecondsToNextMinute = truncateToMinute(currentTime) + MILLISECONDS_PER_MINUTE - currentTime;
|
|
49
|
+
minuteTickTimeoutId = window.setTimeout(() => {
|
|
50
|
+
scheduleNextMinuteTick();
|
|
51
|
+
notifyClockListeners();
|
|
52
|
+
}, millisecondsToNextMinute);
|
|
53
|
+
}
|
|
54
|
+
function stopMinuteTicks() {
|
|
55
|
+
if (minuteTickTimeoutId !== null) {
|
|
56
|
+
window.clearTimeout(minuteTickTimeoutId);
|
|
57
|
+
minuteTickTimeoutId = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function subscribe(onStoreChange) {
|
|
61
|
+
clockListeners.add(onStoreChange);
|
|
62
|
+
if (clockListeners.size === 1) {
|
|
63
|
+
scheduleNextMinuteTick();
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
clockListeners.delete(onStoreChange);
|
|
67
|
+
if (clockListeners.size === 0) {
|
|
68
|
+
stopMinuteTicks();
|
|
69
|
+
settledClockTime = null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function getSnapshot() {
|
|
74
|
+
const currentMinuteTime = truncateToMinute(Date.now());
|
|
75
|
+
if (settledClockTime === null || currentMinuteTime > settledClockTime) {
|
|
76
|
+
settledClockTime = currentMinuteTime;
|
|
77
|
+
}
|
|
78
|
+
return settledClockTime;
|
|
79
|
+
}
|
|
80
|
+
function getServerSnapshot() {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function findNextWakeUpTime(wakeUpTimes, nowTime) {
|
|
84
|
+
let nextWakeUpTime = null;
|
|
85
|
+
for (const wakeUpTime of wakeUpTimes) {
|
|
86
|
+
if (wakeUpTime > nowTime && (nextWakeUpTime === null || wakeUpTime < nextWakeUpTime)) {
|
|
87
|
+
nextWakeUpTime = wakeUpTime;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return nextWakeUpTime;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Current time, refreshed on every minute boundary and at the exact instant of
|
|
94
|
+
* each wake-up time that falls before the next boundary. Between boundaries it
|
|
95
|
+
* holds the minute (or the last wake-up instant) it settled on. Returns null
|
|
96
|
+
* during server rendering and hydration so time-dependent UI never produces a
|
|
97
|
+
* server/client markup mismatch; it settles to a number right after mount.
|
|
98
|
+
*
|
|
99
|
+
* @param wakeUpTimes - Instants (epoch ms) where time-dependent UI changes,
|
|
100
|
+
* such as the end of an occurrence on screen; the clock re-renders exactly
|
|
101
|
+
* then instead of waiting for the next minute.
|
|
102
|
+
* @returns The clock value in epoch milliseconds, or null before hydration.
|
|
103
|
+
*/
|
|
104
|
+
export function useMinuteClock(t0) {
|
|
105
|
+
const $ = _c(8);
|
|
106
|
+
const wakeUpTimes = t0 === undefined ? NO_WAKE_UP_TIMES : t0;
|
|
107
|
+
const nowTime = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
108
|
+
let t1;
|
|
109
|
+
if ($[0] !== nowTime || $[1] !== wakeUpTimes) {
|
|
110
|
+
t1 = nowTime === null ? null : findNextWakeUpTime(wakeUpTimes, nowTime);
|
|
111
|
+
$[0] = nowTime;
|
|
112
|
+
$[1] = wakeUpTimes;
|
|
113
|
+
$[2] = t1;
|
|
114
|
+
} else {
|
|
115
|
+
t1 = $[2];
|
|
116
|
+
}
|
|
117
|
+
const nextWakeUpTime = t1;
|
|
118
|
+
let t2;
|
|
119
|
+
if ($[3] !== nextWakeUpTime) {
|
|
120
|
+
t2 = () => {
|
|
121
|
+
if (nextWakeUpTime === null) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const millisecondsToWakeUp = nextWakeUpTime - Date.now();
|
|
125
|
+
if (millisecondsToWakeUp > MILLISECONDS_PER_MINUTE) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const wakeUpTimeoutId = window.setTimeout(() => advanceMinuteClockTo(nextWakeUpTime), Math.max(0, millisecondsToWakeUp));
|
|
129
|
+
return () => window.clearTimeout(wakeUpTimeoutId);
|
|
130
|
+
};
|
|
131
|
+
$[3] = nextWakeUpTime;
|
|
132
|
+
$[4] = t2;
|
|
133
|
+
} else {
|
|
134
|
+
t2 = $[4];
|
|
135
|
+
}
|
|
136
|
+
let t3;
|
|
137
|
+
if ($[5] !== nextWakeUpTime || $[6] !== nowTime) {
|
|
138
|
+
t3 = [nextWakeUpTime, nowTime];
|
|
139
|
+
$[5] = nextWakeUpTime;
|
|
140
|
+
$[6] = nowTime;
|
|
141
|
+
$[7] = t3;
|
|
142
|
+
} else {
|
|
143
|
+
t3 = $[7];
|
|
144
|
+
}
|
|
145
|
+
useEffect(t2, t3);
|
|
146
|
+
return nowTime;
|
|
147
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const MONTH_TRANSITION_DIRECTION: {
|
|
2
|
+
readonly next: "next";
|
|
3
|
+
readonly none: "none";
|
|
4
|
+
readonly previous: "previous";
|
|
5
|
+
};
|
|
6
|
+
export type MonthTransitionDirection = (typeof MONTH_TRANSITION_DIRECTION)[keyof typeof MONTH_TRANSITION_DIRECTION];
|
|
7
|
+
/**
|
|
8
|
+
* Compares two `YYYY-MM` keys, which sort chronologically as plain strings.
|
|
9
|
+
* @param fromMonth - Month shown before, or null when there is none.
|
|
10
|
+
* @param toMonth - Month shown now.
|
|
11
|
+
* @returns `next` or `previous` when the month changed, `none` otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveMonthTransitionDirection(fromMonth: string | null, toMonth: string): MonthTransitionDirection;
|
|
14
|
+
/**
|
|
15
|
+
* Direction of the latest month change of a calendar: a new `month` on the same instance, or a
|
|
16
|
+
* fresh mount right after another month of the same calendar was on screen.
|
|
17
|
+
* @param scopeKey - Calendar identity, so different calendars do not orient each other.
|
|
18
|
+
* @param month - Visible `YYYY-MM` month.
|
|
19
|
+
* @returns Direction to animate the month entrance with.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useMonthTransitionDirection(scopeKey: string, month: string): MonthTransitionDirection;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as _c } from "react/compiler-runtime";
|
|
3
|
+
/** Tracks which way the visible month moved, to orient month entrance animations. */
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
export const MONTH_TRANSITION_DIRECTION = {
|
|
6
|
+
next: "next",
|
|
7
|
+
none: "none",
|
|
8
|
+
previous: "previous"
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* How long, in milliseconds, the last shown month still orients the next calendar mount. Month
|
|
12
|
+
* links may remount the calendar behind a route loading state, so the memory must outlive that
|
|
13
|
+
* gap but not a later, unrelated visit.
|
|
14
|
+
*/
|
|
15
|
+
const SHOWN_MONTH_MEMORY_MS = 5e3;
|
|
16
|
+
/**
|
|
17
|
+
* Last month shown by a calendar in this tab. It is written only from effects, which never run on
|
|
18
|
+
* the server, so server renders and the hydration render always read `null` and match.
|
|
19
|
+
*/
|
|
20
|
+
let lastShownMonth = null;
|
|
21
|
+
/**
|
|
22
|
+
* Records the month a calendar is showing.
|
|
23
|
+
* @param scopeKey - Calendar identity, such as a group or resource id.
|
|
24
|
+
* @param month - Visible `YYYY-MM` month.
|
|
25
|
+
*/
|
|
26
|
+
function rememberShownMonth(scopeKey, month) {
|
|
27
|
+
lastShownMonth = {
|
|
28
|
+
month,
|
|
29
|
+
shownAt: Date.now(),
|
|
30
|
+
scopeKey
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reads the month recently shown by the same calendar, if still within the memory window.
|
|
35
|
+
* @param scopeKey - Calendar identity.
|
|
36
|
+
* @returns The month, or null.
|
|
37
|
+
*/
|
|
38
|
+
function readRecentlyShownMonth(scopeKey) {
|
|
39
|
+
if (lastShownMonth === null || lastShownMonth.scopeKey !== scopeKey || Date.now() - lastShownMonth.shownAt > SHOWN_MONTH_MEMORY_MS) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return lastShownMonth.month;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Compares two `YYYY-MM` keys, which sort chronologically as plain strings.
|
|
46
|
+
* @param fromMonth - Month shown before, or null when there is none.
|
|
47
|
+
* @param toMonth - Month shown now.
|
|
48
|
+
* @returns `next` or `previous` when the month changed, `none` otherwise.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveMonthTransitionDirection(fromMonth, toMonth) {
|
|
51
|
+
if (fromMonth === null || fromMonth === toMonth) return MONTH_TRANSITION_DIRECTION.none;
|
|
52
|
+
return toMonth > fromMonth ? MONTH_TRANSITION_DIRECTION.next : MONTH_TRANSITION_DIRECTION.previous;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Direction of the latest month change of a calendar: a new `month` on the same instance, or a
|
|
56
|
+
* fresh mount right after another month of the same calendar was on screen.
|
|
57
|
+
* @param scopeKey - Calendar identity, so different calendars do not orient each other.
|
|
58
|
+
* @param month - Visible `YYYY-MM` month.
|
|
59
|
+
* @returns Direction to animate the month entrance with.
|
|
60
|
+
*/
|
|
61
|
+
export function useMonthTransitionDirection(scopeKey, month) {
|
|
62
|
+
const $ = _c(11);
|
|
63
|
+
let t0;
|
|
64
|
+
if ($[0] !== month || $[1] !== scopeKey) {
|
|
65
|
+
t0 = () => ({
|
|
66
|
+
direction: resolveMonthTransitionDirection(readRecentlyShownMonth(scopeKey), month),
|
|
67
|
+
month
|
|
68
|
+
});
|
|
69
|
+
$[0] = month;
|
|
70
|
+
$[1] = scopeKey;
|
|
71
|
+
$[2] = t0;
|
|
72
|
+
} else {
|
|
73
|
+
t0 = $[2];
|
|
74
|
+
}
|
|
75
|
+
const [transition, setTransition] = useState(t0);
|
|
76
|
+
if (transition.month !== month) {
|
|
77
|
+
setTransition({
|
|
78
|
+
direction: resolveMonthTransitionDirection(transition.month, month),
|
|
79
|
+
month
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
let t1;
|
|
83
|
+
let t2;
|
|
84
|
+
if ($[3] !== month || $[4] !== scopeKey) {
|
|
85
|
+
t1 = () => {
|
|
86
|
+
rememberShownMonth(scopeKey, month);
|
|
87
|
+
return () => rememberShownMonth(scopeKey, month);
|
|
88
|
+
};
|
|
89
|
+
t2 = [month, scopeKey];
|
|
90
|
+
$[3] = month;
|
|
91
|
+
$[4] = scopeKey;
|
|
92
|
+
$[5] = t1;
|
|
93
|
+
$[6] = t2;
|
|
94
|
+
} else {
|
|
95
|
+
t1 = $[5];
|
|
96
|
+
t2 = $[6];
|
|
97
|
+
}
|
|
98
|
+
useEffect(t1, t2);
|
|
99
|
+
let t3;
|
|
100
|
+
if ($[7] !== month || $[8] !== transition.direction || $[9] !== transition.month) {
|
|
101
|
+
t3 = transition.month === month ? transition.direction : resolveMonthTransitionDirection(transition.month, month);
|
|
102
|
+
$[7] = month;
|
|
103
|
+
$[8] = transition.direction;
|
|
104
|
+
$[9] = transition.month;
|
|
105
|
+
$[10] = t3;
|
|
106
|
+
} else {
|
|
107
|
+
t3 = $[10];
|
|
108
|
+
}
|
|
109
|
+
return t3;
|
|
110
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Owns the state of `RichLinkEditor`: content, tracked links, popover and selection. */
|
|
2
|
+
import { type ClipboardEvent, type FormEvent, type KeyboardEvent } from "react";
|
|
3
|
+
import { RICH_LINK_POPOVER_MODE } from "../lib/rich-text/link-markdown-constants.js";
|
|
4
|
+
import type { ActiveRichPreviewLink, RichLink, RichPreviewSegment } from "../lib/rich-text/link-markdown-types.js";
|
|
5
|
+
type RichLinkPopoverMode = (typeof RICH_LINK_POPOVER_MODE)[keyof typeof RICH_LINK_POPOVER_MODE];
|
|
6
|
+
/** Plain-text content plus tracked links, used to snapshot/restore the editor. */
|
|
7
|
+
export type RichLinkEditorDisplayState = {
|
|
8
|
+
content: string;
|
|
9
|
+
links: RichLink[];
|
|
10
|
+
};
|
|
11
|
+
type UseRichLinkEditorOptions = {
|
|
12
|
+
/** Persisted markdown loaded as the initial editor value. */
|
|
13
|
+
initialMarkdown?: string;
|
|
14
|
+
/** Called whenever the author changes the content (e.g. to clear errors). */
|
|
15
|
+
onContentChange?: () => void;
|
|
16
|
+
};
|
|
17
|
+
/** Imperative + reactive API exposed by `useRichLinkEditor` to the editor UI. */
|
|
18
|
+
export type RichLinkEditorController = {
|
|
19
|
+
setEditorElement: (node: HTMLDivElement | null) => void;
|
|
20
|
+
segments: RichPreviewSegment[];
|
|
21
|
+
hasContent: boolean;
|
|
22
|
+
content: string;
|
|
23
|
+
activeLink: ActiveRichPreviewLink | null;
|
|
24
|
+
linkTextInput: string;
|
|
25
|
+
linkUrlInput: string;
|
|
26
|
+
popoverMode: RichLinkPopoverMode;
|
|
27
|
+
/** Whether the popover draft has visible text and an http(s) URL, so it can be saved. */
|
|
28
|
+
isLinkEditValid: boolean;
|
|
29
|
+
/** Whether the author typed a URL that cannot become a safe http(s) link. */
|
|
30
|
+
hasInvalidLinkUrl: boolean;
|
|
31
|
+
setLinkTextInput: (value: string) => void;
|
|
32
|
+
setLinkUrlInput: (value: string) => void;
|
|
33
|
+
setPopoverMode: (mode: RichLinkPopoverMode) => void;
|
|
34
|
+
handleBeforeInput: (event: FormEvent<HTMLDivElement>) => void;
|
|
35
|
+
handleInput: (event: FormEvent<HTMLDivElement>) => void;
|
|
36
|
+
handleKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;
|
|
37
|
+
handlePaste: (event: ClipboardEvent<HTMLDivElement>) => void;
|
|
38
|
+
openLinkPopover: (segment: ActiveRichPreviewLink) => void;
|
|
39
|
+
closeLinkPopover: () => void;
|
|
40
|
+
removeLink: (segment: ActiveRichPreviewLink) => void;
|
|
41
|
+
saveLinkEdit: (segment: ActiveRichPreviewLink) => void;
|
|
42
|
+
serialize: () => string;
|
|
43
|
+
reset: (markdown?: string) => void;
|
|
44
|
+
getDisplayState: () => RichLinkEditorDisplayState;
|
|
45
|
+
loadFromDisplay: (state: RichLinkEditorDisplayState) => void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Controls a rich link editor backed by a `contentEditable` element: it owns the
|
|
49
|
+
* plain-text content, the tracked explicit/suppressed links, the link popover
|
|
50
|
+
* state, and the selection bookkeeping, and exposes serialize/reset/snapshot
|
|
51
|
+
* helpers so a feature can persist or restore the value. The DOM rendering lives
|
|
52
|
+
* in `RichLinkEditor`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function useRichLinkEditor(options?: UseRichLinkEditorOptions): RichLinkEditorController;
|
|
55
|
+
export {};
|