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,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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SPRING_LAYOUT, SPRING_TABS } from "./tokens.js";
|
|
1
|
+
import { SPRING_HIGHLIGHT, SPRING_LAYOUT, SPRING_TABS } from "./tokens.js";
|
|
2
2
|
/** Which part of the active item carries the highlight that must travel. */
|
|
3
3
|
type GlideSurface = "background" | "after";
|
|
4
4
|
/** Describes the items of one container and how their active state is exposed. */
|
|
@@ -13,7 +13,13 @@ export interface GlidePreset {
|
|
|
13
13
|
attributes: string[];
|
|
14
14
|
isActive: (item: Element) => boolean;
|
|
15
15
|
surface: (container: HTMLElement) => GlideSurface;
|
|
16
|
-
spring: typeof SPRING_LAYOUT | typeof SPRING_TABS;
|
|
16
|
+
spring: typeof SPRING_LAYOUT | typeof SPRING_TABS | typeof SPRING_HIGHLIGHT;
|
|
17
|
+
/**
|
|
18
|
+
* Highlights that follow the pointer (menus, listboxes) jump straight to the hovered item:
|
|
19
|
+
* the pointer already marks the position, so a travelling copy would only trail behind it.
|
|
20
|
+
* The glide is kept for keyboard navigation.
|
|
21
|
+
*/
|
|
22
|
+
instantOnPointer?: boolean;
|
|
17
23
|
}
|
|
18
24
|
export declare const GLIDE_PRESETS: {
|
|
19
25
|
tabs: {
|
|
@@ -42,7 +48,7 @@ export declare const GLIDE_PRESETS: {
|
|
|
42
48
|
readonly mass: 0.6;
|
|
43
49
|
};
|
|
44
50
|
};
|
|
45
|
-
|
|
51
|
+
pagination: {
|
|
46
52
|
itemSelector: string;
|
|
47
53
|
containerSelector: string;
|
|
48
54
|
attributes: string[];
|
|
@@ -55,6 +61,34 @@ export declare const GLIDE_PRESETS: {
|
|
|
55
61
|
readonly mass: 0.6;
|
|
56
62
|
};
|
|
57
63
|
};
|
|
64
|
+
suggestions: {
|
|
65
|
+
itemSelector: string;
|
|
66
|
+
containerSelector: string;
|
|
67
|
+
attributes: string[];
|
|
68
|
+
isActive: (item: Element) => boolean;
|
|
69
|
+
surface: () => "background";
|
|
70
|
+
spring: {
|
|
71
|
+
readonly type: "spring";
|
|
72
|
+
readonly stiffness: 700;
|
|
73
|
+
readonly damping: 45;
|
|
74
|
+
readonly mass: 0.5;
|
|
75
|
+
};
|
|
76
|
+
instantOnPointer: true;
|
|
77
|
+
};
|
|
78
|
+
menu: {
|
|
79
|
+
itemSelector: string;
|
|
80
|
+
containerSelector: string;
|
|
81
|
+
attributes: string[];
|
|
82
|
+
isActive: (item: Element) => boolean;
|
|
83
|
+
surface: () => "background";
|
|
84
|
+
spring: {
|
|
85
|
+
readonly type: "spring";
|
|
86
|
+
readonly stiffness: 700;
|
|
87
|
+
readonly damping: 45;
|
|
88
|
+
readonly mass: 0.5;
|
|
89
|
+
};
|
|
90
|
+
instantOnPointer: true;
|
|
91
|
+
};
|
|
58
92
|
};
|
|
59
93
|
export type GlidePresetName = keyof typeof GLIDE_PRESETS;
|
|
60
94
|
/** Marks the item whose own highlight is hidden while the travelling copy stands in for it. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* previous item and is removed once it lands, leaving the item's own styles in charge again.
|
|
5
5
|
*/
|
|
6
6
|
import { animate } from "motion/react";
|
|
7
|
-
import { GLIDE_CONTINUITY_MS, SPRING_LAYOUT, SPRING_TABS } from "./tokens.js";
|
|
7
|
+
import { GLIDE_CONTINUITY_MS, SPRING_HIGHLIGHT, SPRING_LAYOUT, SPRING_TABS } from "./tokens.js";
|
|
8
8
|
export const GLIDE_PRESETS = {
|
|
9
9
|
tabs: {
|
|
10
10
|
itemSelector: "[data-slot=\"tabs-trigger\"]",
|
|
@@ -22,13 +22,31 @@ export const GLIDE_PRESETS = {
|
|
|
22
22
|
surface: () => "background",
|
|
23
23
|
spring: SPRING_LAYOUT
|
|
24
24
|
},
|
|
25
|
+
pagination: {
|
|
26
|
+
itemSelector: "[data-slot=\"pagination-link\"]",
|
|
27
|
+
containerSelector: "[data-slot=\"pagination-content\"]",
|
|
28
|
+
attributes: ["aria-current"],
|
|
29
|
+
isActive: (item) => item.getAttribute("aria-current") === "page",
|
|
30
|
+
surface: () => "background",
|
|
31
|
+
spring: SPRING_LAYOUT
|
|
32
|
+
},
|
|
33
|
+
suggestions: {
|
|
34
|
+
itemSelector: "[role=\"option\"]",
|
|
35
|
+
containerSelector: "[role=\"listbox\"]",
|
|
36
|
+
attributes: ["aria-selected"],
|
|
37
|
+
isActive: (item) => item.getAttribute("aria-selected") === "true",
|
|
38
|
+
surface: () => "background",
|
|
39
|
+
spring: SPRING_HIGHLIGHT,
|
|
40
|
+
instantOnPointer: true
|
|
41
|
+
},
|
|
25
42
|
menu: {
|
|
26
43
|
itemSelector: "[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"],[role=\"option\"]",
|
|
27
44
|
containerSelector: "[role=\"menu\"],[role=\"listbox\"]",
|
|
28
45
|
attributes: ["data-highlighted"],
|
|
29
46
|
isActive: (item) => item.hasAttribute("data-highlighted"),
|
|
30
47
|
surface: () => "background",
|
|
31
|
-
spring:
|
|
48
|
+
spring: SPRING_HIGHLIGHT,
|
|
49
|
+
instantOnPointer: true
|
|
32
50
|
}
|
|
33
51
|
};
|
|
34
52
|
/** Marks the item whose own highlight is hidden while the travelling copy stands in for it. */
|
|
@@ -136,6 +154,20 @@ export function attachGlideIndicator(container, preset) {
|
|
|
136
154
|
position: container.style.position,
|
|
137
155
|
isolation: container.style.isolation
|
|
138
156
|
};
|
|
157
|
+
// The last input decides whether a highlight change was driven by the pointer or the keyboard.
|
|
158
|
+
// Keyboard focus may live outside the container (a combobox input), so both are read from the
|
|
159
|
+
// document; the pointer only counts while it moves over this container.
|
|
160
|
+
let isPointerDriven = false;
|
|
161
|
+
const onPointerMove = () => {
|
|
162
|
+
isPointerDriven = true;
|
|
163
|
+
};
|
|
164
|
+
const onKeyDown = () => {
|
|
165
|
+
isPointerDriven = false;
|
|
166
|
+
};
|
|
167
|
+
if (preset.instantOnPointer) {
|
|
168
|
+
container.addEventListener("pointermove", onPointerMove);
|
|
169
|
+
container.ownerDocument.addEventListener("keydown", onKeyDown, true);
|
|
170
|
+
}
|
|
139
171
|
/** Puts the item's own highlight back and removes the travelling copy. */
|
|
140
172
|
function settle() {
|
|
141
173
|
animation?.cancel();
|
|
@@ -236,7 +268,7 @@ export function attachGlideIndicator(container, preset) {
|
|
|
236
268
|
const isContinuous = previous !== null && previous !== active && previous.isConnected && !preset.isActive(previous) && (lastActiveSeenAt === Number.NEGATIVE_INFINITY || now - lastActiveSeenAt <= GLIDE_CONTINUITY_MS);
|
|
237
269
|
lastActive = active;
|
|
238
270
|
lastActiveSeenAt = Number.NEGATIVE_INFINITY;
|
|
239
|
-
if (isContinuous) glide(previous, active);
|
|
271
|
+
if (isContinuous && !(preset.instantOnPointer && isPointerDriven)) glide(previous, active);
|
|
240
272
|
else settle();
|
|
241
273
|
});
|
|
242
274
|
observer.observe(container, {
|
|
@@ -247,6 +279,8 @@ export function attachGlideIndicator(container, preset) {
|
|
|
247
279
|
});
|
|
248
280
|
return () => {
|
|
249
281
|
observer.disconnect();
|
|
282
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
283
|
+
container.ownerDocument.removeEventListener("keydown", onKeyDown, true);
|
|
250
284
|
settle();
|
|
251
285
|
};
|
|
252
286
|
}
|
|
@@ -6,7 +6,7 @@ import { Slot } from "radix-ui";
|
|
|
6
6
|
import { animate, hover, press, usePresence, frame } from "motion/react";
|
|
7
7
|
import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
|
|
8
8
|
import { HOVER_CAPABLE_QUERY, MILLISECONDS_PER_SECOND } from "../constants/motion.js";
|
|
9
|
-
import { MOTION_EASE, MOTION_TIMING, MOTION_PRESS_SCALE, MOTION_SUBTLE_PRESS_SCALE, MOTION_TOGGLE_PRESS_SCALE, MOTION_HOVER_SCALE, MOTION_THUMB_SQUISH_SCALE, MOTION_CONTENT_DISTANCE, MOTION_INVALID_SHAKE_PX, MOTION_ITEM_CHECK_SCALE, MOTION_ICON_SWAP_SCALE, MOTION_CLOSE_BUTTON_SCALE, MOTION_LIST_ITEM_DISTANCE, MOTION_LIST_STAGGER_LIMIT, MOTION_EASE_IN_OUT, SPRING_CHEVRON, SPRING_PANEL, SPRING_PRESS, SPRING_THUMB } from "./tokens.js";
|
|
9
|
+
import { MOTION_EASE, MOTION_TIMING, MOTION_PRESS_SCALE, MOTION_SUBTLE_PRESS_SCALE, MOTION_TOGGLE_PRESS_SCALE, MOTION_HOVER_SCALE, MOTION_THUMB_SQUISH_SCALE, MOTION_CONTENT_DISTANCE, MOTION_INVALID_SHAKE_PX, MOTION_ITEM_CHECK_SCALE, MOTION_ICON_SWAP_SCALE, MOTION_CLOSE_BUTTON_SCALE, MOTION_LIST_ITEM_DISTANCE, MOTION_LIST_STAGGER_LIMIT, MOTION_EASE_IN_OUT, MOTION_REVEAL_BLUR_PX, MOTION_REVEAL_SCALE, MOTION_SHIMMER_TRANSLATE, MOTION_CARD_SHADOW_LIFT, MOTION_CARD_SHADOW_REST, SPRING_CHEVRON, SPRING_PANEL, SPRING_PRESS, SPRING_THUMB } from "./tokens.js";
|
|
10
10
|
import { surfaceEnter, surfaceExit } from "./surface-keyframes.js";
|
|
11
11
|
import { useAttachedElement } from "./use-attached-element.js";
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -277,16 +277,14 @@ export function MotionSlot(t0) {
|
|
|
277
277
|
});
|
|
278
278
|
return dispose;
|
|
279
279
|
}
|
|
280
|
-
if (kind === "skeleton") {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ease: "easeInOut"
|
|
289
|
-
}), ["opacity"]);
|
|
280
|
+
if (kind === "skeleton" && typeof element.animate === "function") {
|
|
281
|
+
const shimmer = element.animate({ translate: [...MOTION_SHIMMER_TRANSLATE] }, {
|
|
282
|
+
duration: MOTION_TIMING.skeleton * MILLISECONDS_PER_SECOND,
|
|
283
|
+
easing: `cubic-bezier(${MOTION_EASE_IN_OUT.join(", ")})`,
|
|
284
|
+
iterations: Infinity,
|
|
285
|
+
pseudoElement: "::after"
|
|
286
|
+
});
|
|
287
|
+
cleanups.push(() => shimmer.cancel());
|
|
290
288
|
} else {
|
|
291
289
|
if (kind === "icon-swap") {
|
|
292
290
|
play({
|
|
@@ -398,6 +396,13 @@ export function MotionSlot(t0) {
|
|
|
398
396
|
}));
|
|
399
397
|
}
|
|
400
398
|
}
|
|
399
|
+
let restingShadow = "none";
|
|
400
|
+
const withRestingShadow = (shadow) => {
|
|
401
|
+
if (!element.style.boxShadow) {
|
|
402
|
+
restingShadow = getComputedStyle(element).boxShadow;
|
|
403
|
+
}
|
|
404
|
+
return restingShadow === "none" ? shadow : `${restingShadow}, ${shadow}`;
|
|
405
|
+
};
|
|
401
406
|
if (kind === "card" || kind === "link" || kind === "icon") {
|
|
402
407
|
cleanups.push(hover(element, () => {
|
|
403
408
|
if (!isEnabled(element) || !canHover() || kind === "card" && !element.querySelector("a,button")) {
|
|
@@ -405,7 +410,9 @@ export function MotionSlot(t0) {
|
|
|
405
410
|
}
|
|
406
411
|
const keep = { restoreOnComplete: false };
|
|
407
412
|
if (kind === "card") {
|
|
408
|
-
|
|
413
|
+
const rest = withRestingShadow(MOTION_CARD_SHADOW_REST);
|
|
414
|
+
const from = element.style.boxShadow ? getComputedStyle(element).boxShadow : rest;
|
|
415
|
+
play({ boxShadow: [from, withRestingShadow(MOTION_CARD_SHADOW_LIFT)] }, tween(MOTION_TIMING.enter), keep);
|
|
409
416
|
} else {
|
|
410
417
|
if (kind === "icon") {
|
|
411
418
|
play({ transform: ["rotate(0deg) scale(1)", "rotate(-3deg) scale(1.025)"] }, SPRING_PRESS, keep);
|
|
@@ -415,7 +422,7 @@ export function MotionSlot(t0) {
|
|
|
415
422
|
}
|
|
416
423
|
return () => {
|
|
417
424
|
if (kind === "card") {
|
|
418
|
-
play({ boxShadow:
|
|
425
|
+
play({ boxShadow: [getComputedStyle(element).boxShadow, withRestingShadow(MOTION_CARD_SHADOW_REST)] }, tween(MOTION_TIMING.fast));
|
|
419
426
|
} else {
|
|
420
427
|
if (kind === "icon") {
|
|
421
428
|
play({ transform: [currentTransform(element), "rotate(0deg) scale(1)"] }, SPRING_PRESS);
|
|
@@ -544,6 +551,28 @@ export function MotionSlot(t0) {
|
|
|
544
551
|
}
|
|
545
552
|
cleanups.push(() => observer_1.disconnect());
|
|
546
553
|
}
|
|
554
|
+
if (kind === "reveal") {
|
|
555
|
+
const isLoading = () => element.hasAttribute("data-loading");
|
|
556
|
+
let wasLoading = isLoading();
|
|
557
|
+
const observer_2 = new MutationObserver(() => {
|
|
558
|
+
const loading = isLoading();
|
|
559
|
+
const loaded = wasLoading && !loading && !element.hasAttribute("data-error");
|
|
560
|
+
wasLoading = loading;
|
|
561
|
+
if (!loaded) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
play({
|
|
565
|
+
opacity: [0, 1],
|
|
566
|
+
transform: [`scale(${MOTION_REVEAL_SCALE})`, "scale(1)"],
|
|
567
|
+
filter: [`blur(${MOTION_REVEAL_BLUR_PX}px)`, "blur(0px)"]
|
|
568
|
+
}, tween(MOTION_TIMING.reveal));
|
|
569
|
+
});
|
|
570
|
+
observer_2.observe(element, {
|
|
571
|
+
attributes: true,
|
|
572
|
+
attributeFilter: ["data-loading", "data-error"]
|
|
573
|
+
});
|
|
574
|
+
cleanups.push(() => observer_2.disconnect());
|
|
575
|
+
}
|
|
547
576
|
if (kind === "thumb") {
|
|
548
577
|
const restingOffset = () => {
|
|
549
578
|
if (!element.getClientRects().length) {
|
|
@@ -565,7 +594,7 @@ export function MotionSlot(t0) {
|
|
|
565
594
|
element.parentElement?.removeEventListener("pointerenter", remeasure);
|
|
566
595
|
element.parentElement?.removeEventListener("focusin", remeasure);
|
|
567
596
|
});
|
|
568
|
-
const
|
|
597
|
+
const observer_3 = new MutationObserver(() => {
|
|
569
598
|
const glideOffset = translateXOf(getComputedStyle(element).transform);
|
|
570
599
|
cancel(["transform"]);
|
|
571
600
|
restore(["transform"]);
|
|
@@ -576,11 +605,11 @@ export function MotionSlot(t0) {
|
|
|
576
605
|
play({ transform: [`translateX(${delta_0}px)`, "translateX(0px)"] }, SPRING_THUMB);
|
|
577
606
|
}
|
|
578
607
|
});
|
|
579
|
-
|
|
608
|
+
observer_3.observe(element, {
|
|
580
609
|
attributes: true,
|
|
581
610
|
attributeFilter: ["data-state"]
|
|
582
611
|
});
|
|
583
|
-
cleanups.push(() =>
|
|
612
|
+
cleanups.push(() => observer_3.disconnect());
|
|
584
613
|
const root = element.parentElement;
|
|
585
614
|
if (root && typeof element.animate === "function") {
|
|
586
615
|
const squishTiming = {
|
|
@@ -597,9 +626,9 @@ export function MotionSlot(t0) {
|
|
|
597
626
|
squish?.cancel();
|
|
598
627
|
squish = element.animate({ scale: ["1", String(MOTION_THUMB_SQUISH_SCALE)] }, squishTiming);
|
|
599
628
|
return () => {
|
|
600
|
-
const
|
|
629
|
+
const from_0 = getComputedStyle(element).scale;
|
|
601
630
|
squish?.cancel();
|
|
602
|
-
const release = element.animate({ scale: [
|
|
631
|
+
const release = element.animate({ scale: [from_0 === "none" ? "1" : from_0, "1"] }, squishTiming);
|
|
603
632
|
squish = release;
|
|
604
633
|
release.finished.then(() => {
|
|
605
634
|
if (squish === release) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { MOTION_DIALOG_DISTANCE, MOTION_EASE, MOTION_SURFACE_CLIP_PERCENT, MOTION_SURFACE_SCALE, MOTION_TIMING, MOTION_TOOLTIP_DISTANCE, SPRING_PANEL, SPRING_TOOLTIP } from "./tokens.js";
|
|
1
|
+
import { MOTION_DIALOG_DISTANCE, MOTION_EASE, MOTION_EASE_DRAWER, MOTION_SURFACE_CLIP_BLEED_PX, MOTION_SURFACE_CLIP_PERCENT, MOTION_SURFACE_SCALE, MOTION_TIMING, MOTION_TOOLTIP_DISTANCE, SPRING_PANEL, SPRING_TOOLTIP } from "./tokens.js";
|
|
2
2
|
const EASE_OUT = [...MOTION_EASE];
|
|
3
|
+
const EASE_DRAWER = [...MOTION_EASE_DRAWER];
|
|
3
4
|
const SIDES = [
|
|
4
5
|
"top",
|
|
5
6
|
"right",
|
|
@@ -48,18 +49,25 @@ function offsetAwayFromTrigger(side, distance) {
|
|
|
48
49
|
y: 0
|
|
49
50
|
};
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Open edges extend the clip past the box, so the ring and drop shadow drawn outside it
|
|
54
|
+
* (Tailwind rings are box-shadows) are never cut while the surface reveals or settles.
|
|
55
|
+
*/
|
|
56
|
+
const OPEN_EDGE = `-${MOTION_SURFACE_CLIP_BLEED_PX}px`;
|
|
57
|
+
/** Fully revealed clip: nothing of the surface, its outline or its shadow is hidden. */
|
|
58
|
+
const REVEALED_CLIP = `inset(${OPEN_EDGE} ${OPEN_EDGE} ${OPEN_EDGE} ${OPEN_EDGE})`;
|
|
51
59
|
/** Collapsed clip anchored at the corner or edge closest to the trigger. */
|
|
52
60
|
function collapsedClip(side, align) {
|
|
53
61
|
const hidden = `${MOTION_SURFACE_CLIP_PERCENT}%`;
|
|
54
62
|
const half = `${MOTION_SURFACE_CLIP_PERCENT / 2}%`;
|
|
55
63
|
/** Maps an alignment to the inset kept on the leading and trailing edges of the cross axis. */
|
|
56
|
-
const crossAxis = (value) => value === "start" ? [
|
|
64
|
+
const crossAxis = (value) => value === "start" ? [OPEN_EDGE, hidden] : value === "end" ? [hidden, OPEN_EDGE] : [half, half];
|
|
57
65
|
if (side === "bottom" || side === "top") {
|
|
58
66
|
const [left, right] = crossAxis(align);
|
|
59
|
-
return side === "bottom" ? `inset(
|
|
67
|
+
return side === "bottom" ? `inset(${OPEN_EDGE} ${right} ${hidden} ${left})` : `inset(${hidden} ${right} ${OPEN_EDGE} ${left})`;
|
|
60
68
|
}
|
|
61
69
|
const [top, bottom] = crossAxis(align);
|
|
62
|
-
return side === "right" ? `inset(${top} ${hidden} ${bottom}
|
|
70
|
+
return side === "right" ? `inset(${top} ${hidden} ${bottom} ${OPEN_EDGE})` : `inset(${top} ${OPEN_EDGE} ${bottom} ${hidden})`;
|
|
63
71
|
}
|
|
64
72
|
/** Edge-panel travel: fully off-screen on the side the sheet is attached to. */
|
|
65
73
|
function sheetOffset(element) {
|
|
@@ -136,7 +144,7 @@ export function surfaceEnter(element, kind) {
|
|
|
136
144
|
};
|
|
137
145
|
// Primitives aligned over their trigger (Select item-aligned) have no side to reveal from.
|
|
138
146
|
if (side) {
|
|
139
|
-
keyframes.clipPath = [collapsedClip(side, align),
|
|
147
|
+
keyframes.clipPath = [collapsedClip(side, align), REVEALED_CLIP];
|
|
140
148
|
}
|
|
141
149
|
return {
|
|
142
150
|
keyframes,
|
|
@@ -190,9 +198,13 @@ export function surfaceExit(element, kind) {
|
|
|
190
198
|
}
|
|
191
199
|
if (kind === "sheet") {
|
|
192
200
|
const offset = sheetOffset(element);
|
|
201
|
+
// A spring would linger at the edge; the drawer curve accelerates the panel away.
|
|
193
202
|
return {
|
|
194
203
|
keyframes: { transform: [transform("0%", "0%"), transform(offset.x, offset.y)] },
|
|
195
|
-
transition:
|
|
204
|
+
transition: {
|
|
205
|
+
duration: MOTION_TIMING.sheetExit,
|
|
206
|
+
ease: EASE_DRAWER
|
|
207
|
+
}
|
|
196
208
|
};
|
|
197
209
|
}
|
|
198
210
|
return {
|
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;
|
|
@@ -16,6 +19,14 @@ export declare const MOTION_TIMING: {
|
|
|
16
19
|
readonly listStagger: 0.035;
|
|
17
20
|
readonly closeEnter: 0.2;
|
|
18
21
|
readonly closeDelay: 0.16;
|
|
22
|
+
readonly sheetExit: 0.24;
|
|
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;
|
|
28
|
+
readonly monthSwap: 0.24;
|
|
29
|
+
readonly cursorBlink: 1;
|
|
19
30
|
readonly skeleton: 1.8;
|
|
20
31
|
};
|
|
21
32
|
/** Items beyond this position share the last stagger delay, so long lists never lag behind. */
|
|
@@ -47,6 +58,13 @@ export declare const SPRING_LAYOUT: {
|
|
|
47
58
|
readonly damping: 32;
|
|
48
59
|
readonly mass: 0.6;
|
|
49
60
|
};
|
|
61
|
+
/** Keyboard highlight in menus and lists: quick enough to keep up with a held arrow key. */
|
|
62
|
+
export declare const SPRING_HIGHLIGHT: {
|
|
63
|
+
readonly type: "spring";
|
|
64
|
+
readonly stiffness: 700;
|
|
65
|
+
readonly damping: 45;
|
|
66
|
+
readonly mass: 0.5;
|
|
67
|
+
};
|
|
50
68
|
/** Tab indicator glide, tuned to settle without overshooting the list edges. */
|
|
51
69
|
export declare const SPRING_TABS: {
|
|
52
70
|
readonly type: "spring";
|
|
@@ -74,6 +92,13 @@ export declare const SPRING_THUMB: {
|
|
|
74
92
|
readonly damping: 80;
|
|
75
93
|
readonly mass: 4;
|
|
76
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
|
+
};
|
|
77
102
|
/** Primary actions compress noticeably; dense navigation items stay subtle. */
|
|
78
103
|
export declare const MOTION_PRESS_SCALE = 0.93;
|
|
79
104
|
export declare const MOTION_SUBTLE_PRESS_SCALE = 0.98;
|
|
@@ -84,13 +109,27 @@ export declare const MOTION_ITEM_CHECK_SCALE = 0.75;
|
|
|
84
109
|
export declare const MOTION_ICON_SWAP_SCALE = 0.25;
|
|
85
110
|
export declare const MOTION_CLOSE_BUTTON_SCALE = 0.8;
|
|
86
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;
|
|
114
|
+
/** Loaded images settle from a slight zoom, so they develop in place instead of popping in. */
|
|
115
|
+
export declare const MOTION_REVEAL_SCALE = 1.06;
|
|
116
|
+
export declare const MOTION_REVEAL_BLUR_PX = 6;
|
|
117
|
+
/** Calendar months slide just enough to show the direction of navigation. */
|
|
118
|
+
export declare const MOTION_MONTH_DISTANCE = 12;
|
|
119
|
+
/** Skeleton shine travels from fully before the box to fully past it, as a `translate` value. */
|
|
120
|
+
export declare const MOTION_SHIMMER_TRANSLATE: readonly ["-100% 0", "100% 0"];
|
|
121
|
+
/** Hovered interactive cards gain a soft drop shadow; the rest value has the same shape, invisible. */
|
|
122
|
+
export declare const MOTION_CARD_SHADOW_LIFT = "0px 4px 16px 0px rgba(0, 0, 0, 0.08)";
|
|
123
|
+
export declare const MOTION_CARD_SHADOW_REST = "0px 0px 0px 0px rgba(0, 0, 0, 0)";
|
|
87
124
|
export declare const MOTION_SURFACE_SCALE = 0.96;
|
|
88
125
|
/** Fraction of the surface kept hidden by the clip at the start of its reveal, in percent. */
|
|
89
126
|
export declare const MOTION_SURFACE_CLIP_PERCENT = 92;
|
|
127
|
+
/** Room left outside a revealing surface for its ring and drop shadow (`shadow-lg` spans ~22px). */
|
|
128
|
+
export declare const MOTION_SURFACE_CLIP_BLEED_PX = 24;
|
|
90
129
|
export declare const MOTION_TOOLTIP_DISTANCE = 8;
|
|
91
130
|
export declare const MOTION_DIALOG_DISTANCE = 20;
|
|
92
131
|
export declare const MOTION_CONTENT_DISTANCE = 4;
|
|
93
132
|
export declare const MOTION_INVALID_SHAKE_PX: readonly [0, -6, 6, -4, 4, -2, 0];
|
|
94
133
|
/** Time after an item loses its active state during which a new active item still glides from it. */
|
|
95
134
|
export declare const GLIDE_CONTINUITY_MS = 300;
|
|
96
|
-
export type MotionKind = "press" | "subtle-press" | "toggle" | "fade" | "content" | "message" | "card" | "row" | "field" | "thumb" | "check" | "selection" | "item-check" | "rotate" | "list-item" | "icon-swap" | "delayed-pop" | "skeleton" | "link" | "icon" | "tooltip" | "surface" | "dialog" | "sheet" | "overlay";
|
|
135
|
+
export type MotionKind = "press" | "subtle-press" | "toggle" | "fade" | "content" | "message" | "card" | "row" | "field" | "thumb" | "check" | "selection" | "item-check" | "rotate" | "list-item" | "icon-swap" | "delayed-pop" | "skeleton" | "reveal" | "link" | "icon" | "tooltip" | "surface" | "dialog" | "sheet" | "overlay";
|