beez-ui 0.7.0 → 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 +21 -3
- package/README.md +290 -274
- package/dist/components/account-menu.d.ts +53 -0
- package/dist/components/account-menu.js +405 -0
- 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/bouncing-dots-loader.d.ts +19 -0
- package/dist/components/bouncing-dots-loader.js +47 -0
- package/dist/components/confirm-delete-button.d.ts +29 -0
- package/dist/components/confirm-delete-button.js +254 -0
- 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/info-popover.d.ts +23 -0
- package/dist/components/info-popover.js +158 -0
- 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/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/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/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/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/tokens.d.ts +17 -1
- package/dist/motion/tokens.js +17 -1
- package/dist/styles.css +1 -1
- package/package.json +211 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { parseRichTextSegments } from "./link-markdown.js";
|
|
2
|
+
import { RICH_TEXT_SEGMENT_TYPE } from "./link-markdown-constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Block-level markdown subset for long-form content such as descriptions and stories.
|
|
5
|
+
*
|
|
6
|
+
* It builds on top of the shared link parser (`parseRichTextSegments`) without
|
|
7
|
+
* touching it, adding paragraphs, unordered lists (`- item` / `* item`) and
|
|
8
|
+
* inline bold (`**text**`). Everything else stays plain text, so the renderer
|
|
9
|
+
* never interprets raw HTML.
|
|
10
|
+
*/
|
|
11
|
+
export const RICH_MARKDOWN_BLOCK_TYPE = {
|
|
12
|
+
list: "list",
|
|
13
|
+
paragraph: "paragraph"
|
|
14
|
+
};
|
|
15
|
+
export const RICH_MARKDOWN_INLINE_TYPE = {
|
|
16
|
+
bold: "bold",
|
|
17
|
+
link: "link",
|
|
18
|
+
text: "text"
|
|
19
|
+
};
|
|
20
|
+
const LINE_BREAK_PATTERN = /\r?\n/;
|
|
21
|
+
const LIST_ITEM_PATTERN = /^\s*[-*]\s+(.*)$/;
|
|
22
|
+
const BOLD_PATTERN = /\*\*([^*]+)\*\*/g;
|
|
23
|
+
const PARAGRAPH_LINE_SEPARATOR = "\n";
|
|
24
|
+
function parseInlineLinks(text) {
|
|
25
|
+
return parseRichTextSegments(text).map((segment) => segment.type === RICH_TEXT_SEGMENT_TYPE.link ? {
|
|
26
|
+
text: segment.text,
|
|
27
|
+
type: RICH_MARKDOWN_INLINE_TYPE.link,
|
|
28
|
+
url: segment.url
|
|
29
|
+
} : {
|
|
30
|
+
text: segment.text,
|
|
31
|
+
type: RICH_MARKDOWN_INLINE_TYPE.text
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function parseInlineSegments(text) {
|
|
35
|
+
const segments = [];
|
|
36
|
+
let lastIndex = 0;
|
|
37
|
+
for (const boldMatch of text.matchAll(BOLD_PATTERN)) {
|
|
38
|
+
const matchIndex = boldMatch.index ?? 0;
|
|
39
|
+
if (matchIndex > lastIndex) {
|
|
40
|
+
segments.push(...parseInlineLinks(text.slice(lastIndex, matchIndex)));
|
|
41
|
+
}
|
|
42
|
+
segments.push({
|
|
43
|
+
text: boldMatch[1],
|
|
44
|
+
type: RICH_MARKDOWN_INLINE_TYPE.bold
|
|
45
|
+
});
|
|
46
|
+
lastIndex = matchIndex + boldMatch[0].length;
|
|
47
|
+
}
|
|
48
|
+
if (lastIndex < text.length) {
|
|
49
|
+
segments.push(...parseInlineLinks(text.slice(lastIndex)));
|
|
50
|
+
}
|
|
51
|
+
return segments;
|
|
52
|
+
}
|
|
53
|
+
const EXCERPT_SEGMENT_SEPARATOR = " ";
|
|
54
|
+
const EXCERPT_ELLIPSIS = "…";
|
|
55
|
+
/**
|
|
56
|
+
* Flattens the parsed content into plain text (formatting stripped) and trims it
|
|
57
|
+
* to `maxLength`, for SEO descriptions and previews.
|
|
58
|
+
*/
|
|
59
|
+
export function buildRichMarkdownExcerpt(content, maxLength) {
|
|
60
|
+
const plainText = parseRichMarkdownBlocks(content).flatMap((block) => block.type === RICH_MARKDOWN_BLOCK_TYPE.paragraph ? block.segments.map((segment) => segment.text) : block.items.flatMap((itemSegments) => itemSegments.map((segment) => segment.text))).join(EXCERPT_SEGMENT_SEPARATOR).replace(/\s+/g, EXCERPT_SEGMENT_SEPARATOR).trim();
|
|
61
|
+
if (plainText.length <= maxLength) {
|
|
62
|
+
return plainText;
|
|
63
|
+
}
|
|
64
|
+
return plainText.slice(0, maxLength - EXCERPT_ELLIPSIS.length).trimEnd() + EXCERPT_ELLIPSIS;
|
|
65
|
+
}
|
|
66
|
+
export function parseRichMarkdownBlocks(content) {
|
|
67
|
+
const blocks = [];
|
|
68
|
+
let paragraphLines = [];
|
|
69
|
+
let listItems = [];
|
|
70
|
+
const flushParagraph = () => {
|
|
71
|
+
if (paragraphLines.length > 0) {
|
|
72
|
+
blocks.push({
|
|
73
|
+
segments: parseInlineSegments(paragraphLines.join(PARAGRAPH_LINE_SEPARATOR)),
|
|
74
|
+
type: RICH_MARKDOWN_BLOCK_TYPE.paragraph
|
|
75
|
+
});
|
|
76
|
+
paragraphLines = [];
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const flushList = () => {
|
|
80
|
+
if (listItems.length > 0) {
|
|
81
|
+
blocks.push({
|
|
82
|
+
items: listItems,
|
|
83
|
+
type: RICH_MARKDOWN_BLOCK_TYPE.list
|
|
84
|
+
});
|
|
85
|
+
listItems = [];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
for (const line of content.split(LINE_BREAK_PATTERN)) {
|
|
89
|
+
const listItemMatch = LIST_ITEM_PATTERN.exec(line);
|
|
90
|
+
if (listItemMatch) {
|
|
91
|
+
flushParagraph();
|
|
92
|
+
listItems.push(parseInlineSegments(listItemMatch[1]));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
flushList();
|
|
96
|
+
if (line.trim().length === 0) {
|
|
97
|
+
flushParagraph();
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
paragraphLines.push(line);
|
|
101
|
+
}
|
|
102
|
+
flushParagraph();
|
|
103
|
+
flushList();
|
|
104
|
+
return blocks;
|
|
105
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM helpers that translate between the rich link editor's plain-text offsets
|
|
3
|
+
* and the browser `Selection`/`Range` API. Kept apart from the pure helpers so
|
|
4
|
+
* the latter can be unit-tested without a DOM.
|
|
5
|
+
*/
|
|
6
|
+
import type { RichTextSelectionRange } from "./link-markdown-types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Returns the plain-text character offset within `editor` for a DOM
|
|
9
|
+
* `(container, offset)` boundary, or `null` when it cannot be resolved.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getEditorBoundaryOffset(editor: HTMLDivElement, container: Node, offset: number): number | null;
|
|
12
|
+
/**
|
|
13
|
+
* Reads the current selection as a plain-text offset range within `editor`, or
|
|
14
|
+
* `null` when there is no selection inside the editor.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getEditorSelectionRange(editor: HTMLDivElement): RichTextSelectionRange | null;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves a plain-text offset within `editor` to a DOM `(node, offset)`
|
|
19
|
+
* boundary, walking its text nodes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getEditorTextBoundary(editor: HTMLDivElement, targetOffset: number): {
|
|
22
|
+
node: Node;
|
|
23
|
+
offset: number;
|
|
24
|
+
};
|
|
25
|
+
/** Restores a plain-text offset range as the live selection inside `editor`. */
|
|
26
|
+
export declare function setEditorSelectionRange(editor: HTMLDivElement, selectionRange: RichTextSelectionRange): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the plain-text character offset within `editor` for a DOM
|
|
3
|
+
* `(container, offset)` boundary, or `null` when it cannot be resolved.
|
|
4
|
+
*/
|
|
5
|
+
export function getEditorBoundaryOffset(editor, container, offset) {
|
|
6
|
+
const boundaryRange = document.createRange();
|
|
7
|
+
boundaryRange.selectNodeContents(editor);
|
|
8
|
+
try {
|
|
9
|
+
boundaryRange.setEnd(container, offset);
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return boundaryRange.toString().length;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads the current selection as a plain-text offset range within `editor`, or
|
|
17
|
+
* `null` when there is no selection inside the editor.
|
|
18
|
+
*/
|
|
19
|
+
export function getEditorSelectionRange(editor) {
|
|
20
|
+
const selection = window.getSelection();
|
|
21
|
+
if (!selection || selection.rangeCount === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const anchorNode = selection.anchorNode;
|
|
25
|
+
const focusNode = selection.focusNode;
|
|
26
|
+
if (!anchorNode || !focusNode) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!editor.contains(anchorNode) || !editor.contains(focusNode)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const anchorOffset = getEditorBoundaryOffset(editor, anchorNode, selection.anchorOffset);
|
|
33
|
+
const focusOffset = getEditorBoundaryOffset(editor, focusNode, selection.focusOffset);
|
|
34
|
+
if (anchorOffset === null || focusOffset === null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
end: Math.max(anchorOffset, focusOffset),
|
|
39
|
+
start: Math.min(anchorOffset, focusOffset)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolves a plain-text offset within `editor` to a DOM `(node, offset)`
|
|
44
|
+
* boundary, walking its text nodes.
|
|
45
|
+
*/
|
|
46
|
+
export function getEditorTextBoundary(editor, targetOffset) {
|
|
47
|
+
const textNodeWalker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT);
|
|
48
|
+
let remainingOffset = targetOffset;
|
|
49
|
+
let currentNode = textNodeWalker.nextNode();
|
|
50
|
+
while (currentNode) {
|
|
51
|
+
const currentTextLength = currentNode.textContent?.length ?? 0;
|
|
52
|
+
if (remainingOffset <= currentTextLength) {
|
|
53
|
+
return {
|
|
54
|
+
node: currentNode,
|
|
55
|
+
offset: remainingOffset
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
remainingOffset -= currentTextLength;
|
|
59
|
+
currentNode = textNodeWalker.nextNode();
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
node: editor,
|
|
63
|
+
offset: editor.childNodes.length
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Restores a plain-text offset range as the live selection inside `editor`. */
|
|
67
|
+
export function setEditorSelectionRange(editor, selectionRange) {
|
|
68
|
+
const selection = window.getSelection();
|
|
69
|
+
if (!selection) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const editorRange = document.createRange();
|
|
73
|
+
const startBoundary = getEditorTextBoundary(editor, selectionRange.start);
|
|
74
|
+
const endBoundary = getEditorTextBoundary(editor, selectionRange.end);
|
|
75
|
+
editorRange.setStart(startBoundary.node, startBoundary.offset);
|
|
76
|
+
editorRange.setEnd(endBoundary.node, endBoundary.offset);
|
|
77
|
+
selection.removeAllRanges();
|
|
78
|
+
selection.addRange(editorRange);
|
|
79
|
+
}
|
package/dist/motion/tokens.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Defines the timing, curves and physics shared by Motion animations, aligned with beui.dev/components/motion.
|
|
3
|
+
* Public: consumers animate their own product surfaces with these tokens so they move like the library.
|
|
4
|
+
*/
|
|
2
5
|
export declare const MOTION_TIMING: {
|
|
3
6
|
readonly fast: 0.14;
|
|
4
7
|
readonly enter: 0.18;
|
|
@@ -18,6 +21,10 @@ export declare const MOTION_TIMING: {
|
|
|
18
21
|
readonly closeDelay: 0.16;
|
|
19
22
|
readonly sheetExit: 0.24;
|
|
20
23
|
readonly reveal: 0.4;
|
|
24
|
+
/** Heart and badge pops that confirm a toggle. */
|
|
25
|
+
readonly pop: 0.32;
|
|
26
|
+
/** Height of expanding and collapsing regions. */
|
|
27
|
+
readonly collapse: 0.26;
|
|
21
28
|
readonly monthSwap: 0.24;
|
|
22
29
|
readonly cursorBlink: 1;
|
|
23
30
|
readonly skeleton: 1.8;
|
|
@@ -85,6 +92,13 @@ export declare const SPRING_THUMB: {
|
|
|
85
92
|
readonly damping: 80;
|
|
86
93
|
readonly mass: 4;
|
|
87
94
|
};
|
|
95
|
+
/** Small, confident pops for counters, badges and toggled icons. */
|
|
96
|
+
export declare const SPRING_POP: {
|
|
97
|
+
readonly type: "spring";
|
|
98
|
+
readonly stiffness: 520;
|
|
99
|
+
readonly damping: 30;
|
|
100
|
+
readonly mass: 0.5;
|
|
101
|
+
};
|
|
88
102
|
/** Primary actions compress noticeably; dense navigation items stay subtle. */
|
|
89
103
|
export declare const MOTION_PRESS_SCALE = 0.93;
|
|
90
104
|
export declare const MOTION_SUBTLE_PRESS_SCALE = 0.98;
|
|
@@ -95,6 +109,8 @@ export declare const MOTION_ITEM_CHECK_SCALE = 0.75;
|
|
|
95
109
|
export declare const MOTION_ICON_SWAP_SCALE = 0.25;
|
|
96
110
|
export declare const MOTION_CLOSE_BUTTON_SCALE = 0.8;
|
|
97
111
|
export declare const MOTION_LIST_ITEM_DISTANCE = 6;
|
|
112
|
+
/** Scale a freshly inserted list item starts from, and a leaving one shrinks to. */
|
|
113
|
+
export declare const MOTION_ITEM_ENTER_SCALE = 0.98;
|
|
98
114
|
/** Loaded images settle from a slight zoom, so they develop in place instead of popping in. */
|
|
99
115
|
export declare const MOTION_REVEAL_SCALE = 1.06;
|
|
100
116
|
export declare const MOTION_REVEAL_BLUR_PX = 6;
|
package/dist/motion/tokens.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Defines the timing, curves and physics shared by Motion animations, aligned with beui.dev/components/motion.
|
|
3
|
+
* Public: consumers animate their own product surfaces with these tokens so they move like the library.
|
|
4
|
+
*/
|
|
2
5
|
export const MOTION_TIMING = {
|
|
3
6
|
fast: .14,
|
|
4
7
|
enter: .18,
|
|
@@ -18,6 +21,10 @@ export const MOTION_TIMING = {
|
|
|
18
21
|
closeDelay: .16,
|
|
19
22
|
sheetExit: .24,
|
|
20
23
|
reveal: .4,
|
|
24
|
+
/** Heart and badge pops that confirm a toggle. */
|
|
25
|
+
pop: .32,
|
|
26
|
+
/** Height of expanding and collapsing regions. */
|
|
27
|
+
collapse: .26,
|
|
21
28
|
monthSwap: .24,
|
|
22
29
|
cursorBlink: 1,
|
|
23
30
|
skeleton: 1.8
|
|
@@ -100,6 +107,13 @@ export const SPRING_THUMB = {
|
|
|
100
107
|
damping: 80,
|
|
101
108
|
mass: 4
|
|
102
109
|
};
|
|
110
|
+
/** Small, confident pops for counters, badges and toggled icons. */
|
|
111
|
+
export const SPRING_POP = {
|
|
112
|
+
type: "spring",
|
|
113
|
+
stiffness: 520,
|
|
114
|
+
damping: 30,
|
|
115
|
+
mass: .5
|
|
116
|
+
};
|
|
103
117
|
/** Primary actions compress noticeably; dense navigation items stay subtle. */
|
|
104
118
|
export const MOTION_PRESS_SCALE = .93;
|
|
105
119
|
export const MOTION_SUBTLE_PRESS_SCALE = .98;
|
|
@@ -110,6 +124,8 @@ export const MOTION_ITEM_CHECK_SCALE = .75;
|
|
|
110
124
|
export const MOTION_ICON_SWAP_SCALE = .25;
|
|
111
125
|
export const MOTION_CLOSE_BUTTON_SCALE = .8;
|
|
112
126
|
export const MOTION_LIST_ITEM_DISTANCE = 6;
|
|
127
|
+
/** Scale a freshly inserted list item starts from, and a leaving one shrinks to. */
|
|
128
|
+
export const MOTION_ITEM_ENTER_SCALE = .98;
|
|
113
129
|
/** Loaded images settle from a slight zoom, so they develop in place instead of popping in. */
|
|
114
130
|
export const MOTION_REVEAL_SCALE = 1.06;
|
|
115
131
|
export const MOTION_REVEAL_BLUR_PX = 6;
|